Las transiciones en CSS son lo que hace que un menú se abra suave en lugar de aparecer de golpe. En este tutorial desde cero aprendes a usar transition con un proyecto real: un sidebar que se oculta con un botón y un buscador hecho con popover.
Vas a entender las cuatro propiedades que forman una transición, cuándo usar cubic-bezier() y linear(), y cómo animar display: none, algo que hasta hace poco era imposible en CSS. Todo con el playground que hice para este video, donde puedes probar cada curva antes de copiarla.
En este tutorial
- Resultado: un sidebar que se oculta con transición y un buscador con
popoverque aparece y desaparece animado - Tecnologías: HTML y CSS (sin JavaScript)
- Aprendes:
transition-property,duration,timing-function,delay, el shorthand,transition-behaviory@starting-style - Nivel: principiante
- Ir directo al código fuente
Qué vas a construir
Dos ejemplos. El primero es Brote, el sidebar de una tienda de plantas: al pulsar «Ocultar», el menú baja y se recorta con un retraso calculado. El segundo es un buscador tipo paleta de comandos que se abre con un botón, entra desde abajo con un fundido y sale igual, todo con HTML y CSS.
Qué necesitas
- Un editor de código, por ejemplo VS Code con Live Server
- HTML y CSS básicos: selectores, propiedades y pseudoclases
- El playground de transiciones, para probar las curvas: transiciones.cursocss.com
- Un navegador actualizado (Chrome, Edge, Safari o Firefox recientes) para
transition-behaviory@starting-style
Paso a paso
¿Qué es una transición en CSS? Una transición (transition) es el cambio gradual de una propiedad entre un estado A y un estado B, en vez de instantáneo. Sin transición, el fondo del sidebar pasa de blanco a rojo en 0 segundos; con transición, el navegador calcula los pasos intermedios durante el tiempo que le digas. Solo necesitas un estado inicial con la transición declarada, un estado final y algo que dispare el cambio.
1. El disparador: qué hace que cambie el estado
Para que una transición ocurra necesitas un disparador: algo que provoque el paso del estado A al B. En JavaScript sería un evento, como un clic. En CSS es una pseudoclase como :hover, :focus o :checked, y con :has() ese cambio llega al elemento padre.
En el sidebar, el botón «Ocultar» es un checkbox. El selector pregunta: si .app tiene un .toggle__input marcado, cambia el sidebar. Ese es el estado final.
.app:has(.toggle__input:checked) .sidebar {
background-color: red;
}
2. transition-property y transition-duration
transition-property define qué propiedades se animan. Su valor por defecto es all, pero con eso solo no ves nada: falta transition-duration, que dice cuánto dura. Acepta segundos (1s) o milisegundos (1000ms).
.sidebar {
transition-property: all;
transition-duration: 1000ms;
}
Ojo con transition: all
Con all se anima todo lo que cambie, hasta un margin. Eso consume recursos del navegador. Además, animar margin o top, left, right y bottom recalcula el layout y afecta al rendimiento. Anima solo lo imprescindible, y si algo se mueve usa transform y opacity.
3. Mover elementos con transform: translate()
translate() mueve el elemento sin tocar el layout. El primer valor es el eje X (positivo a la derecha, negativo a la izquierda) y el segundo el eje Y (positivo hacia abajo, negativo hacia arriba). Con translateY(100%) lo bajas el 100 % de su propia altura.
Fíjate en que ahora transition-property nombra solo transform y opacity: es la forma recomendada.
.sidebar {
transform: translateY(0);
transition-property: transform, opacity;
transition-duration: 1000ms;
}
.app:has(.toggle__input:checked) .sidebar {
transform: translateY(100%);
}
4. transition-timing-function: cómo progresa la animación
Si la transición dura 2 segundos, en el segundo 2 siempre estará en su estado final. Lo que cambias con transition-timing-function es cómo progresa por el camino. Los valores por defecto son:
ease(por defecto): arranca rápido y frena suavelinear: velocidad constanteease-in: arranca lento y termina rápidoease-out: arranca rápido y frena al finalease-in-out: lento al inicio y al final
.sidebar {
transition-property: transform, opacity;
transition-duration: 1000ms;
transition-timing-function: ease-in;
}
5. cubic-bezier() y linear(): curvas a tu medida
cubic-bezier() dibuja la curva de la animación con cuatro números. En la gráfica, el eje horizontal es el tiempo y el vertical, el progreso. Esta curva empieza muy lenta y termina de golpe:
transition-timing-function: cubic-bezier(1.00, 0.03, 0.76, 0.11);
Si un valor de Y se sale del rango 0–1, la animación retrocede y rebota antes de llegar al final:
transition-timing-function: cubic-bezier(0.31, -0.97, 0.69, 2.00);
linear() es otra forma de controlar la velocidad: le das puntos de progreso, donde 0 es el inicio y 1 el final. Aquí va al final, regresa al 25 % y vuelve a terminar:
transition-timing-function: linear(
0,
1,
0.25,
1
);
Consejo
Nadie se aprende las curvas de Bézier de memoria. Usa un generador, como el de transiciones.cursocss.com, copia la función y pégala. En la mayoría de los proyectos, ease es suficiente.
6. transition-delay: cuánto espera antes de empezar
transition-delay retrasa el inicio. Lo potente es darle un retraso distinto a cada propiedad, en el mismo orden que transition-property. Aquí el sidebar primero baja con transform y, un segundo después, se recorta con clip-path (el polígono sale del generador Clippy):
.sidebar {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0% 100%);
transform: translateY(0);
transition-property: transform, clip-path;
transition-duration: 1000ms;
transition-timing-function: ease;
transition-delay: 0s, 1s;
}
.app:has(.toggle__input:checked) .sidebar {
transform: translateY(100%);
clip-path: polygon(0 0, 100% 0, 100% 0, 0 0);
}
Error típico: 0 sin unidad
En la lista de transition-delay escribe 0s, no 0. Los tiempos en CSS necesitan unidad: con 0 la declaración no es válida y el retraso no se aplica.
7. El shorthand transition: todo en una línea
Todo lo anterior cabe en transition: propiedad, duración, función y retraso, separados por espacios, y cada propiedad separada por comas. Lo que coincide con el valor por defecto (ease, retraso 0s) no hace falta escribirlo.
transition: transform 1s, clip-path 1s 1s;
En cada propiedad, el primer tiempo es la duración y el segundo, el retraso: clip-path 1s 1s dura un segundo y espera otro antes de empezar. Esta línea hace exactamente lo mismo que las cuatro propiedades del paso anterior.
En el repositorio, la versión final va un paso más allá: cada estado tiene su propio transition. Al ocultarse, el sidebar primero baja y luego se recorta; al volver, primero se descubre y luego sube. La transición que se aplica es siempre la del estado al que vas.
.sidebar {
transition: clip-path 1s 0s, transform 1s 1s;
}
.app:has(.toggle__input:checked) .sidebar {
transform: translateY(300px);
clip-path: polygon(0 0, 100% 0, 100% 0, 0 0);
transition: transform 1s 0s, clip-path 1s 1s;
}
Patrocinador oficial del curso
Publica tus proyectos con Hostinger
El playground de transiciones de este video lo hice con el AI Builder de Hostinger. Con el cupón ALEXCG tienes un 10 % de descuento extra en tu hosting.
8. Ejemplo avanzado: un buscador con popover
El atributo popover esconde el elemento y lo prepara para mostrarse después. El botón con popovertarget apunta al id del elemento que abre. Sin una línea de JavaScript:
<!-- Botón de buscar -->
<button class="search-btn" popovertarget="palette">
Buscar
</button>
<!-- Buscador -->
<section class="palette" id="palette" popover>
<search class="palette__search">
<input type="search" autofocus placeholder="Escribe un comando o busca…">
<kbd>esc</kbd>
</search>
</section>
¿El problema? Por detrás, HTML pasa de display: none a display: block (con la pseudoclase :popover-open). Y display es una propiedad discreta: si le pones una transición, no pasa nada.
9. Animar display con transition-behavior: allow-discrete
transition-behavior: allow-discrete permite incluir display en la transición. En realidad no lo anima: retrasa el cambio a none lo que dure la transición, y eso da tiempo a que transform y opacity se animen al cerrar.
.palette {
display: none;
transform: translateY(100%);
opacity: 0;
transition-property: display, transform, opacity;
transition-duration: .5s;
transition-behavior: allow-discrete;
}
.palette:popover-open {
display: block;
transform: translateY(0);
opacity: 1;
}
Si quitas transition-behavior, todo vuelve a ser de golpe. Con él, al cerrar, el buscador baja y se desvanece. En Chrome y Edge puedes añadir también overlay a transition-property para que el popover no salga de la capa superior antes de tiempo. Lo explico más a fondo en 3 características de CSS que no conocías.
10. @starting-style en CSS: la animación de entrada
Falta la entrada: de display: none a block no hay un estado anterior del que partir. @starting-style define los estilos que tiene el elemento justo antes de mostrarse, y desde ahí se anima.
.palette:popover-open {
display: block;
transform: translateY(0);
opacity: 1;
@starting-style {
opacity: 0;
transform: translateY(100%);
}
}
Con esto el buscador entra desde abajo con un fundido y sale igual.
Resumen: transition = propiedad + duración + curva + retraso. Para animar display, allow-discrete. Para la entrada, @starting-style.
Código fuente
El repositorio incluye los dos proyectos del video: el sidebar Brote con todas las transiciones y el buscador con popover, transition-behavior y @starting-style.
Preguntas frecuentes
¿Qué es transition en CSS y para qué sirve?
transition hace que el cambio de una propiedad CSS ocurra de forma gradual en lugar de instantánea. Sirve para que menús, botones, modales o tarjetas pasen de un estado a otro con suavidad. Se compone de cuatro propiedades principales: transition-property, transition-duration, transition-timing-function y transition-delay, que puedes escribir juntas con el shorthand transition.
¿Por qué no funciona mi transition en CSS?
Las causas más comunes son tres: falta transition-duration (por defecto es 0), la propiedad que cambias no se puede animar (como display sin allow-discrete), o el elemento no cambia de estado porque el disparador no coincide (revisa el selector). Revisa también que los tiempos lleven unidad: 0s, no 0.
¿Cómo se anima display: none en CSS?
Añade display a transition-property y usa transition-behavior: allow-discrete: así el cambio a none espera a que termine la transición. Para la entrada, define el estado de partida dentro de @starting-style. Funciona en las versiones recientes de Chrome, Edge, Safari y Firefox.
¿Qué diferencia hay entre transition y animation en CSS?
transition anima el paso entre dos estados y necesita un disparador, como :hover, :checked o una clase. animation usa @keyframes, puede tener muchos pasos, repetirse y arrancar sola al cargar la página. Para abrir y cerrar elementos, transition suele bastar.
Más proyectos
- 3 características de CSS: @starting-style, transition-behavior y más
- Animaciones avanzadas en CSS con @property
- Selectores avanzados de CSS: selectores hermanos
- Menú dropdown con CSS
- Cómo centrar en CSS (7 maneras)
Curso en vivo
Domina CSS desde cero
Muy pronto empiezo un curso de CSS en vivo, de cero a profundidad. Regístrate y te aviso de todas las novedades.



