Instalación
npx logic2b@next add motionEl motor motion incluye la primitiva <Motion> junto con los mapas de recetas
y sus utilidades. Cada preset con nombre también puede instalarse por separado:
motion-fade,
motion-slide,
motion-scale y
motion-blur. Todos añaden motion de forma
automática.
Uso
import { Motion } from "@/components/ui/motion"
<Motion preset="fade-up">
<Card>…</Card>
</Motion>
<Motion> reproduce su receta de entrada una vez al montarse y después deja de
interferir. Genera un div por defecto; usa asChild para animar directamente
el elemento hijo sin añadir un nodo envoltorio:
<Motion preset="scale" asChild>
<Card>…</Card>
</Motion>
Presets
| Preset | Efecto |
|---|---|
fade |
Opacidad de 0 a 1. |
fade-up |
Aparece mientras asciende desde abajo. |
fade-down |
Aparece mientras desciende desde arriba. |
fade-left |
Aparece mientras entra desde la derecha. |
fade-right |
Aparece mientras entra desde la izquierda. |
scale |
Aparece mientras amplía desde el 95 %. |
blur |
Aparece mientras elimina un desenfoque de 8 px. |
Temporización
La duración y el retraso son propiedades. No se genera ninguna clase arbitraria
de Tailwind durante el runtime, por lo que funciona cualquier valor. delay
permite escalonar una lista:
{items.map((item, i) => (
<Motion key={item.id} preset="fade-up" duration={500} delay={i * 80}>
<Item {...item} />
</Motion>
))}
El valor predeterminado es una entrada ease-out de 500 ms. Sustituye la curva
mediante className, como ease-in-out o ease-linear; se combina por encima
del valor predeterminado.
Hover
Pasa una receta hover para añadir una interacción sobre la animación de
entrada:
<Motion preset="fade" hover="lift">
<Card>…</Card>
</Motion>
| Hover | Efecto |
|---|---|
lift |
Asciende 4 px. |
sink |
Desciende 2 px al presionar. |
scale |
Crece hasta el 103 %. |
glow |
Aumenta la sombra. |
Animaciones de salida
<Motion> cubre el montaje. Cuando un elemento también deba animarse al
salir —un diálogo, un panel emergente o cualquier nodo con data-[state] de
Radix— usa los mapas de recetas y controla ambos estados mediante data-state:
import {
motionEnterPresets,
motionExitPresets,
} from "@/components/ui/motion"
// Aplicado a un nodo de contenido de Radix:
<DialogContent
className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out",
"data-[state=open]:fade-in data-[state=open]:zoom-in-95",
"data-[state=closed]:fade-out data-[state=closed]:zoom-out-95",
)}
/>
motionEnterPresets y motionExitPresets contienen las cadenas de clases
exactas de cada preset si prefieres consultarlas a escribir cada pareja. Las
utilidades motionEnter, motionExit y motionHover construyen la cadena
completa —animación, temporización y protección ante movimiento reducido—.
Movimiento reducido
Todas las recetas se degradan con prefers-reduced-motion: la animación de
entrada desaparece para mostrar el contenido al instante
(motion-reduce:animate-none) y se desactivan las transiciones de hover
(motion-reduce:transition-none). No requiere configuración adicional.
Variante para Framer Motion
Los presets anteriores usan solo CSS: no añaden coste durante el runtime y
cubren los casos de entrada y hover habituales. Para física de muelle,
transiciones de layout compartido o salidas en elementos que no sean de Radix,
añade la variante opcional de Framer Motion. Instala framer-motion y copia
este componente junto al motor CSS:
"use client"
import * as React from "react"
import { AnimatePresence, motion, type Transition } from "framer-motion"
const spring: Transition = { type: "spring", stiffness: 300, damping: 30 }
const variants = {
fade: { hidden: { opacity: 0 }, visible: { opacity: 1 } },
"fade-up": {
hidden: { opacity: 0, y: 12 },
visible: { opacity: 1, y: 0 },
},
scale: {
hidden: { opacity: 0, scale: 0.95 },
visible: { opacity: 1, scale: 1 },
},
} as const
export function MotionSpring({
preset = "fade-up",
show = true,
children,
}: {
preset?: keyof typeof variants
show?: boolean
children: React.ReactNode
}) {
return (
<AnimatePresence>
{show && (
<motion.div
initial="hidden"
animate="visible"
exit="hidden"
variants={variants[preset]}
transition={spring}
>
{children}
</motion.div>
)}
</AnimatePresence>
)
}
Los mismos nombres de preset y tokens aportan sensación de muelle y animaciones de salida reales cuando las necesitas, sin añadir nada al bundle cuando no.
Playground interactivo
Edita las propiedades del componente y revisa el JSX exacto antes de copiarlo. La vista previa se carga solo cuando esta sección entra en pantalla.
<Motion
preset="fade-up"
hover="lift"
duration={500}
delay={0}
>Animated content</Motion>Referencia de API
Generada a partir de las exportaciones públicas de TypeScript en src/ui/motion.tsx. Las descripciones técnicas conservan el texto canónico del código fuente.
MotionPreset
TipoMotion presets — enter/exit/hover recipes expressed as token-driven class strings on top of `tw-animate-css` (zero runtime deps). The `<Motion>` primitive plays an enter recipe once on mount; the exported maps and helpers let you drop the same recipes onto anything else (a Radix `data-[state]` element, a hand-rolled transition). Timing rides the CSS custom properties tw-animate-css reads: - default duration/easing come from the static `duration-*`/`ease-*` classes baked into `motionEnter`, - the `duration`/`delay` props override them per-instance via `--tw-animation-duration` / `--tw-animation-delay` (no arbitrary Tailwind class is generated at runtime, so it works with any value). Everything degrades under `prefers-reduced-motion`: the enter animation is dropped (content shows immediately) and hover transitions are disabled.
type MotionPreset = | "fade" | "fade-up" | "fade-down" | "fade-left" | "fade-right" | "scale" | "blur"MotionHover
Tipotype MotionHover = "lift" | "sink" | "scale" | "glow"motionEnterPresets
UtilidadEnter recipes: tw-animate-css `animate-in` sets the keyframe `from` state, the element animates to its natural rendered state.
motionExitPresets
UtilidadExit recipes: the mirror of each enter preset, for `animate-out` on a Radix `data-[state=closed]` element.
motionHoverPresets
UtilidadHover recipes: plain Tailwind transitions, no keyframes.
motionEnter
UtilidadBuild the class string for an enter recipe (plays on mount).
(preset: MotionPreset, className?: string): stringmotionExit
UtilidadBuild the class string for an exit recipe (pair with `animate-out`).
(preset: MotionPreset, className?: string): stringmotionHover
UtilidadBuild the class string for a hover recipe.
(hover: MotionHover, className?: string): stringMotionProps
Tipointerface MotionProps extends React.ComponentProps<"div"> { /** Enter recipe to play on mount. Default `"fade"`. */ preset?: MotionPreset /** Optional hover recipe applied to the same element. */ hover?: MotionHover /** Enter duration in milliseconds. Overrides the 500ms default. */ duration?: number /** Enter delay in milliseconds — the knob for staggering a list. */ delay?: number /** Merge props onto the single child instead of rendering a wrapper. */ asChild?: boolean }Motion
ComponenteProps: MotionProps| Propiedad | Tipo | Obligatoria | Valor predeterminado | Descripción |
|---|---|---|---|---|
preset | MotionPreset | No | "fade" | Enter recipe to play on mount. Default `"fade"`. |
hover | MotionHover | No | — | Optional hover recipe applied to the same element. |
duration | number | No | — | Enter duration in milliseconds. Overrides the 500ms default. |
delay | number | No | — | Enter delay in milliseconds — the knob for staggering a list. |
asChild | boolean | No | false | Merge props onto the single child instead of rendering a wrapper. |
Contrato de accesibilidad
Comportamiento legible por máquinas y responsabilidades de quien integra motion. Los detalles normativos conservan el texto canónico del registro.
- Soporte
- Contrato de composición
- Patrón
- animated semantic wrapper
- Motion for React
Semántica incorporada
- Preserves the semantics and focus behavior of the rendered child element.
Responsabilidades de integración
- Respect prefers-reduced-motion and avoid animating focus, reading order or critical status changes.
Limitaciones conocidas
- • Motion safety depends on the selected transition and consumer composition.