Módulo 06: Bordes, sombras y efectos
Objetivos del módulo
- Dibujar bordes con
border-*y entender por qué basta una clase gracias a Preflight - Redondear esquinas con
rounded-*y recortar el contenido sin sorpresas - Distinguir
border,ring,outlineyshadow, y elegir el adecuado en cada caso - Crear indicadores de foco que funcionen con teclado y en colores forzados
- Separar elementos con
divide-*y conocer sus trampas en v4 - Construir sombras por capas con
shadow-*,inset-shadow-*einset-ring-* - Controlar la transparencia con
opacity-*y mezclar capas conmix-blend-* - Aplicar filtros con
blur-*,grayscale,drop-shadow-*y sus versionesbackdrop-* - Prever los efectos secundarios de estas clases: contextos de apilamiento y
position: fixed - Recortar elementos con máscaras
mask-*y escalarlos conzoom-* - Conocer las escalas renombradas de v3 a v4, incluidos los falsos amigos que no dan error
1. Preparación del laboratorio
Todo el módulo se apoya en el proyecto Vite del módulo 01. Los ejemplos usan cuatro imágenes propias. Descárgalas y guárdalas en public/img/ con estos nombres, para que Vite las sirva en /img/...:
| Descarga | Guárdala como | Para qué sirve |
|---|---|---|
| 06-img-escena.svg | public/img/escena.svg | Imagen de fondo con muchos colores, ideal para filtros |
| 06-img-portatil.svg | public/img/portatil.svg | Imagen con transparencia y forma irregular |
| 06-img-torre.svg | public/img/torre.svg | Producto de la tienda |
| 06-img-teclado.svg | public/img/teclado.svg | Producto de la tienda |
Para que cada figura sea legible, los ejemplos usan algunas clases que aún no has estudiado. Con esta tabla te basta por ahora:
| Clases | Qué hacen | Dónde se estudian |
|---|---|---|
flex, grid, grid-cols-3, items-center, justify-between, gap-4 | Colocan los elementos | Flexbox y Grid |
relative, absolute, inset-0, z-10, overflow-hidden | Posicionan y recortan | Layout y posicionamiento |
hover:, focus-visible:, disabled: | Aplican una clase solo en ese estado | Variantes y estados |
dark: | Aplica una clase en modo oscuro | Modo oscuro |
transition-shadow, duration-300 | Animan el cambio | Transiciones y animaciones |
⚠️ Importante: Este es el módulo con más falsos amigos de todo el curso: clases que existen en v3 y en v4 con distinto resultado y que no dan ningún error. Cada sección los señala con su marca, y la sección 19 los reúne en una tabla.
Si quieres repasar antes las propiedades CSS de base, consulta el modelo de cajas del Curso de HTML y CSS.
2. Bordes: border-*
Por qué basta con una clase
En CSS puro, border-width: 1px no dibuja nada, porque el navegador parte de border-style: none. Tailwind lo resuelve desde Preflight, la hoja de reinicio que viste en el módulo 02. Esta regla se aplica a todos los elementos:
/* Preflight de Tailwind 4.3.3 */
*,
::after,
::before,
::backdrop,
::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
Todo elemento nace con borde de ancho 0 y estilo solid. La clase border solo necesita dar el ancho, y el borde aparece:
/* Lo que genera la clase border en 4.3.3 */
.border {
border-style: var(--tw-border-style);
border-width: 1px;
}
La variable --tw-border-style vale solid por defecto. Existe para que border-dashed siga funcionando aunque otra clase vuelva a escribir border-style.

Figura 1. Anchos, lados, estilos y colores. border-3 es un valor dinámico de v4 (ver la sección 20).
Ancho y lados
| Clase | CSS | Efecto |
|---|---|---|
border | border-width: 1px | Los cuatro lados |
border-0, border-2, border-4, border-8 | border-width: 0, 2px, 4px, 8px | Otros anchos |
border-t, border-r, border-b, border-l | border-top-width: 1px… | Un lado físico |
border-x, border-y | Los dos laterales, o arriba y abajo | Dos lados opuestos |
border-s, border-e | border-inline-start-width, border-inline-end-width | Inicio y fin de línea |
border-t-4, border-s-2… | El lado con ese ancho | Todas las combinaciones |
Los lados s (start) y e (end) son lógicos: dependen de la dirección del texto. En español, border-s es el lado izquierdo; en árabe o hebreo (dir="rtl"), el derecho. Si tu aplicación puede traducirse a esos idiomas, usa s y e en lugar de l y r. Las propiedades lógicas se presentaron en el módulo 03.
Estilo
| Clase | CSS | Observación |
|---|---|---|
border-solid | border-style: solid | Por defecto, gracias a Preflight |
border-dashed | border-style: dashed | Discontinuo: zonas para soltar ficheros, estados vacíos |
border-dotted | border-style: dotted | Punteado |
border-double | border-style: double | Doble línea: necesita al menos 3 px para distinguirse |
border-none | border-style: none | Sin borde, aunque tenga ancho |
border-hidden | border-style: hidden | Igual que none, salvo en tablas colapsadas, donde gana a los bordes vecinos |
border-0 y border-none parecen iguales, pero actúan sobre propiedades distintas. Usa border-0 para quitar un borde que da otra clase, y border-none solo cuando necesites cambiar el estilo.
Color
Los colores usan la paleta del módulo 05 y admiten modificador de opacidad:
<div class="border-2 border-rose-500/30">Con el 30 % de opacidad</div>
<div class="border-4 border-t-rose-500 border-r-amber-400 border-b-teal-500 border-l-indigo-500">Un color por lado</div>
<div class="border-2 border-current">El color del texto</div>
<div class="border-2 border-transparent">Invisible, pero ocupa su sitio</div>
Color por defecto 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4), un borde sin color es gris (
gray-200); en v4 usacurrentColor, el color del texto. Si usas la LTS: no tienes que hacer nada si quieres ese gris, y si quieres el color del texto añadeborder-current.
Con la misma clase border y text-indigo-700, esto es lo que devuelve el navegador:
Tailwind 3.4.19 border-top-color: rgb(229, 231, 235) (gris 200)
Tailwind 4.3.3 border-top-color: oklch(0.457 0.24 277.023) (el del texto, indigo-700)
El motivo está en Preflight: el de v3 fija border-color con el gris del tema, y el de v4 escribe solo border: 0 solid, que deja el color por defecto del navegador. El cambio afecta también a divide-*.
Si migras un proyecto grande y no quieres revisar cada borde, esta regla recupera el gris de v3. La hemos probado: los bordes sin color vuelven a gray-200.
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentcolor);
}
}
💡 Consejo: En código nuevo para v4, escribe siempre el color:
border border-slate-200. Queda explícito, y el mismo HTML se ve igual en las dos versiones.
Los lados de bloque: border-bs-* y border-be-* 🆕 v4.2
🆕 Desde Tailwind 4.2. No existe en la LTS (Tailwind 3.4). Si usas la LTS: usa
border-t-*yborder-b-*, que coinciden salvo en escritura vertical, o escribe la propiedad con[border-block-start-width:2px].
border-s y border-e son lógicos en el eje de la línea. border-bs y border-be lo son en el eje de bloque: en español, arriba y abajo. Escriben border-block-start-width y border-block-end-width, y aceptan color (border-bs-red-500). Solo notarás la diferencia con border-t en textos verticales.
El borde ocupa sitio
Preflight pone box-sizing: border-box, de modo que el ancho que das a una caja incluye su borde. El borde no agranda la caja, le quita espacio al contenido:
<div class="w-40 border-8 border-slate-300 p-2">caja</div>
Medido en Chromium:
ancho de la caja: 160 px (w-40)
ancho interior: 144 px (160 - 8 - 8; incluye el padding)
Pero la altura sí crece si no la fijas, y esto causa un defecto habitual: al añadir un borde en un estado concreto, todo se mueve. La solución es reservar el sitio con un borde transparente:
<!-- Mal: al pasar el ratón, el borde aparece y la tarjeta crece 2 px por cada lado -->
<a class="rounded-lg p-4 hover:border-2 hover:border-indigo-500">Portátiles</a>
<!-- Bien: el borde existe siempre; solo cambia de color -->
<a class="rounded-lg border-2 border-transparent p-4 hover:border-indigo-500">Portátiles</a>
Este borde transparente tiene una ventaja extra: con colores forzados (Windows en alto contraste) el navegador sustituye transparent por un color visible. Lo medimos: border-transparent pasa de rgba(0, 0, 0, 0) a rgb(0, 0, 0). Tu tarjeta conserva así un contorno aunque el modo elimine los fondos.
Borde semitransparente sobre fondo de color
El fondo de una caja se pinta por defecto también debajo del borde, y un borde semitransparente lo deja ver. Con bg-clip-padding, el fondo acaba donde empieza el borde (background-clip pasa de border-box a padding-box), y el borde se mezcla con lo que hay detrás de la caja:
<div class="w-40 border-4 border-black/50 bg-amber-300 bg-clip-padding p-2">Borde sobre lo que hay detrás</div>
Opacidad de borde: border-opacity-* eliminada 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) existen
border-opacity-*,divide-opacity-*yring-opacity-*; en v4 están eliminadas. Si usas la LTS: siguen funcionando, pero escribe yaborder-black/50,divide-black/50yring-black/50, que van en las dos versiones.
3. Esquinas: rounded-*
rounded-* escribe la propiedad border-radius. Admite la caja entera, un lado o una esquina, y funciona aunque el elemento no tenga borde.

Figura 2. Escala de radios de v4, lados, esquinas, propiedades lógicas y el efecto de overflow-hidden.
La escala de radios 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
rounded-smvale 2 px, y en v4 vale 4 px: el mismo nombre da otro resultado. Si usas la LTS: usarounded-smpara 2 px yroundedpara 4 px. No tienesrounded-xsnirounded-4xl; este último se escriberounded-[2rem].
| Clase en v4 | Variable del tema | Valor | Clase en v3 |
|---|---|---|---|
rounded-none | 0 | rounded-none | |
rounded-xs | --radius-xs | 0.125rem (2 px) | rounded-sm |
rounded-sm | --radius-sm | 0.25rem (4 px) | rounded |
rounded-md | --radius-md | 0.375rem (6 px) | rounded-md |
rounded-lg | --radius-lg | 0.5rem (8 px) | rounded-lg |
rounded-xl | --radius-xl | 0.75rem (12 px) | rounded-xl |
rounded-2xl | --radius-2xl | 1rem (16 px) | rounded-2xl |
rounded-3xl | --radius-3xl | 1.5rem (24 px) | rounded-3xl |
rounded-4xl | --radius-4xl | 2rem (32 px) | rounded-[2rem] |
rounded-full | calc(infinity * 1px) | rounded-full (9999px) |
La clase rounded sin sufijo sigue funcionando en v4 y vale lo mismo que en v3, 4 px. Pero en el tema de v4 figura en un bloque marcado como Deprecated (obsoleto), así que usa rounded-sm.
rounded-full da una píldora si la caja es alargada y un círculo si es cuadrada: size-20 rounded-full.
Lados y esquinas
| Clase | Qué redondea |
|---|---|
rounded-t-lg, rounded-r-lg, rounded-b-lg, rounded-l-lg | Las dos esquinas de ese lado |
rounded-tl-lg, rounded-tr-lg, rounded-br-lg, rounded-bl-lg | Una esquina física |
rounded-s-lg, rounded-e-lg | Lado de inicio o de fin de línea (lógico) |
rounded-ss-lg, rounded-se-lg, rounded-ee-lg, rounded-es-lg | Una esquina lógica |
Fíjate en la tercera fila de la Figura 2: con dir="rtl", el mismo rounded-s-2xl redondea el otro lado. Las esquinas lógicas se leen como inicio de bloque + inicio de línea: ss es arriba a la izquierda en español, y ee, abajo a la derecha.
Radios y contenido: overflow-hidden
border-radius redondea la caja, pero no recorta a sus hijos. Una imagen dentro de una tarjeta redondeada sigue siendo rectangular y asoma por las esquinas. Hay dos soluciones:
<!-- 1. El padre recorta a todos sus hijos: suele ser lo mejor -->
<article class="overflow-hidden rounded-2xl border border-slate-300">
<img src="/img/escena.svg" alt="" class="h-24 w-full object-cover">
<p class="p-2">El padre recorta a sus hijos</p>
</article>
<!-- 2. Redondear al hijo con el mismo radio: útil si no puedes usar overflow-hidden -->
<article class="rounded-2xl border border-slate-300">
<img src="/img/escena.svg" alt="" class="h-24 w-full rounded-t-2xl object-cover">
<p class="p-2">El hijo se redondea por su cuenta</p>
</article>
⚠️ Importante:
overflow-hiddentambién recorta todo lo que se dibuje fuera del padre: menús desplegables, tooltips, y las sombras y anillos de los hijos. Lo veremos en la sección 6. Si tu tarjeta contiene un menú desplegable, usa la opción 2.
Tablas: rounded-* no funciona con border-collapse
Preflight pone border-collapse: collapse en las tablas, y con bordes colapsados el navegador ignora border-radius. Es lo que muestra la columna derecha de la Figura 2. La solución es meter la tabla en un contenedor con overflow-hidden rounded-2xl border. Las tablas se tratan a fondo en el módulo 17.
4. Anillos: ring-*
Un anillo es una línea alrededor de la caja que no ocupa sitio. No es un borde de verdad: Tailwind lo dibuja con una sombra sólida, sin difuminado.
📘 Concepto:
box-shadow: 0 0 0 3px azules una sombra sin desplazamiento (0 0), sin difuminado (el tercer0) y con 3 px de extensión. La extensión agranda la sombra por igual en todas direcciones, y el resultado parece un borde exterior.

Figura 3. Arriba, la misma marca de 4 px hecha de tres formas: solo el borde mueve a sus vecinos. Abajo, sobre fondo oscuro, el hueco de ring-offset es en realidad un color.
Los anillos se adaptan al border-radius y se pueden combinar con una sombra normal gracias a las capas que verás en la sección 10.
Ancho y color por defecto 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
ringmide 3 px y es azul (blue-500al 50 %); en v4 mide 1 px y usacurrentColor, igual queborder. Si usas la LTS: escribe siemprering-2oring-4y un color explícito (ring-indigo-500), que da el mismo resultado en las dos versiones. La clasering-3no existe; usaring-[3px].
Es uno de los falsos amigos peligrosos, porque ring existe en las dos versiones:
ring en 3.4.19: rgba(59, 130, 246, 0.5) 0px 0px 0px 3px
ring en 4.3.3: oklch(0.457 0.24 277.023) 0px 0px 0px 1px (con text-indigo-700)
El resto de la escala coincide en las dos versiones: ring-0, ring-1, ring-2, ring-4 y ring-8. ring-3 solo existe en v4.
Si migras y no quieres tocar el código, estas variables de tema recuperan el comportamiento de v3. Las hemos probado y funcionan, aunque la guía oficial avisa de que existen solo por compatibilidad:
@theme {
--default-ring-width: 3px;
--default-ring-color: var(--color-blue-500);
}
Color y opacidad
Funcionan como en los bordes: ring-indigo-500, ring-indigo-500/40 y ring-current.
Anillo interior: ring-inset
ring-inset dibuja el anillo hacia dentro de la caja, sobre su fondo. Sirve cuando el elemento está pegado a un borde y un anillo exterior quedaría cortado, por ejemplo en una celda de tabla enfocada.
<div class="rounded-lg bg-slate-100 p-3 ring-4 ring-inset ring-indigo-500">Anillo interior</div>
Separación: ring-offset-*
ring-offset-2 separa el anillo de la caja, pero no crea un hueco real: pinta otra sombra sólida, blanca por defecto, entre la caja y el anillo. Sobre un fondo blanco no se nota. Sobre uno oscuro aparece una franja blanca, como en la Figura 3.
<!-- Mal sobre fondo oscuro: el hueco se ve blanco -->
<button class="ring-2 ring-indigo-300 ring-offset-2">Comprar</button>
<!-- Bien, pero hay que repetir el color del fondo -->
<button class="ring-2 ring-indigo-300 ring-offset-2 ring-offset-slate-900">Comprar</button>
💡 Consejo: Si quieres separación, usa
outline-offset-*, que deja un hueco de verdad: se ve lo que haya detrás, sea del color que sea. Es la tercera tarjeta oscura de la Figura 3, y no te obliga a repetir el color del fondo.
5. Contornos: outline-*
outline es la propiedad nativa de CSS para contornos. Como el anillo, no ocupa sitio. Se distingue de él en dos cosas: admite un hueco real (outline-offset) y sobrevive a los colores forzados, como veremos en la sección 7.
outline y outline-2 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
outline-2solo da el ancho y no se ve sin la claseoutline, que además mide 3 px por sí sola; en v4,outlinemide 1 px youtline-2funciona sola. Si usas la LTS: escribe siempre las dos clases,outline outline-2. La claseoutline-solidno existe, porqueoutlineya es sólido.
Medido con las mismas clases en Chromium:
| Clases | v3.4.19 | v4.3.3 |
|---|---|---|
outline | solid 3px | solid 1px |
outline-2 | none 2px (invisible) | solid 2px |
outline outline-2 | solid 2px | solid 2px |
La tabla explica un fallo frecuente al seguir tutoriales: copias focus:outline-2 de un ejemplo de v4 en un proyecto v3 y no aparece nada.
Estilo, color y separación
| Clase | CSS | Observación |
|---|---|---|
outline-dashed, outline-dotted, outline-double | outline-style | Cambian el estilo |
outline-indigo-600, outline-indigo-600/50 | outline-color | Sin color, usa currentColor |
outline-offset-2 | outline-offset: 2px | Hueco real entre caja y contorno |
-outline-offset-2 | outline-offset: -2px | Contorno hacia dentro de la caja |
outline-none y outline-hidden 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
outline-nonecrea un contorno transparente que reaparece en colores forzados; en v4 esa misma clase elimina el contorno del todo, y lo de antes se llamaoutline-hidden. Si usas la LTS: sigue usandooutline-none; es la opción accesible youtline-hiddenno existe.
/* outline-none en 3.4.19 */
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
/* outline-none en 4.3.3: solo outline-style: none, sin contorno de ningún tipo */
/* outline-hidden en 4.3.3: equivale al outline-none de v3 */
.outline-hidden {
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
}
Por tanto, al copiar código de v3 a v4 una clase idéntica pierde silenciosamente un mecanismo de accesibilidad. La regla es sencilla: en v4, outline-hidden para quitar el contorno del navegador. Deja outline-none para casos en los que de verdad no quieras contorno en ningún modo.
6. Borde, anillo, contorno o sombra: cómo elegir
Ya tienes cuatro formas de dibujar una línea alrededor de una caja. Parecen intercambiables, pero no lo son.
Figura 4. Dónde se pinta cada línea y cómo se comporta. Todas las filas de la tabla se han medido en Chromium.
Hemos medido la Figura 3 con getBoundingClientRect() para confirmar la fila del espacio. El elemento marcado es “Sobremesa”, y su vecino, “Periféricos”:
Sobremesa (ancho x alto) Periféricos (posición x)
sin marca 91,4 x 32 333,4
border-4 99,4 x 40 341,4 <- crece 8 px y empuja 8 px
ring-4 91,4 x 32 333,4
outline-4 91,4 x 32 333,4
| Necesitas… | Usa |
|---|---|
| Una línea que forme parte del diseño y siempre esté ahí | border |
| Solo un lado, o colores distintos por lado | border-t, border-s… |
| Resaltar un elemento sin mover nada | ring u outline |
| Un perfilado sutil, junto con una sombra | ring-1 ring-black/5 |
| El indicador de foco | outline (ver la sección siguiente) |
| Sensación de altura | shadow |
Lo que recorta overflow-hidden
Si un padre tiene overflow-hidden y el hijo llega hasta su borde, todo lo que el hijo pinte fuera de su caja desaparece: anillos, contornos y sombras. El borde se salva porque está dentro de la caja.
<div class="overflow-hidden">
<div class="ring-4 ring-rose-500">Este anillo no se ve</div>
</div>
<!-- Soluciones: dejar margen en el padre (overflow-hidden p-1), o dibujar hacia dentro -->
<div class="overflow-hidden">
<div class="ring-4 ring-inset ring-rose-500">Con ring-inset no sale de la caja</div>
</div>
7. Foco visible y accesibilidad 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
outline-2necesita la claseoutlineyoutline-noneya es accesible. Si usas la LTS: escribefocus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2para el foco, y usaoutline-nonedonde esta sección diceoutline-hidden.
Quien navega con teclado necesita ver en qué elemento está. Es la función del indicador de foco, y es un requisito de accesibilidad (WCAG). Preflight no lo quita: si no haces nada, el navegador dibuja el suyo (outline: auto, medido en las dos versiones).
Cuando quieras uno propio, hay tres formas habituales. Las hemos probado de verdad: pulsando el tabulador con Playwright, una vez en modo normal y otra emulando colores forzados, el modo que usa Windows en alto contraste. En ese modo el navegador impone su paleta y elimina todas las sombras, y los anillos son sombras.

Figura 5. En modo normal las tres opciones se ven iguales. Con colores forzados, la primera se queda sin indicador de foco.
Foco medido en colores forzados (Chromium):
outline-none + ring box-shadow: none outline: none <- no se ve el foco
outline-hidden + ring box-shadow: none outline: solid 2px <- se ve
outline-3 en foco box-shadow: none outline: solid 3px <- se ve
La primera fila es un falso amigo: con las mismas clases, en v3 sí quedaba un contorno visible en colores forzados, porque su outline-none no eliminaba nada. Lo medimos también:
outline-none + ring en colores forzados:
v3.4.19 outline: solid 2px <- se ve
v4.3.3 outline: none <- no se ve
Este es el código de los tres botones:
<!-- 1. MAL en v4: en colores forzados no queda ningún indicador -->
<button class="outline-none focus-visible:ring-3 focus-visible:ring-indigo-400 focus-visible:ring-offset-2">
Añadir al carrito
</button>
<!-- 2. Aceptable: el anillo se ve en modo normal y outline-hidden entra en colores forzados -->
<button class="outline-hidden focus-visible:ring-3 focus-visible:ring-indigo-400 focus-visible:ring-offset-2">
Añadir al carrito
</button>
<!-- 3. Recomendado: un contorno de verdad -->
<button class="focus-visible:outline-3 focus-visible:outline-offset-2 focus-visible:outline-indigo-400">
Añadir al carrito
</button>
La opción 3 es la más sencilla: no hay que quitar nada, no hay que repetir colores de fondo y funciona en cualquier modo.
💡 Consejo: Usa
focus-visible:y nofocus:. El navegador solo muestrafocus-visiblecuando el foco tiene sentido visual, como al usar el teclado, y no al hacer clic con el ratón en un botón. Tienes los detalles en el módulo 11.
Contraste del indicador
Comprueba que el contorno se distingue de lo que tiene alrededor. La pauta WCAG 2.2 pide un contraste mínimo de 3:1 para los componentes de la interfaz. Un outline-white en un botón situado sobre fondo blanco no sirve, aunque el código sea correcto. El hueco de outline-offset-2 ayuda mucho, porque separa el contorno del color del propio botón.
8. Separadores: divide-*
Cuando tienes una lista y quieres una línea entre sus elementos, poner border-b a cada uno deja una línea sobrante en el último. divide-y, aplicado al padre, dibuja solo las líneas intermedias.

Figura 6. divide-y, divide-x, divide-y en una tabla y, abajo a la derecha, el defecto del último hijo oculto en v4.
<ul class="divide-y divide-slate-200 rounded-xl border border-slate-200">
<li class="flex justify-between p-3"><span>Portátil UltraBook 14</span><span>899,00 €</span></li>
<li class="flex justify-between p-3"><span>Ratón inalámbrico</span><span>19,90 €</span></li>
<li class="flex justify-between p-3"><span>Teclado mecánico</span><span>79,90 €</span></li>
</ul>
En horizontal, con divide-x:
<dl class="flex divide-x divide-slate-200 rounded-xl border border-slate-200 text-center">
<div class="flex-1 p-3"><dt>Pedidos</dt><dd class="text-2xl font-bold">128</dd></div>
<div class="flex-1 p-3"><dt>Clientes</dt><dd class="text-2xl font-bold">47</dd></div>
<div class="flex-1 p-3"><dt>Productos</dt><dd class="text-2xl font-bold">36</dd></div>
</dl>
En una tabla, se aplica a <tbody>, cuyos hijos son las filas: <tbody class="divide-y divide-slate-200">.
| Clase | Qué hace |
|---|---|
divide-y, divide-x | Línea de 1 px entre hijos |
divide-y-2, divide-x-4… | Otros anchos |
divide-slate-200, divide-slate-200/50 | Color y opacidad |
divide-dashed, divide-dotted, divide-double, divide-solid, divide-none | Estilo |
divide-y-reverse, divide-x-reverse | Para hijos en orden inverso |
Hijos en orden inverso
Si los hijos se muestran al revés con flex-col-reverse, las líneas caen en el lado equivocado. divide-y-reverse lo corrige:
<ul class="flex flex-col-reverse divide-y divide-y-reverse divide-rose-500">
<li>Primero en el HTML, último en pantalla</li>
<li>Segundo</li>
<li>Tercero</li>
</ul>
Un selector distinto en cada versión 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4), el divisor es un borde superior en cada hijo menos el primero, con más prioridad que las clases del hijo; en v4 es un borde inferior en cada hijo menos el último, y las clases del hijo ganan. Si usas la LTS: no te afecta el defecto del último hijo oculto; para anular un divisor en un hijo concreto necesitas
!border-t-0.
/* divide-y en 3.4.19 */
.divide-y > :not([hidden]) ~ :not([hidden]) { border-top-width: 1px; }
/* divide-y en 4.3.3 (simplificado) */
:where(.divide-y > :not(:last-child)) { border-bottom-width: 1px; }
El cambio responde a un problema de rendimiento en páginas grandes, según la guía oficial. Tiene tres consecuencias prácticas, que hemos medido con el mismo HTML en las dos versiones.
1. El último hijo oculto deja una línea colgando. En v4, :last-child selecciona al último hijo del HTML, esté visible o no. Si ocultas el último con el atributo hidden o con la clase hidden, el anterior conserva su línea inferior:
<ul class="divide-y divide-rose-400">
<li>Primero</li>
<li>Segundo</li>
<li hidden>Tercero</li>
</ul>
Segundo <li>: v3.4.19 border-top: 1px border-bottom: 0 correcto
v4.3.3 border-top: 0 border-bottom: 1px línea sobrante bajo el último visible
Tienes dos soluciones. La primera, y más limpia, es no pintar el elemento: si usas un framework, no lo incluyas en el HTML. La segunda, cuando lo ocultas con JavaScript, es copiar con una variante arbitraria el selector de v3, que funciona en las dos versiones:
<ul class="[&>:not([hidden])~:not([hidden])]:border-t [&>:not([hidden])~:not([hidden])]:border-rose-400">
<li>Primero</li>
<li>Segundo</li>
<li hidden>Tercero</li>
</ul>
2. Las clases del hijo ahora ganan. :where() tiene especificidad cero, de modo que en v4 una clase en el hijo se impone al divisor del padre. Esto es una mejora:
<ul class="divide-y-4 divide-red-500">
<li class="border-b-0">Sin línea debajo en v4</li>
<li class="border-blue-500">Línea azul en v4</li>
<li>Último</li>
</ul>
3. Si hay márgenes en los hijos, la línea cambia de sitio. Antes iba encima de cada hijo, y ahora va debajo. Una tarjeta con mt-4 puede quedar con la línea a otra distancia. Si tu diseño dependía de ello, usa flex flex-col gap-4 y bordes explícitos.
9. Sombras: shadow-*
Una sombra sugiere que un elemento flota sobre el fondo. En una interfaz, más sombra significa más altura: las tarjetas llevan poca, y los menús y los modales, mucha.

Figura 7. Escala de sombras de v4, color y opacidad, y capas interiores.
La escala de sombras 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
shadow-smes la sombra más pequeña (0 1px 2px); en v4 esa sombra se llamashadow-xs, yshadow-smda la que en v3 erashadow. Si usas la LTS: usashadow-smyshadowen lugar deshadow-xsyshadow-sm. Parashadow-2xs, escribeshadow-[0_1px_rgb(0_0_0/0.05)].
| Clase en v4 | Valor | Clase en v3 |
|---|---|---|
shadow-2xs | 0 1px rgb(0 0 0 / 0.05) | shadow-[0_1px_rgb(0_0_0/0.05)] |
shadow-xs | 0 1px 2px 0 rgb(0 0 0 / 0.05) | shadow-sm |
shadow-sm | 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1) | shadow |
shadow-md | 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1) | shadow-md |
shadow-lg | 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1) | shadow-lg |
shadow-xl | 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1) | shadow-xl |
shadow-2xl | 0 25px 50px -12px rgb(0 0 0 / 0.25) | shadow-2xl |
shadow-none | Sin sombra | shadow-none |
Como pasa con rounded, la clase shadow sin sufijo sigue funcionando en v4 y da lo mismo que en v3, pero es obsoleta. El falso amigo es shadow-sm, no shadow.
Fíjate en que los valores tienen dos sombras: una amplia y otra corta, con extensión negativa. Es lo que hace que parezcan naturales: la luz real genera una penumbra difusa y una sombra de contacto más nítida.
Color de la sombra
El color se da con una clase aparte, que se combina con la del tamaño:
<div class="shadow-lg shadow-indigo-500">Sombra de color</div>
<div class="shadow-lg shadow-indigo-500/50">Con el 50 % de opacidad</div>
<button class="rounded-lg bg-indigo-600 p-4 text-white shadow-xl shadow-indigo-600/40">Botón con resplandor</button>
Una sombra de color usa todo el color indicado, sin el 10 % de las sombras negras. Por eso suele quedar demasiado fuerte y conviene rebajarla con /40 o /50.
Opacidad en la clase de tamaño: shadow-lg/50 🆕 v4.1
🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: combina el tamaño con un color:
shadow-lg shadow-black/50da exactamente el mismo resultado queshadow-lg/50.
Desde v4.1 puedes dar la opacidad en la propia clase de tamaño. Hay que tener cuidado con lo que significa el número, porque no es una reducción: sustituye a la opacidad de la sombra. Con un 10 % de partida, /30 da una sombra más oscura que la original. Puedes verlo en la Figura 7: es la última tarjeta de la fila de color.
Medido en Chromium (primera capa de la sombra):
shadow-xl rgba(0, 0, 0, 0.1) <- 10 % del tema
shadow-xl/5 oklab(0 0 0 / 0.05) <- más suave
shadow-xl/30 oklab(0 0 0 / 0.3) <- MÁS oscura que shadow-xl
shadow-xl shadow-black/30 oklab(0 0 0 / 0.3) <- lo mismo, con otra escritura
shadow-xl/30 shadow-indigo-500/50 opacidad 0,15 <- con color, los dos valores se multiplican
Sin color propio, el número fija la opacidad final. Con un color que ya trae opacidad, los dos valores se multiplican.
Quitar o heredar una sombra
shadow-none solo limpia su capa: no afecta a un anillo que tengas puesto. shadow-inherit, por su parte, hereda el color de sombra del padre.
Clases: ring-2 shadow-none
Medido: el anillo sigue ahí; solo la capa de sombra queda vacía
10. Capas interiores 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: usa
shadow-innerpara una sombra interior, yring-1 ring-insetpara un anillo interior. Si necesitas combinarlos con una sombra exterior, escríbelos juntos en un valor arbitrario, comoshadow-[inset_0_2px_4px_rgb(0_0_0/0.05),0_10px_15px_-3px_rgb(0_0_0/0.1)].
v4 añade dos familias de utilidades que se dibujan hacia dentro y que no interfieren con las sombras ni con los anillos exteriores.
Sombras interiores: inset-shadow-*
| Clase | Valor |
|---|---|
inset-shadow-2xs | inset 0 1px rgb(0 0 0 / 0.05) |
inset-shadow-xs | inset 0 1px 1px rgb(0 0 0 / 0.05) |
inset-shadow-sm | inset 0 2px 4px rgb(0 0 0 / 0.05) |
inset-shadow-none | Sin sombra interior |
Aceptan color (inset-shadow-indigo-500/60). El modificador de opacidad en el tamaño (inset-shadow-sm/50) llegó en v4.1, igual que en shadow-*.
Anillos interiores: inset-ring-*
inset-ring mide 1 px y usa el color del texto; inset-ring-2 inset-ring-indigo-500/40 da otro grosor y color. ¿En qué se diferencia de ring-inset? ring-inset convierte en interior el único anillo que hay. inset-ring es otro anillo: puedes tener uno por dentro y otro por fuera a la vez.
Por qué existen: cinco capas en una propiedad 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
box-shadowcompone tres capas:ring-offset,ringyshadow, sin capas interiores propias. Si usas la LTS: para combinar más de lo que permiten esas tres capas, junta todas las sombras en un solo valor arbitrarioshadow-[...], separadas por comas.
En CSS solo existe una propiedad box-shadow. Si dos clases la escribieran directamente, la última borraría a la otra. Por eso Tailwind guarda cada capa en una variable y compone el valor final:
/* Lo que escribe shadow-lg en 4.3.3 */
.shadow-lg {
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
Figura 8. Cada familia de clases rellena su propia variable, y todas confluyen en box-shadow.
Tres reglas salen de este diagrama:
- Clases de capas distintas se suman.
ring-2 shadow-lgda anillo y sombra. - Clases de la misma capa se pisan, y gana la que aparece después en el CSS, no en tu atributo
class.shadow-lgyshadow-innerescriben--tw-shadow, así que solo una sobrevive. - Un valor arbitrario entra por la capa de sombra, salvo que escribas la propiedad.
shadow-[0_0_0_3px_red]convive con un anillo. En cambio,[box-shadow:0_0_0_3px_red]sobrescribe la propiedad entera y borra todas las capas.
La segunda regla la medimos con los dos órdenes, y el resultado es idéntico:
class="shadow-lg shadow-inner" -> solo la sombra de shadow-lg
class="shadow-inner shadow-lg" -> solo la sombra de shadow-lg
class="shadow-lg inset-shadow-sm" -> las dos (solo en v4)
Con estas capas se construye un botón con relieve, como en la Figura 9:

Figura 9. Cada clase añade una capa al mismo box-shadow, sin borrar las anteriores.
<button class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white
shadow-md
ring-1 ring-indigo-900
inset-ring inset-ring-white/20
inset-shadow-sm inset-shadow-white/40">
Comprar
</button>
11. Sombras en modo oscuro
Una sombra negra sobre fondo casi negro no se ve. Si tu interfaz tiene modo oscuro, la sensación de altura debe venir de otra parte. Hay varias técnicas, recogidas en la Figura 10, capturada con el modo oscuro del sistema activo.

Figura 10. Formas de separar una tarjeta del fondo en modo oscuro. La primera, solo con sombra, apenas se distingue.
| Técnica | Clases | Cuándo |
|---|---|---|
| Anillo claro y casi transparente | dark:ring-1 dark:ring-white/10 | La opción más limpia y habitual |
| Borde de un gris más claro | dark:border-slate-700 | Si el diseño ya usa bordes |
| Superficie más clara | dark:bg-slate-800 | Para elementos que deben parecer más cercanos |
| Sombra de color | shadow-lg shadow-indigo-500/30 | Resplandor para destacar un elemento |
<article class="rounded-xl bg-white p-5 shadow-lg ring-1 ring-black/5 dark:bg-slate-900 dark:ring-white/10">
<h3 class="font-semibold">Con anillo</h3>
<p class="text-sm">En claro, sombra y un perfilado sutil. En oscuro, el anillo dibuja el contorno.</p>
</article>
El patrón ring-1 ring-black/5 usado en modo claro es muy común: añade un perfilado casi invisible que mejora la definición de la tarjeta, y al usar opacidad se adapta a cualquier color de fondo. La variante dark: se explica en el módulo 12.
Brillo superior 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: consigue el mismo brillo con
dark:shadow-[inset_0_1px_rgb(255_255_255/0.2)], teniendo en cuenta que ocupa la capa de sombra.
Una línea clara arriba simula luz que cae desde encima:
<article class="rounded-xl bg-white p-5 shadow-lg dark:bg-slate-900 dark:inset-shadow-2xs dark:inset-shadow-white/20">
Con brillo superior
</article>
12. Opacidad: opacity-*
opacity-* vuelve transparente el elemento completo, con su texto, sus bordes y todos sus hijos. Es distinto del modificador de color, que solo afecta a esa propiedad.

Figura 11. Arriba, opacity-50 frente a bg-white/50. Debajo, modos de mezcla, isolate y bg-blend-*.
<div class="bg-white p-4 opacity-50">El texto también queda a la mitad y cuesta leerlo</div>
<div class="bg-white/50 p-4">Solo el fondo es transparente: el texto se lee perfectamente</div>
| Quieres… | Usa |
|---|---|
| Un fondo translúcido con texto legible | bg-white/50 |
| Atenuar un elemento entero, como un botón desactivado | opacity-50 |
| Mostrar u ocultar con transición | opacity-0 y opacity-100 |
La escala va de 5 en 5, de opacity-0 a opacity-100.
⚠️ Importante:
opacity-0no quita el elemento: sigue ocupando sitio, recibiendo clics y siendo enfocable con el teclado. Para ocultarlo de verdad, añadeinvisible, ohiddensi tampoco debe ocupar sitio. Las veremos en el módulo 07.
Un botón desactivado (disabled:opacity-50) es el uso típico. Vigila el contraste: el texto al 50 % de opacidad puede quedar por debajo de lo legible.
13. Modos de mezcla: mix-blend-*, bg-blend-* e isolate
Un modo de mezcla decide cómo se combinan los colores de un elemento con los de lo que tiene detrás. Son las mismas fórmulas que en un programa de edición de imágenes. No son exclusivas de Tailwind: mix-blend-multiply es solo mix-blend-mode: multiply.
| Clase | Efecto | Uso típico |
|---|---|---|
mix-blend-multiply | Oscurece: multiplica los colores | Quitar el fondo blanco de un logotipo sobre una foto |
mix-blend-screen | Aclara: lo contrario que multiply | Luces y brillos |
mix-blend-overlay | Refuerza el contraste | Tintar imágenes |
mix-blend-difference | Resta los colores | Texto que contrasta con cualquier fondo |
mix-blend-normal | Sin mezcla | Valor por defecto |
Existen también mix-blend-darken, -lighten, -color-dodge, -color-burn, -hard-light, -soft-light, -exclusion, -hue, -saturation, -color, -luminosity, -plus-lighter y -plus-darker.
<div class="relative h-28 overflow-hidden rounded-lg">
<img src="/img/escena.svg" alt="" class="h-full w-full object-cover">
<div class="absolute inset-0 flex items-center justify-center text-4xl font-black text-white mix-blend-difference">
OFERTA
</div>
</div>
Aquí el texto blanco se invierte con lo que tiene detrás, y por eso cambia de color letra a letra en la Figura 11.
Limitar la mezcla con isolate
mix-blend-* se mezcla con todo lo que queda debajo, incluido el fondo de la página. isolate crea un grupo aislado: los elementos de dentro solo se mezclan entre sí.
<div class="bg-[repeating-linear-gradient(45deg,#fde68a_0_14px,#a7f3d0_14px_28px)]">
<div class="isolate flex">
<div class="size-20 rounded-full bg-rose-500 mix-blend-multiply"></div>
<div class="-ml-8 size-20 rounded-full bg-sky-500 mix-blend-multiply"></div>
</div>
</div>
Sin isolate, las franjas se transparentan a través de los círculos. Con isolate, solo se oscurece la zona donde los círculos se cruzan. Compara las dos versiones en la Figura 11.
Mezclar capas del mismo fondo: bg-blend-*
bg-blend-* no mezcla el elemento con lo de detrás, sino las capas de fondo del propio elemento: su imagen con su color de fondo. Es una forma sencilla de teñir una imagen:
<div class="h-28 bg-indigo-500 bg-[url(/img/escena.svg)] bg-cover bg-center bg-blend-luminosity"></div>
14. Filtros: blur-*, grayscale y compañía
Los filtros procesan los píxeles ya dibujados de un elemento, con sus hijos incluidos. Funcionan sobre cualquier elemento, no solo sobre imágenes.

Figura 12. Filtros sobre la misma imagen, combinaciones y, abajo, shadow-* frente a drop-shadow-*.
| Clases | CSS | Observación |
|---|---|---|
brightness-0…brightness-200 | filter: brightness() | Por debajo de 100 oscurece, y por encima aclara |
contrast-0…contrast-200 | filter: contrast() | Por debajo de 100 apaga la imagen |
grayscale, grayscale-0 | filter: grayscale() | Blanco y negro, o color normal |
sepia, sepia-0 | filter: sepia() | Tono envejecido |
invert, invert-0 | filter: invert() | Colores invertidos |
saturate-0…saturate-200 | filter: saturate() | Intensidad del color |
hue-rotate-15…hue-rotate-180, -hue-rotate-90 | filter: hue-rotate() | Gira el tono |
filter-none | filter: none | Quita todos los filtros |
Los filtros se combinan, pero en orden fijo
Como con las sombras, cada clase rellena una variable, y filter las compone todas. Por eso se suman sin pisarse:
<img src="/img/escena.svg" alt="" class="grayscale contrast-125">
<img src="/img/escena.svg" alt="" class="brightness-75 saturate-150">
Pero el orden de aplicación lo fija Tailwind, no tu atributo class:
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,)
var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
Con los filtros el orden importa: girar el tono y luego aplicar sepia no da lo mismo que al revés. Si necesitas un orden concreto, escribe la propiedad completa:
<!-- Estas dos dan lo mismo: blur(12px) hue-rotate(90deg) sepia(1) -->
<div class="sepia hue-rotate-90 blur-md">Primero</div>
<div class="hue-rotate-90 blur-md sepia">Segundo</div>
<!-- Aquí mandas tú: sepia(1) hue-rotate(90deg) -->
<div class="[filter:sepia(1)_hue-rotate(90deg)]">Orden propio</div>
Desenfoque: la escala de blur-* 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
blur-smdesenfoca 4 px; en v4, 8 px, justo el doble. Si usas la LTS: usablur-smpara 4 px yblurpara 8 px.blur-xsno existe.
| Clase en v4 | Valor | Clase en v3 |
|---|---|---|
blur-xs | 4 px | blur-sm |
blur-sm | 8 px | blur |
El resto de la escala coincide: blur-md (12 px), blur-lg (16 px), blur-xl (24 px), blur-2xl (40 px) y blur-3xl (64 px).
La clase blur sin sufijo sigue funcionando en v4 con los mismos 8 px, pero es obsoleta. El desenfoque también difumina los bordes de la imagen, que se abren más allá de su caja, como ves en blur-md en la Figura 12.
shadow-* frente a drop-shadow-*
shadow-* sigue siempre la caja rectangular. drop-shadow-* es un filtro que analiza los píxeles reales, así que sigue la forma de lo que se dibuja, respetando la transparencia. Es la diferencia entre las dos primeras imágenes de la última fila de la Figura 12: con shadow-xl aparece un rectángulo; con drop-shadow-xl, la silueta del portátil.
Escala de drop-shadow-* 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
drop-shadow-md,-lgy-xlusan dos sombras y dan otro resultado que en v4, donde usan una sola, y ademásdrop-shadow-smpasa a llamarsedrop-shadow-xs. Si usas la LTS: las clases de la tabla de esta sección dan los valores de v3; para el aspecto exacto de v4 usa un valor arbitrario, comodrop-shadow-[0_9px_7px_rgb(0_0_0/0.1)].
Esta es la fila de la tabla de renombrados que más engaña. La guía oficial solo cita drop-shadow-sm y drop-shadow, pero md, lg y xl conservan el nombre y cambian de valor. El registro de cambios lo recoge como “use single drop shadow values”:
| Clase | v3.4.19 | v4.3.3 |
|---|---|---|
drop-shadow-xs | No existe | 0 1px 1px rgb(0 0 0 / 0.05) |
drop-shadow-sm | 0 1px 1px rgb(0 0 0 / 0.05) | 0 1px 2px rgb(0 0 0 / 0.15) |
drop-shadow | 0 1px 2px al 10 % y 0 1px 1px al 6 % | Igual que en v3, obsoleta |
drop-shadow-md | 0 4px 3px al 7 % y 0 2px 2px al 6 % | 0 3px 3px rgb(0 0 0 / 0.12) |
drop-shadow-lg | 0 10px 8px al 4 % y 0 4px 3px al 10 % | 0 4px 4px rgb(0 0 0 / 0.15) |
drop-shadow-xl | 0 20px 13px al 3 % y 0 8px 5px al 8 % | 0 9px 7px rgb(0 0 0 / 0.1) |
drop-shadow-2xl | 0 25px 25px rgb(0 0 0 / 0.15) | 0 25px 25px rgb(0 0 0 / 0.15) |
El cambio de drop-shadow-sm a drop-shadow tampoco es un simple renombrado: el drop-shadow de v3 tiene dos capas y el drop-shadow-sm de v4, una.
Sombras de filtro con color: drop-shadow-<color> 🆕 v4.1
🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: escribe el valor completo en un valor arbitrario, como
drop-shadow-[0_4px_4px_rgb(99_102_241/0.6)], que funciona en las dos versiones.
Igual que shadow-*, ahora drop-shadow-* acepta color y opacidad:
<img src="/img/portatil.svg" alt="" class="drop-shadow-xl drop-shadow-indigo-500">
<img src="/img/portatil.svg" alt="" class="drop-shadow-xl drop-shadow-rose-500/60">
<img src="/img/portatil.svg" alt="" class="drop-shadow-xl/30">
Funcionan como en shadow-*: drop-shadow-xl/30 da una sombra más oscura, porque sustituye la opacidad del 10 %.
15. Filtros del fondo: backdrop-*
blur-md desenfoca el propio elemento. backdrop-blur-md desenfoca lo que hay detrás y deja el contenido nítido. Sin él no se puede hacer el efecto de cristal esmerilado de muchas interfaces modernas.

Figura 13. Arriba, cristal esmerilado con distintos desenfoques. En el centro, otros filtros de fondo. Abajo, un padre con opacidad anula el filtro del hijo.
Para que funcione, el elemento necesita un fondo translúcido o transparente: si es opaco, no deja ver nada detrás.
<header class="sticky top-0 border-b border-white/30 bg-white/30 px-5 py-3 backdrop-blur-md">
<strong>Tienda EMPRESA</strong>
</header>
Las clases son las mismas que las de los filtros normales, con el prefijo backdrop-. backdrop-blur-md, backdrop-brightness-50, backdrop-contrast-50, backdrop-grayscale, backdrop-hue-rotate-90, backdrop-invert, backdrop-saturate-150 y backdrop-sepia actúan sobre lo que hay detrás. backdrop-opacity-50 reduce la intensidad de los demás filtros de fondo.
💡 Consejo: Un cristal resulta más vivo si añades
backdrop-saturate-150: el desenfoque apaga los colores, y la saturación los recupera. Es lo que hace la tercera tarjeta de la Figura 13.
Escala de backdrop-blur-* 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
backdrop-blur-smdesenfoca 4 px; en v4, 8 px. Si usas la LTS: usabackdrop-blur-smpara 4 px ybackdrop-blurpara 8 px.backdrop-blur-xsno existe.
La escala es la misma que la de blur-*, y backdrop-blur sin sufijo sigue en v4 con 8 px, pero obsoleta.
Un padre puede anular el filtro de su hijo
Es el fallo más desconcertante de este módulo: el backdrop-* “no hace nada” y el CSS parece correcto.
📘 Concepto:
backdrop-filterno ve toda la página, sino lo que hay hasta su raíz de fondo (backdrop root), que es el antepasado más cercano conopacitymenor que 100 %,filter,mask,mix-blend-mode,backdrop-filteru otras propiedades similares. Si un antepasado tiene una de ellas, el hijo solo filtra lo que hay dentro de ese antepasado.
En la Figura 13, abajo, los dos paneles son iguales salvo por una clase. A la derecha, el padre tiene opacity-95, y backdrop-grayscale se queda sin efecto, porque dentro del padre no hay nada detrás de él:
<!-- El hijo filtra la imagen -->
<div class="bg-[url(/img/escena.svg)] bg-cover">
<div>
<div class="h-20 backdrop-grayscale">Funciona</div>
</div>
</div>
<!-- El padre es una raíz de fondo: el hijo no alcanza la imagen -->
<div class="bg-[url(/img/escena.svg)] bg-cover">
<div class="opacity-95">
<div class="h-20 backdrop-grayscale">No filtra la imagen</div>
</div>
</div>
Lo mismo ocurre al anidar dos paneles de cristal. La solución es sacar el efecto del padre: pasa la opacidad a un color (bg-white/90) o haz que el panel con backdrop-* no sea descendiente del que tiene el efecto.
Rendimiento y accesibilidad
backdrop-blur-* es de los efectos más caros de dibujar, porque el navegador debe volver a procesar lo que hay detrás en cada frame cuando algo se mueve. Una barra o un modal están bien; decenas de tarjetas con desenfoque en una lista larga pueden ir lentas en un móvil modesto.
Además, un fondo translúcido reduce el contraste del texto, y algunas personas configuran su sistema para reducir transparencias. Puedes respetar esa preferencia con una variante arbitraria:
<header class="bg-white/50 backdrop-blur-md [@media(prefers-reduced-transparency:reduce)]:bg-white">
Tienda EMPRESA
</header>
Lo probamos en Chromium emulando esa preferencia: el fondo pasa de translúcido a rgb(255, 255, 255).
16. Efectos secundarios: apilamiento y position: fixed
Las clases de este módulo parecen puramente decorativas, pero algunas cambian cómo se organizan los elementos. Conocer esto te evitará horas de depuración.
Contextos de apilamiento
📘 Concepto: Un contexto de apilamiento es un grupo de elementos que se ordena como un bloque. El
z-indexde un hijo solo compite con sus hermanos de dentro del grupo. Por muy alto que sea, nunca pasará por encima de algo que esté por encima del grupo entero.
Lo hemos probado con el mismo montaje: un hijo con z-50 dentro de un padre, frente a una caja hermana del padre con z-10. Sin contexto, gana z-50. Con contexto, el hijo queda atrapado y gana z-10.
<div class="relative h-20">
<div class="opacity-95"> <!-- crea un contexto -->
<div class="absolute inset-0 z-50 bg-rose-300">z-50</div>
</div>
<div class="relative z-10 h-20 bg-sky-300">z-10</div> <!-- esta queda encima -->
</div>
| Clase en el padre | ¿Crea contexto? | Gana |
|---|---|---|
| Ninguna | No | z-50 |
shadow-lg, zoom-125 | No | z-50 |
opacity-95 (cualquier valor menor que 100), isolate | Sí | z-10 |
grayscale y demás filtros, backdrop-blur-sm | Sí | z-10 |
mix-blend-multiply, máscaras mask-* | Sí | z-10 |
Es muy típico en este caso: un menú desplegable con z-50 queda oculto bajo el contenido, porque una tarjeta antepasada tiene opacity-95 o un filtro. El estudio completo de z-index está en el módulo 07.
💡 Consejo:
isolatees la forma limpia de crear un contexto a propósito. Evita usar una opacidad casi completa como truco, porque esconde la intención y crea además una raíz de fondo.
filter atrapa a position: fixed
Un elemento fixed se coloca respecto a la ventana… salvo que un antepasado tenga filter o backdrop-filter. En ese caso, se coloca respecto a ese antepasado.
Elemento fixed top-0 left-0 w-full, en una ventana de 900 px, dentro de un padre de 256 px:
Clase en el padre x ancho Resultado (igual en v3.4.19 y en v4.3.3)
(ninguna) 0 900 Respecto a la ventana
opacity-50 0 900 Respecto a la ventana
blur-none 0 900 Respecto a la ventana: el filtro queda vacío
blur-sm 112 256 Atrapado en el padre
backdrop-blur-sm 112 256 Atrapado en el padre
Este es el motivo por el que un modal fixed inset-0 dentro de una tarjeta con backdrop-blur no cubre la pantalla. La solución es colocar el modal fuera de ese antepasado, justo antes de cerrar <body>, o usar el elemento <dialog>, que se muestra en la capa superior del navegador (lo verás en el módulo 17).
Fíjate también en que opacity crea contexto de apilamiento, pero no atrapa a fixed. Los tres fenómenos de este módulo se parecen, pero no son el mismo.
⚠️ Importante: En v3,
drop-shadow-nonegenera un filtro de verdad,drop-shadow(0 0 #0000), que sigue creando contexto de apilamiento. En v4,drop-shadow-nonedejafilter: none. Para quitar filtros en v3, usafilter-none, que funciona en las dos versiones.
17. Máscaras: mask-* 🆕 v4.1
🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: escribe la propiedad con un valor arbitrario, como
[mask-image:linear-gradient(to_bottom,black_30%,transparent_95%)], que da el mismo resultado quemask-b-from-30% mask-b-to-95%.
Una máscara decide qué partes de un elemento se ven: donde la máscara es opaca, el elemento se ve; donde es transparente, desaparece. Hasta v4.1 había que escribir degradados a mano, y ahora hay utilidades para casi todo. Son el equivalente CSS de las máscaras de capa de un editor de imágenes.

Figura 14. Las zonas enmascaradas dejan ver el fondo gris. Abajo, una cabecera fundida con la página.
from y to
Las máscaras de degradado se describen con dos puntos: dónde empieza a desaparecer el elemento (from) y dónde ya no se ve (to).
Figura 15. from y to son las dos paradas del degradado. Varias máscaras se intersecan.
La clase mask-b-from-50% significa: desvanece hacia abajo, empezando en el 50 %. La letra indica el borde hacia el que desaparece, y el valor es la distancia desde el borde opuesto. Por defecto, from vale 0% y to vale 100%.
| Clases | Resultado |
|---|---|
mask-b-from-50% | Se ve hasta la mitad y se desvanece hasta el borde inferior |
mask-b-to-60% | Se desvanece desde arriba y desaparece en el 60 % |
mask-b-from-30% mask-b-to-70% | Desvanecimiento entre el 30 % y el 70 % |
mask-b-from-12 mask-b-to-12 | Con los dos iguales hay corte brusco: a 3 rem, porque usa la escala de espaciado |
Por lados
Las letras son t, r, b y l para cada borde, x para los dos laterales e y para arriba y abajo: mask-x-from-70%, mask-y-to-90%.
El valor admite porcentajes (mask-b-from-50%), números de la escala de espaciado (mask-l-from-20, que da calc(var(--spacing) * 20)), y valores arbitrarios (mask-b-from-[3px]). Los colores (mask-t-from-black) cambian la opacidad de cada parada: solo cuenta su transparencia, salvo que uses mask-luminance.
Degradados lineales con ángulo, radiales y cónicos
<img src="/img/escena.svg" alt="" class="mask-linear-45 mask-linear-from-40%">
<img src="/img/escena.svg" alt="" class="mask-radial-from-40%">
<img src="/img/escena.svg" alt="" class="mask-circle mask-radial-at-right mask-radial-from-30% mask-radial-to-70%">
<img src="/img/escena.svg" alt="" class="mask-conic-from-75% mask-conic-to-75%">
Las familias son mask-linear-* con su ángulo (mask-linear-45, -mask-linear-45), mask-radial-* con su forma (mask-circle, mask-ellipse), tamaño (mask-radial-closest-side) y posición (mask-radial-at-right), y mask-conic-* con su ángulo (mask-conic-90).
Las máscaras cónicas giran como las agujas del reloj desde arriba, y son la base de indicadores circulares de progreso.
Combinar máscaras
Tailwind añade mask-composite: intersect a cada utilidad de degradado. Por eso, al combinar varias, solo queda lo que todas dejan ver. Es muy útil para desvanecer dos bordes a la vez:
<img src="/img/escena.svg" alt="" class="mask-r-from-60% mask-b-from-60%">
Máscaras con imagen
Una imagen con transparencia también sirve de máscara, y el elemento toma su silueta. El fondo gris de la Figura 14 se ve alrededor de la forma del portátil.
<img src="/img/escena.svg" alt="" class="mask-[url(/img/portatil.svg)] mask-contain mask-center mask-no-repeat">
| Clases | Propiedad | Se parecen a… |
|---|---|---|
mask-[url(...)], mask-none | mask-image | bg-[url(...)] |
mask-contain, mask-cover, mask-auto | mask-size | bg-contain… |
mask-center, mask-top, mask-top-left… | mask-position | bg-center… |
mask-no-repeat, mask-repeat, mask-repeat-x… | mask-repeat | bg-no-repeat… |
mask-add, mask-subtract, mask-intersect, mask-exclude | mask-composite | Cómo se combinan varias máscaras |
mask-alpha, mask-luminance, mask-match | mask-mode | Si manda la transparencia o el brillo |
Caso real: cabecera fundida con la página
<header class="relative h-72 overflow-hidden">
<img src="/img/escena.svg" alt="" class="absolute inset-0 h-full w-full object-cover mask-b-from-30% mask-b-to-95%">
<div class="absolute inset-x-0 bottom-4 text-center">
<h1 class="text-3xl font-black">Rebajas de otoño</h1>
<p class="text-slate-600">Hasta un 30 % en portátiles seleccionados</p>
</div>
</header>
La imagen no se funde con el color de fondo de la página: se vuelve transparente. Por eso funcionará igual en modo oscuro, sin tener que cambiar ningún color. Un degradado de color puesto encima de la imagen, en cambio, tendría que repetir el color de fondo.
18. Escala de la caja: zoom-* 🆕 v4.3
🆕 Desde Tailwind 4.3. No existe en la LTS (Tailwind 3.4). Si usas la LTS: escribe la propiedad directamente con un valor arbitrario, por ejemplo
[zoom:1.25], que da el mismo resultado quezoom-125.
zoom-* escribe la propiedad CSS zoom, que agranda o reduce un elemento recalculando el diseño, como si cada longitud se multiplicara por ese factor.
/* Lo que genera zoom-125 */
.zoom-125 {
zoom: 125%;
}
Acepta cualquier número (zoom-50, zoom-110, zoom-200), valores arbitrarios (zoom-[1.5]) y variables (zoom-(--escala)). La propiedad funciona en todos los navegadores que admite v4; MDN la marca como disponible de forma general desde mayo de 2024, cuando la incorporó Firefox.

Figura 16. zoom-* hace sitio; scale-* solo se dibuja más grande.
zoom-* frente a scale-*
Las dos clases parecen hacer lo mismo, pero scale es una transformación visual: el diseño no se entera, y el elemento pisa a sus vecinos. Lo medimos con una tarjeta de w-32 (128 px) seguida de un vecino a 16 px de distancia:
Ancho dibujado Ancho CSS Vecino empieza en ¿Pisa al vecino?
zoom-150 192 px 128 px 664 (a 16 px) No
scale-150 192 px 128 px 312 Sí: la tarjeta acaba en 328
zoom-* | scale-* | |
|---|---|---|
| Recoloca a los vecinos | Sí | No |
| Coste | Recalcula el diseño | Muy bajo, solo se redibuja |
| Animación | Poco fluida | Fluida |
| Uso típico | Hacer más densa o más grande una zona | Efectos al pasar el ratón |
Usa scale-* para animaciones, y lo estudiarás en el módulo 13. Usa zoom-* para cambiar de forma estable el tamaño de una zona, como un panel denso de datos o una vista previa.
⚠️ Importante: No uses
zoom-*para que la interfaz “quepa” en un móvil: eso se resuelve con el diseño adaptable. Además, reducir la letra por debajo de lo legible perjudica a todos.
19. Escalas renombradas 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4), las clases de
shadow,drop-shadow,blur,backdrop-bluryroundedtienen nombres desplazados un puesto respecto a v4, youtline-noneyringcambian de significado. Si usas la LTS: usa la columna v3 de la tabla de esta sección.
En v3 las escalas tenían un valor “por defecto” sin nombre (shadow, blur, rounded), justo entre sm y md. v4 elimina ese hueco para que todos los valores tengan nombre, y la escala se corre un puesto.
Figura 17. El valor no cambia; cambia el nombre. El peligro está en rounded-sm, que existe en las dos versiones.
| v3.4 | Se llama en v4 | Valor | Si no cambias nada al migrar… |
|---|---|---|---|
shadow-sm | shadow-xs | 0 1px 2px 0 rgb(0 0 0 / 0.05) | La sombra se hace mayor |
shadow | shadow-sm | Dos capas, 0 1px 3px y 0 1px 2px -1px | Se ve igual, pero es obsoleta |
drop-shadow-sm | drop-shadow-xs | 0 1px 1px rgb(0 0 0 / 0.05) | La sombra se hace mayor |
drop-shadow | drop-shadow-sm | Parecido, no idéntico: ver la sección 14 | Se ve igual, pero es obsoleta |
blur-sm | blur-xs | 4 px | El desenfoque se duplica |
blur | blur-sm | 8 px | Se ve igual, pero es obsoleta |
backdrop-blur-sm | backdrop-blur-xs | 4 px | El desenfoque se duplica |
backdrop-blur | backdrop-blur-sm | 8 px | Se ve igual, pero es obsoleta |
rounded-sm | rounded-xs | 2 px | La esquina se duplica |
rounded | rounded-sm | 4 px | Se ve igual, pero es obsoleta |
outline-none | outline-hidden | Contorno transparente | Pierde el foco en colores forzados |
ring | ring-3 | 3 px | Pasa a 1 px y al color del texto |
La guía de actualización presenta shadow, blur y rounded como renombradas, y la columna final puede sorprender: no desaparecen. Las hemos compilado y dan el mismo valor que en v3, porque theme.css las conserva en un bloque Deprecated. El problema real son las clases -sm, que siguen existiendo y cambian de valor.
La Figura 18 muestra las mismas clases compiladas con cada versión. Es el mismo HTML, y las dos columnas no tienen ni una sola clase distinta:

Figura 18. Mismo código, dos versiones reales. Mira el color del borde, el grosor de ring y outline, outline-2 (invisible en v3), blur-sm y backdrop-blur-sm.
⚠️ Importante: Una búsqueda y sustitución ingenua estropea el proyecto. Si primero cambias
shadowporshadow-smy luegoshadow-smporshadow-xs, todas acaban enshadow-xs. Hay que hacerlo en el orden inverso, o usarnpx @tailwindcss/upgrade, como verás en el módulo 19.
20. Valores propios y escalas dinámicas
Cuando la escala no te sirve, tienes varias salidas. Elige la primera que resuelva tu caso.
Valores arbitrarios
Funcionan igual en las dos versiones, con corchetes. En una sombra, los espacios se escriben con _:
<div class="border-[3px] border-[#123456]">Ancho y color exactos</div>
<div class="rounded-[1.25rem] shadow-[0_0_0_3px_rgb(225_29_72)]">Radio y sombra exactos</div>
<div class="opacity-[.67] blur-[2px]">Opacidad y desenfoque exactos</div>
Variables con paréntesis 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4), las variables CSS se escriben con corchetes y
var(), comorounded-[var(--radio)]; la sintaxis con paréntesis de v4 no existe. Si usas la LTS: usa corchetes y añade la pista de tipo cuando la clase sea ambigua, comoborder-[length:var(--grosor)]oshadow-[shadow:var(--sombra)].
| v4 | v3 | Medido en Chromium |
|---|---|---|
rounded-(--radio) | rounded-[var(--radio)] | Las dos dan el radio |
border-(--color) | border-[color:var(--color)] | Color del borde |
border-(length:--grosor) | border-[length:var(--grosor)] | Ancho del borde |
shadow-(--sombra) | shadow-[shadow:var(--sombra)] | La sombra de la variable |
⚠️ Importante:
borderpuede significar ancho o color, y Tailwind no puede leer el valor de la variable. Por eso hay que indicar el tipo. En v3 el caso es aún más traicionero:shadow-[var(--sombra)]se interpreta como color de sombra, y lo medimos: no aparece ninguna sombra. Hay que escribirshadow-[shadow:var(--sombra)].
Escalas dinámicas 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4), solo existen los valores fijos de cada escala;
border-3yopacity-33no generan CSS. Si usas la LTS: escribeborder-[3px],opacity-[.33],grayscale-[50%],brightness-[.37]osepia-[.5].
v4 acepta cualquier número en estas familias, sin corchetes y sin configuración:
| Familia | Ejemplo en v4 | Equivalente en v3 |
|---|---|---|
| Anchos | border-3, border-13, ring-13, outline-3 | border-[3px], ring-[13px], outline outline-[3px] |
| Opacidad | opacity-33 | opacity-[.33] |
| Filtros | brightness-37, contrast-37, saturate-37 | brightness-[.37] |
| Filtros en porcentaje | grayscale-50, sepia-50, invert-20 | grayscale-[50%], sepia-[.5], invert-[.2] |
Tokens propios en el tema 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4), el tema se amplía en
tailwind.config.js, no con@theme. Si usas la LTS: añade tus valores entheme.extend, dentro deborderRadius,boxShadowyblur.
Si un valor se repite, dale nombre. En v4 cada variable del tema crea sus clases:
@import "tailwindcss";
@theme {
--radius-tarjeta: 1.25rem;
--shadow-tarjeta: 0 8px 24px -6px rgb(30 58 138 / 0.25);
--blur-suave: 2px;
}
<article class="rounded-tarjeta shadow-tarjeta">Tarjeta de la tienda</article>
<img src="/img/escena.svg" alt="" class="blur-suave">
El equivalente en v3, que hemos compilado y genera el mismo resultado:
// tailwind.config.js
export default {
content: ['./index.html'],
theme: {
extend: {
borderRadius: { tarjeta: '1.25rem' },
boxShadow: { tarjeta: '0 8px 24px -6px rgb(30 58 138 / 0.25)' },
blur: { suave: '2px' },
},
},
};
El tema se estudia en profundidad en el módulo 14.
21. Caso completo: tarjetas y pedidos de la tienda 🆕 v4.1
🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: en esta tarjeta de ejemplo, cambia
drop-shadow-xl drop-shadow-black/40pordrop-shadow-[0_9px_7px_rgb(0_0_0/0.4)],inset-ring inset-ring-emerald-600/30porring-1 ring-inset ring-emerald-600/30,shadow-xsporshadow-sm, y la sombra del botón porshadow-[inset_0_1px_rgb(255_255_255/0.3),0_1px_3px_0_rgb(0_0_0/0.1)].
Reunimos casi todo lo visto con datos reales de la tienda de EMPRESA: tres productos en distintas situaciones de existencias y cuatro pedidos, uno por cada estado. La estructura de la tarjeta normal es la que construirás en el ejercicio 2, así que aquí vemos sus clases más ricas y la tarjeta agotada completa. Hemos compilado esta página con v4 y su traducción a v3 con los cambios del recuadro, y el resultado es visualmente igual.

Figura 19. Bordes, radios, anillos, sombras por capas, filtros, backdrop-blur, opacidad y separadores trabajando juntos.
<!-- En la tarjeta normal, la imagen y el botón llevan varias capas -->
<img src="/img/portatil.svg" alt="" class="absolute inset-0 m-auto h-28 drop-shadow-xl drop-shadow-black/40">
<button type="button" class="rounded-lg bg-indigo-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm ring-1 ring-indigo-800 inset-shadow-2xs inset-shadow-white/30 hover:bg-indigo-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">
Añadir
</button>
<!-- Tarjeta agotada: gris en la imagen, opacidad solo en el texto -->
<article class="overflow-hidden rounded-2xl bg-white shadow-xs ring-1 ring-slate-900/5">
<div class="relative h-40 bg-[url(/img/escena.svg)] bg-cover bg-center grayscale">
<img src="/img/teclado.svg" alt="" class="absolute inset-0 m-auto h-28 opacity-60">
<span class="absolute inset-0 flex items-center justify-center bg-white/40 text-sm font-bold tracking-widest text-slate-800 uppercase">
Agotado
</span>
</div>
<div class="p-4 opacity-60">
<h3 class="font-semibold">Pack teclado y ratón</h3>
<p class="text-sm text-slate-500">Conjunto inalámbrico de teclado y ratón</p>
<div class="mt-3 flex items-center justify-between">
<span class="text-lg font-bold">34,50 €</span>
<button type="button" disabled class="rounded-lg border border-dashed border-slate-400 px-3 py-1.5 text-sm font-semibold text-slate-500">
Avisarme
</button>
</div>
</div>
</article>
La lista de pedidos combina los separadores de la sección 8 con border-s-4 border-s-emerald-500 para la marca de color, y con las insignias del ejercicio 1.
Algunas decisiones que conviene comentar:
overflow-hiddenen la tarjeta: recorta la imagen con el radio. Por eso los anillos y las sombras se ponen en la propia tarjeta, no en un hijo, que quedaría recortado.ring-1 ring-slate-900/5y noborder: perfila sin ocupar sitio, y su opacidad se adapta al fondo.drop-shadow-*en el portátil: la imagen es irregular;shadow-*dibujaría un rectángulo.- Opacidad por partes en la tarjeta agotada: no la aplicamos a la tarjeta entera, porque crearía un contexto de apilamiento y una raíz de fondo, y también apagaría el rótulo “Agotado”, que debe leerse bien.
inset-ringen las insignias: el fondo es muy claro y el anillo interior define el contorno sin cambiar el tamaño.border-s-4: es lógico, de modo que condir="rtl"la marca de color pasará al lado derecho sin tocar nada.- Sin anillo en el foco del botón: usa
outline, que aguanta los colores forzados y no choca con elring-1que ya tiene.
22. Depuración de bordes y efectos
| Síntoma | Causa | Solución |
|---|---|---|
Copio border-2 y sale del color del texto | Color por defecto de v4 | Añade border-slate-200 |
outline-2 no se ve | Estás en v3 | Escribe outline outline-2 |
ring se ve fino y negro | v4: 1 px y currentColor | Usa ring-3 ring-blue-500 |
| Las esquinas de la imagen asoman | rounded-* no recorta a los hijos | overflow-hidden en el padre |
| El anillo o la sombra se cortan | Un antepasado tiene overflow-hidden | Quita ese overflow, añade p-1, o usa ring-inset |
| La tabla no se redondea | border-collapse: collapse de Preflight | Mete la tabla en un contenedor redondeado |
shadow-inner no se suma a shadow-lg | Las dos escriben --tw-shadow | En v4, inset-shadow-sm; en v3, un valor shadow-[...] |
shadow-lg/30 queda más oscura | El número sustituye al 10 % original | Usa un valor menor, como shadow-lg/5 |
| El anillo blanco aparece en el modo oscuro | ring-offset pinta un color | ring-offset-slate-900 o outline-offset-* |
| Una línea sobra al final de la lista | Último hijo oculto con divide-* en v4 | No lo pintes, o usa [&>:not([hidden])~:not([hidden])]:border-t |
| El foco no se ve en alto contraste | outline-none más ring | outline-hidden, o mejor focus-visible:outline-2 |
| El desenfoque es el doble que en el diseño | blur-sm vale 8 px en v4 | Usa blur-xs |
backdrop-blur-* no hace nada | Fondo opaco, o antepasado con opacity o filter | Usa bg-white/30 o retira el efecto del antepasado |
z-50 no sube | Un antepasado crea contexto de apilamiento | Mueve el elemento o reorganiza los contextos |
Un modal fixed no cubre la pantalla | Un antepasado tiene filter o backdrop-filter | Saca el modal de ahí o usa <dialog> |
mix-blend-* se mezcla con el fondo de la página | Es el comportamiento normal | Pon isolate en un contenedor |
border-(--x) da color y querías ancho | Variable ambigua | border-(length:--x) |
shadow-[var(--x)] no da sombra en v3 | Se interpreta como color | shadow-[shadow:var(--x)] |
| Un HTML nuevo no recibe sus clases con Vite en marcha | El plugin escaneó los ficheros al arrancar | Reinicia Vite o guarda app.css para forzar otro escaneo |
💡 Consejo: Ante un efecto que no funciona, abre las herramientas de desarrollo y mira Computed, no Styles. Ahí ves el
box-shadowo elfilterfinal, con todas las capas ya resueltas. Si la clase no aparece en Styles, Tailwind no la generó: vuelve a la depuración del módulo 01.
23. Ejercicios
Ejercicio 1: Insignias de estado de los pedidos 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: haz estas insignias con
ring-1 ring-inset ring-emerald-600/30en lugar deinset-ring inset-ring-emerald-600/30.
Los pedidos de EMPRESA pueden estar en cuatro estados: ENTREGADO, ENVIADO, PENDIENTE y CANCELADO. Crea una insignia con forma de píldora para cada uno, con fondo muy claro, texto oscuro, un punto de color delante del texto y un anillo interior semitransparente.
Ver solución
<ul class="flex flex-wrap gap-3">
<li><span class="inline-flex items-center gap-1.5 rounded-full bg-emerald-50 px-2.5 py-0.5 text-xs font-medium text-emerald-700 inset-ring inset-ring-emerald-600/30"><span class="size-1.5 rounded-full bg-emerald-500"></span>ENTREGADO</span></li>
<li><span class="inline-flex items-center gap-1.5 rounded-full bg-sky-50 px-2.5 py-0.5 text-xs font-medium text-sky-700 inset-ring inset-ring-sky-600/30"><span class="size-1.5 rounded-full bg-sky-500"></span>ENVIADO</span></li>
<li><span class="inline-flex items-center gap-1.5 rounded-full bg-amber-50 px-2.5 py-0.5 text-xs font-medium text-amber-700 inset-ring inset-ring-amber-600/30"><span class="size-1.5 rounded-full bg-amber-500"></span>PENDIENTE</span></li>
<li><span class="inline-flex items-center gap-1.5 rounded-full bg-rose-50 px-2.5 py-0.5 text-xs font-medium text-rose-700 inset-ring inset-ring-rose-600/30"><span class="size-1.5 rounded-full bg-rose-500"></span>CANCELADO</span></li>
</ul>
rounded-full da la píldora, y size-1.5 con rounded-full, el punto. Usamos inset-ring porque va por dentro y no cambia el tamaño de la insignia. La opacidad /30 hace que el anillo combine con el fondo claro.
Fíjate en que el estado no se comunica solo con el color: el texto sigue diciendo cuál es. Quien no distinga colores puede leerlo igualmente.
Ejercicio 2: Ficha de producto 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4),
drop-shadow-xlybackdrop-blur-smdan otro valor en esta tarjeta. Si usas la LTS: escribedrop-shadow-[0_9px_7px_rgb(0_0_0/0.1)]ybackdrop-blurpara que se vea igual.
Crea una tarjeta de 288 px de ancho para el “Portátil UltraBook 14” (899,00 €). La cabecera tendrá /img/escena.svg de fondo, la imagen del portátil centrada con una sombra que siga su forma, y una insignia “Novedad” de cristal. Las esquinas de la cabecera deben seguir el radio de la tarjeta, y la sombra debe crecer al pasar el ratón.
Ver solución
<article class="w-72 overflow-hidden rounded-2xl bg-white shadow-md ring-1 ring-slate-900/5 transition-shadow hover:shadow-xl">
<div class="relative h-40 bg-[url(/img/escena.svg)] bg-cover bg-center">
<img src="/img/portatil.svg" alt="Portátil UltraBook 14" class="absolute inset-0 m-auto h-28 drop-shadow-xl">
<span class="absolute top-3 left-3 rounded-full bg-black/40 px-2.5 py-0.5 text-xs font-medium text-white ring-1 ring-white/30 backdrop-blur-sm">
Novedad
</span>
</div>
<div class="p-4">
<h3 class="font-semibold">Portátil UltraBook 14</h3>
<p class="text-sm text-slate-500">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<div class="mt-3 flex items-center justify-between">
<span class="text-lg font-bold">899,00 €</span>
<button type="button" class="rounded-lg bg-indigo-600 px-3 py-1.5 text-sm font-semibold text-white">Añadir</button>
</div>
</div>
</article>
Tres detalles marcan la diferencia. overflow-hidden en el <article> hace que la cabecera respete el radio sin necesidad de rounded-t-2xl. La sombra y el anillo están en el mismo <article>, de modo que overflow-hidden no los recorta: solo afecta a los hijos. Y drop-shadow-xl sigue la silueta del portátil porque el SVG es transparente.
La prueba automática pasó el ratón sobre la tarjeta y comprobó que el box-shadow calculado cambia. transition-shadow anima ese cambio.
Ejercicio 3: Pestañas que no saltan
Crea la navegación de las categorías Portátiles, Sobremesa y Periféricos, con Sobremesa como pestaña activa. La activa llevará una línea inferior de 2 px, que debe coincidir exactamente con la línea gris que recorre toda la navegación. Al activar una pestaña nada debe moverse.
Ver solución
<nav class="flex gap-6 border-b border-slate-200 text-sm font-medium" aria-label="Catálogo">
<a href="#" class="-mb-px border-b-2 border-transparent px-1 py-3 text-slate-500 hover:border-slate-300 hover:text-slate-700">Portátiles</a>
<a href="#" aria-current="page" class="-mb-px border-b-2 border-indigo-600 px-1 py-3 text-indigo-600">Sobremesa</a>
<a href="#" class="-mb-px border-b-2 border-transparent px-1 py-3 text-slate-500 hover:border-slate-300 hover:text-slate-700">Periféricos</a>
</nav>
Medido en Chromium:
altura de cada enlace: 46, 46, 46 px (activa e inactivas son iguales)
parte inferior del enlace: 78 px
parte inferior del nav: 78 px (las dos líneas coinciden)
Hay dos técnicas. border-b-2 border-transparent en las inactivas reserva el sitio, y así todas miden lo mismo. -mb-px baja cada enlace 1 px para que su línea tape la gris de nav, en lugar de quedar encima de ella.
aria-current="page" indica la pestaña activa a los lectores de pantalla. Es lo que falta en muchas pestañas que solo se distinguen por el color.
Ejercicio 4: Plantilla filtrable sin línea sobrante
Crea una lista de empleados con separadores y un desplegable que filtre por departamento, con cuatro empleados de los departamentos 10, 20, 20 y 30. Prueba primero con divide-y y observa qué pasa al filtrar Ventas. Después corrige el defecto para que nunca quede una línea sobrante.
Ver solución
<div class="w-96">
<label class="mb-3 block text-sm font-medium">Departamento
<select id="filtro" class="ms-2 rounded-md border border-slate-300 px-2 py-1">
<option value="">Todos</option>
<option value="20">Ventas</option>
<option value="10">Dirección</option>
</select>
</label>
<ul id="plantilla" class="rounded-xl border border-slate-200 [&>:not([hidden])~:not([hidden])]:border-t [&>:not([hidden])~:not([hidden])]:border-slate-200">
<li data-dept="10" class="p-3">Ana García López <span class="text-sm text-slate-500">Director general</span></li>
<li data-dept="20" class="p-3">Luis Martínez Ruiz <span class="text-sm text-slate-500">Director</span></li>
<li data-dept="20" class="p-3">Carmen Ruiz Ortega <span class="text-sm text-slate-500">Vendedora</span></li>
<li data-dept="30" class="p-3">Marta Sánchez Gil <span class="text-sm text-slate-500">Directora</span></li>
</ul>
</div>
<script>
const filtro = document.querySelector('#filtro');
filtro.addEventListener('change', () => {
for (const empleado of document.querySelectorAll('#plantilla > li')) {
empleado.hidden = filtro.value !== '' && empleado.dataset.dept !== filtro.value;
}
});
</script>
Bordes medidos tras cada filtro (t = arriba, b = abajo):
Todos Ana t=0 Luis t=1 Carmen t=1 Marta t=1
Ventas Luis t=0 Carmen t=1
Dirección Ana t=0
Con divide-y en v4, al filtrar Ventas el <li> de Marta queda oculto, pero sigue siendo el :last-child. Por eso Carmen, que no es el último hijo, conserva su border-bottom. La línea toca el borde del contenedor y forma una raya doble.
La variante arbitraria aplica el borde superior a cada elemento visible que tenga otro visible antes. Es lo mismo que hacía el divide-y de v3: el primer visible nunca lleva línea. Por eso, como ves en la salida, cada filtro deja a su primer elemento con t=0.
Ejercicio 5: Botones accesibles 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4). Si usas la LTS: cambia
inset-ring inset-ring-slate-300porring-1 ring-inset ring-slate-300y añadefocus-visible:outlinea cada botón, porqueoutline-2no se ve sola.
Crea cuatro botones: Guardar (principal), Cancelar (secundario, blanco con anillo interior gris), Eliminar (peligro) y uno desactivado. Los tres activos deben tener un indicador de foco con teclado que siga visible en colores forzados, sin moverse ni tapar a sus vecinos. Pruébalo con el tabulador.
Ver solución
<div class="flex flex-wrap gap-4">
<button type="button" class="rounded-lg bg-indigo-600 px-4 py-2 font-semibold text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Guardar</button>
<button type="button" class="rounded-lg bg-white px-4 py-2 font-semibold text-slate-800 inset-ring inset-ring-slate-300 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Cancelar</button>
<button type="button" class="rounded-lg bg-rose-600 px-4 py-2 font-semibold text-white focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-600">Eliminar</button>
<button type="button" disabled class="rounded-lg bg-indigo-600 px-4 py-2 font-semibold text-white disabled:opacity-50">Sin permiso</button>
</div>
Pulsando el tabulador con Playwright:
Modo normal Guardar, Cancelar, Eliminar: outline solid 2px, offset 2px
Colores forzados Guardar, Cancelar, Eliminar: outline solid 2px, offset 2px
Tras Eliminar el foco sale del grupo: el botón desactivado no lo recibe
outline resuelve todo a la vez: no ocupa sitio, tiene un hueco real y resiste los colores forzados. El gap-4 deja sitio de sobra para los 4 px que necesita (2 de contorno y 2 de hueco).
Ejercicio 6: Navegación de cristal
Crea una barra superior fija sobre una página cuyo fondo sea /img/escena.svg. Debe desenfocar lo que pasa por debajo, ser legible si el navegador no admite backdrop-filter, y quedarse opaca si el usuario ha pedido reducir las transparencias.
Ver solución
<body class="bg-[url(/img/escena.svg)] bg-cover bg-fixed text-slate-900">
<header class="sticky top-0 flex items-center justify-between border-b border-white/40 bg-white/90 px-6 py-3 shadow-md backdrop-blur-md supports-[backdrop-filter]:bg-white/50 [@media(prefers-reduced-transparency:reduce)]:bg-white">
<strong>Tienda EMPRESA</strong>
<nav class="flex gap-5 text-sm">
<a href="#">Portátiles</a><a href="#">Sobremesa</a><a href="#">Periféricos</a>
</nav>
</header>
<main class="h-[150vh] p-6 text-white">
<p>Desplázate y mira cómo se difumina lo que pasa por debajo de la barra.</p>
</main>
</body>
Medido en Chromium:
Normal fondo blanco al 50 %, backdrop-filter: blur(12px)
Transparencia reducida fondo rgb(255, 255, 255) opaco
Hay tres niveles, de menos a más exigente. bg-white/90 es la base: si backdrop-filter no existe, el texto se lee igualmente. supports-[backdrop-filter]:bg-white/50 solo se aplica si el navegador sabe filtrar, y entonces puede ser más transparente. [@media(prefers-reduced-transparency:reduce)]:bg-white tiene la última palabra.
Este ejemplo compila igual en v3 y en v4, y lo hemos medido en las dos. Por eso usa shadow-md, que vale lo mismo en ambas, y no shadow-sm. supports-[...] y las variantes arbitrarias se explican en el módulo 11.
Ejercicio 7: Categorías en gris que recuperan el color
En la portada de la tienda queremos tres categorías con su imagen en blanco y negro y atenuada, que recuperen el color de forma suave al pasar el ratón.
Ver solución
<ul class="grid w-[560px] grid-cols-3 gap-6">
<li><img src="/img/portatil.svg" alt="Portátiles" class="grayscale opacity-60 transition-[filter,opacity] duration-300 hover:grayscale-0 hover:opacity-100"></li>
<li><img src="/img/torre.svg" alt="Sobremesa" class="mx-auto h-28 grayscale opacity-60 transition-[filter,opacity] duration-300 hover:grayscale-0 hover:opacity-100"></li>
<li><img src="/img/teclado.svg" alt="Periféricos" class="grayscale opacity-60 transition-[filter,opacity] duration-300 hover:grayscale-0 hover:opacity-100"></li>
</ul>
Medido con Playwright sobre la primera imagen:
En reposo filter: grayscale(1) opacity: 0.6
Tras pasar el ratón filter: grayscale(0) opacity: 1
grayscale-0 no quita el filtro, sino que pone grayscale(0). Es deliberado: una transición necesita dos valores numéricos entre los que interpolar. Con filter-none el salto sería brusco.
transition-[filter,opacity] limita la animación a esas dos propiedades, en lugar de a todas. Las transiciones se tratan en el módulo 13.
Recuerda que un móvil no tiene ratón: ahí estas imágenes se quedarán en gris. Si el color es importante, cambia la lógica y aplica el gris solo en pantallas con hover.
Ejercicio 8: Cabecera fundida en v4 y en v3 🆕 v4.1
🆕 Desde Tailwind 4.1. No existe en la LTS (Tailwind 3.4). Si usas la LTS: haz directamente la segunda parte del ejercicio, con
[mask-image:linear-gradient(...)].
Crea una cabecera de 288 px de alto con /img/escena.svg, que se desvanezca hacia abajo entre el 30 % y el 95 % de su altura y deje ver el título sobre el fondo de la página. Escríbela con mask-* y después tradúcela a una versión que compile en Tailwind 3.4. Comprueba que las dos dan la misma máscara.
Ver solución
Versión para v4.1 o superior:
<header class="relative h-72 overflow-hidden">
<img src="/img/escena.svg" alt="" class="absolute inset-0 h-full w-full object-cover mask-b-from-30% mask-b-to-95%">
<div class="absolute inset-x-0 bottom-4 text-center">
<h1 class="text-3xl font-black">Rebajas de otoño</h1>
<p class="text-slate-600">Hasta un 30 % en portátiles seleccionados</p>
</div>
</header>
Versión para v3.4, que solo cambia la clase de la máscara:
<img src="/img/escena.svg" alt="" class="absolute inset-0 h-full w-full object-cover [mask-image:linear-gradient(to_bottom,black_30%,transparent_95%)]">
mask-image calculado en Chromium:
v4.3.3 linear-gradient(blanco), linear-gradient(blanco),
linear-gradient(rgb(0, 0, 0) 30%, rgba(0, 0, 0, 0) 95%), linear-gradient(blanco)...
v3.4.19 linear-gradient(rgb(0, 0, 0) 30%, rgba(0, 0, 0, 0) 95%)
Las capturas de las dos versiones son iguales. La de v4 tiene más capas, porque cada lado que no usas ocupa su sitio con un degradado blanco, que al intersecarse no recorta nada. Ese es el precio de poder combinar mask-b-*, mask-r-* y demás con solo añadir clases. En v3, con un valor arbitrario, combinarlas te tocaría a ti.
Ejercicio 9 (propuesto): De v4 a v3
Toma el caso completo de la sección 21 y compílalo en Tailwind 3.4. Sustituye cada clase que no exista y revisa también las que existen pero cambian de valor, como shadow-sm, drop-shadow-xl o blur-sm. Haz una captura de cada versión y compáralas.
Ejercicio 10 (propuesto): Sistema de elevación 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4), el tema no se define con
@theme. Si usas la LTS: define las tres sombras entheme.extend.boxShadow, dentro detailwind.config.js.
Define en @theme tres sombras propias para tu tienda: --shadow-tarjeta, --shadow-menu y --shadow-modal. Todas deben tener el mismo color azulado y una altura creciente. Crea una página con los tres niveles sobre fondo claro y oscuro, y decide qué anillo añadir en oscuro para que se sigan distinguiendo.
Ejercicio 11 (propuesto): Grupo de avatares
Crea cinco avatares circulares de size-12, solapados con -space-x-2, cada uno con ring-2 ring-white para separarlos. Al pasar el ratón sobre uno, debe colocarse por encima de sus vecinos sin que nada se mueva. Prueba después con un fondo oscuro y explica por qué el anillo debe cambiar de color. ¿Podrías resolverlo con ring-offset-*?
Ejercicio 12 (propuesto): Vista previa de factura 🆕 v4.3
🆕 Desde Tailwind 4.3. No existe en la LTS (Tailwind 3.4). Si usas la LTS: usa
[zoom:0.5]en lugar dezoom-50y un valor[mask-image:...]para el sombreado.
Maqueta una factura de EMPRESA a tamaño completo y muéstrala dentro de un panel como vista previa con zoom-50. Mide el espacio que ocupa en la página y repítelo con scale-50. Explica la diferencia. Añade después un sombreado con mask-b-from-* para indicar que la factura continúa, y comprueba cómo queda al imprimir la página.
Resumen
| Concepto | API | Idea clave |
|---|---|---|
| Bordes | border, border-t-2, border-dashed, border-slate-200 | Ocupan sitio; en v4 el color por defecto es currentColor |
| Radios | rounded-lg, rounded-t-2xl, rounded-full | No recortan a los hijos: overflow-hidden |
| Anillos | ring-2, ring-inset, ring-offset-2 | Sombra sólida; ring mide 1 px en v4 y 3 px en v3 |
| Contornos | outline-2, outline-offset-2 | Hueco real y visibles en colores forzados |
| Foco | focus-visible:outline-2, outline-hidden | outline-none de v4 elimina el contorno del todo |
| Separadores | divide-y, divide-slate-200 | En v4, cuidado con el último hijo oculto |
| Sombras | shadow-md, shadow-indigo-500/50 | shadow-sm de v4 es shadow de v3 |
| Opacidad de sombra | shadow-lg/50 (4.1) | Sustituye la opacidad, no la reduce |
Capas de box-shadow | inset-shadow-sm, inset-ring (4.0) | Capas distintas se suman; la misma, se pisa |
| Opacidad | opacity-50 frente a bg-white/50 | La primera afecta a todo el elemento |
| Mezcla | mix-blend-multiply, bg-blend-luminosity, isolate | isolate limita con qué se mezcla |
| Filtros | blur-sm, grayscale, brightness-75 | Se combinan en un orden fijo |
| Sombra con forma | drop-shadow-lg, drop-shadow-indigo-500 (4.1) | Sigue la transparencia; md, lg y xl cambian de valor en v4 |
| Cristal | backdrop-blur-md con bg-white/30 | Necesita fondo translúcido; cuidado con los antepasados |
| Efectos secundarios | isolate, opacity, filter, máscaras | Atrapan el z-index; filter también atrapa a fixed |
| Máscaras | mask-b-from-50%, mask-radial-from-40% (4.1) | Se intersecan; from y to son las paradas |
| Zoom | zoom-125 (4.3) | Recalcula el diseño, al contrario que scale-* |
| Renombrados | -sm pasa a -xs; sin sufijo pasa a -sm | -sm es el falso amigo |
| Valores propios | rounded-(--radio), --radius-*, --shadow-* | En v3, corchetes con pista de tipo y tailwind.config.js |