Skip to content

Animaciones

base.css incluye un sistema de animaciones completo. Todos los @keyframes están centralizados allí y los archivos de @headless-primitives/styles los reutilizan sin duplicarlos.

Variables de timing

Las duraciones y curvas están expuestas como CSS custom properties con fallback incorporado:

VariableValor por defectoUso
--hp-duration-fast100msTooltip
--hp-duration150msPopover, close animations
--hp-duration-slow200msDialog, backdrop, toast entrada
--hp-easeeaseAnimaciones de salida y fade
--hp-ease-outcubic-bezier(0.16, 1, 0.3, 1)Entradas (spring suave, estilo Radix)

Puedes sobrescribir cualquier variable en :root:

css
:root {
  --hp-duration-slow: 300ms; /* entradas más lentas */
  --hp-ease-out: cubic-bezier(0.34, 1.56, 0.64, 1); /* spring más pronunciado */
}

Animaciones por componente

Overlays: Dialog

La entrada usa scale(0.95 → 1) + fade, preservando el translate(-50%, -50%) del centrado estructural. La salida es la inversa.

css
/* Keyframes definidos en base.css */
@keyframes hp-dialog-in {
  from {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.95);
  }
  to {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
}

@keyframes hp-dialog-out {
  from {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
  }
  to {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.96);
  }
}

El backdrop hace fade independiente:

css
@keyframes hp-backdrop-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes hp-backdrop-out {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

Overlays: Drawer

El drawer usa transform para deslizarse desde un borde del viewport. La dirección del slide es automática según el atributo data-position que hp-drawer sincroniza en el content.

No usa @keyframes propios — la animación es una transición CSS sobre transform + opacity + visibility. Esto permite que el slide respete exactamente la curva --hp-ease-out sin necesidad de keyframes por cada posición:

css
/* Transición en base.css — transform incluido solo para el drawer */
[data-hp-drawer-content] {
  transition:
    opacity var(--hp-duration) ease,
    visibility 0s linear var(--hp-duration),
    transform var(--hp-duration) cubic-bezier(0.16, 1, 0.3, 1);
}

/* Estado inicial (fuera del viewport) por posición */
[data-hp-drawer-content] {
  transform: translateX(-100%);
} /* left  */
[data-hp-drawer-content][data-position="right"] {
  transform: translateX(100%);
} /* right */
[data-hp-drawer-content][data-position="top"] {
  transform: translateY(-100%);
} /* top   */
[data-hp-drawer-content][data-position="bottom"] {
  transform: translateY(100%);
} /* bottom*/

/* Estado abierto — sin transform */
[data-hp-drawer-content][data-state="open"] {
  transform: translate(0, 0);
}

El backdrop comparte los keyframes hp-backdrop-in / hp-backdrop-out con el dialog.

Para personalizar la velocidad del slide independientemente del resto de overlays:

css
:root {
  --hp-duration: 250ms; /* afecta todos los overlays */
}

/* O solo el drawer */
hp-drawer-content {
  transition-duration: 350ms;
}

Overlays: Popover

Slide vertical + scale leve. Salida es más rápida que la entrada (patrón estándar):

css
@keyframes hp-overlay-in {
  from {
    opacity: 0;
    transform: translateY(-6px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes hp-overlay-out {
  from {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  to {
    opacity: 0;
    transform: translateY(-4px) scale(0.97);
  }
}

Tooltip

Igual que el popover pero más corto y preserva el translateX(-50%) del centrado horizontal:

css
@keyframes hp-tooltip-in {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

Toast

El slide adapta su dirección según la posición del contenedor usando una custom property heredable:

css
/* El contenedor define la dirección de slide */
hp-toast-container {
  --hp-toast-slide: 110%;
} /* derecha (default) */
hp-toast-container[data-position*="left"] {
  --hp-toast-slide: -110%;
} /* izquierda */
hp-toast-container[data-position*="center"] {
  --hp-toast-slide: 0px;
} /* centrado → scale */

@keyframes hp-toast-in {
  from {
    opacity: 0;
    transform: translateX(var(--hp-toast-slide, 110%));
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

Los toasts centrados usan animación diferente (scale en lugar de slide):

css
@keyframes hp-toast-in-center {
  from {
    opacity: 0;
    transform: scale(0.92);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

Paneles (Accordion, Tabs, Collapsible)

Los paneles se muestran/ocultan con display: none. Para animar la entrada se usa @starting-style, que captura el estado inicial justo después de que display: none es removido:

css
[data-hp-panel][data-state="open"],
[data-hp-panel][data-state="selected"] {
  transition: opacity var(--hp-duration) var(--hp-ease);
}

@starting-style {
  [data-hp-panel][data-state="open"],
  [data-hp-panel][data-state="selected"] {
    opacity: 0;
  }
}

@starting-style requiere Chrome 117+, Safari 17.5+, Firefox 129+. En navegadores sin soporte los paneles simplemente aparecen sin animación.


Visibilidad con transición suave

Los overlays usan una técnica de visibility con delay para que la animación de salida pueda completarse antes de que el elemento desaparezca del árbol de accesibilidad:

css
/* Base: cuando cierra, opacity primero, visibility después */
[data-hp-overlay-content] {
  transition:
    opacity var(--hp-duration) var(--hp-ease),
    visibility 0s linear var(--hp-duration); /* delay = duración de la animación */
}

/* Al abrir: visibility es inmediata (sin delay) */
[data-hp-overlay-content][data-state="open"] {
  transition:
    opacity var(--hp-duration) var(--hp-ease),
    visibility 0s; /* sin delay */
}

Desactivar animaciones

Respetar prefers-reduced-motion

Añade esto a tu CSS global para usuarios que prefieren menos movimiento:

css
@media (prefers-reduced-motion: reduce) {
  :root {
    --hp-duration-fast: 0ms;
    --hp-duration: 0ms;
    --hp-duration-slow: 0ms;
  }
}

Al poner las duraciones a 0ms, las transiciones y animaciones son instantáneas sin eliminar el estado final correcto.

Desactivar completamente

css
:root {
  --hp-duration-fast: 0ms;
  --hp-duration: 0ms;
  --hp-duration-slow: 0ms;
}

Personalizar una animación específica

Dado que los @keyframes tienen nombres globales (hp-dialog-in, hp-overlay-in, etc.), puedes reemplazar la animación de un componente puntual sobreescribiendo solo la regla de aplicación:

css
/* Reemplazar la entrada del dialog con un flip vertical */
@keyframes my-dialog-in {
  from {
    opacity: 0;
    transform: translate(-50%, -50%) rotateX(-8deg) scale(0.96);
  }
  to {
    opacity: 1;
    transform: translate(-50%, -50%) rotateX(0deg) scale(1);
  }
}

[data-hp-overlay-content][role="dialog"][data-state="open"] {
  animation: my-dialog-in 250ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
css
/* Hacer que el toast entre desde abajo */
@keyframes my-toast-in {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

hp-toast {
  animation: my-toast-in var(--hp-duration-slow) var(--hp-ease-out) both;
}

Lanzado bajo la MIT License.