Módulo 02: Fundamentos: utilidades y Preflight
Objetivos del módulo
- Entender qué problema resuelve el enfoque utility-first y qué precio se paga por él
- Leer cualquier clase de Tailwind separando variante, utilidad, valor, modificador e importancia
- Saber cómo se genera el CSS: qué lee el escáner, qué descarta el compilador y por qué
bg-${color}-500no funciona - Dominar las cuatro capas
@layerde Tailwind y predecir quién gana en la cascada - Conocer Preflight regla a regla, extenderlo con
@layer basey desactivarlo cuando convenga - Usar las variables del tema en tu propio CSS y saber cuáles llegan a la hoja final
- Trabajar con la escala de espaciado
--spacingy con los valores dinámicos - Escribir valores arbitrarios, propiedades arbitrarias y variables con la sintaxis correcta
- Distinguir un modificador de una variante y usar
/50o/7con criterio - Forzar una clase con
!y conocer sus alternativas - Explicar por qué el orden de las clases en el HTML no decide nada, y detectar los conflictos
- Hacer convivir Tailwind con otro CSS mediante un prefijo
- Diagnosticar una clase que no se aplica
1. Preparación del laboratorio
Este módulo se apoya en el proyecto Vite que montaste en el módulo 01. Solo necesitas un index.html y un app.css con esta línea:
@import "tailwindcss";
Arranca el servidor de desarrollo con npm run dev y deja abierto el navegador. Cada vez que el módulo diga “crea una página”, añade un .html a la raíz del proyecto y ábrelo con su ruta (http://localhost:5173/capas.html).
⚠️ Importante: Vite no detecta siempre los ficheros HTML creados con el servidor ya en marcha. Si una clase nueva no aparece, guarda
app.csssin cambiar nada: eso obliga a Tailwind a volver a escanear el proyecto.
Para que las figuras sean legibles, los ejemplos usan clases que aún no has estudiado. Con esta tabla te basta por ahora:
| Clases | Qué hacen | Dónde se estudian |
|---|---|---|
p-4, mt-2, px-4, w-32, max-w-xs | Relleno, margen y tamaño | Espaciado y tamaño |
text-sm, font-bold, uppercase, tracking-wider | Texto | Tipografía |
bg-indigo-600, text-gray-900 | Color | Color y fondos |
border, rounded-lg | Bordes | Bordes, sombras y efectos |
flex, grid, grid-cols-3, gap-4, items-center | Colocación | Flexbox y Grid |
hover:, md:, dark: | Aplican una clase en una condición | Variantes y estados y Diseño responsive |
Si necesitas repasar la cascada, la especificidad o las variables CSS, tienes el Curso de HTML y CSS.
2. La filosofía: por qué utilidades
El problema que intenta resolver
La forma clásica de escribir CSS es inventar un nombre que describa qué es un elemento y dar los estilos en otro fichero:
<article class="tarjeta-producto">
<p class="tarjeta-producto__categoria">Portátiles</p>
<h2 class="tarjeta-producto__nombre">Portátil UltraBook 14</h2>
<p class="tarjeta-producto__descripcion">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<p class="tarjeta-producto__precio">899 €</p>
</article>
.tarjeta-producto {
max-width: 20rem;
padding: 1.5rem;
border-radius: 0.75rem;
background-color: #fff;
border: 1px solid #e5e7eb;
}
.tarjeta-producto__categoria {
font-size: 0.75rem;
line-height: 1rem;
font-weight: 600;
color: #4f46e5;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.tarjeta-producto__nombre {
margin-top: 0.25rem;
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 700;
color: #111827;
}
.tarjeta-producto__descripcion {
margin-top: 0.5rem;
font-size: 0.875rem;
line-height: 1.25rem;
color: #4b5563;
}
.tarjeta-producto__precio {
margin-top: 1rem;
font-size: 1.5rem;
line-height: 2rem;
font-weight: 700;
color: #111827;
}
Es un buen CSS. En un proyecto pequeño funciona muy bien. Los problemas aparecen cuando el proyecto crece y lo tocan varias personas durante años:
- Los nombres cuestan. Hay que decidir si esto es una
tarjeta-producto, unproductoo unitem-catalogo, y qué hacer cuando aparece una tarjeta casi igual para los empleados. - El CSS solo crece. Antes de borrar una regla hay que estar seguro de que nadie la usa, cosa difícil de comprobar. Por eso casi nadie borra, y la hoja engorda con cada función nueva.
- Los cambios asustan. Modificar
.tarjeta-productoafecta a todas las páginas que la usan, incluidas las que no estás mirando. - Los valores se dispersan. Sin disciplina acabas con
#4f46e5,#4f47e6y#5046e4, o con márgenes de13px,14pxy15px. - El contexto se pierde. Para saber cómo se ve un elemento hay que saltar entre dos ficheros.
La misma tarjeta con utilidades
Utility-first significa construir la interfaz componiendo clases pequeñas, cada una con un solo propósito:
<article class="max-w-xs rounded-xl border border-gray-200 bg-white p-6">
<p class="text-xs font-semibold tracking-wider text-indigo-600 uppercase">Portátiles</p>
<h2 class="mt-1 text-xl font-bold text-gray-900">Portátil UltraBook 14</h2>
<p class="mt-2 text-sm text-gray-600">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<p class="mt-4 text-2xl font-bold text-gray-900">899 €</p>
</article>
No hay CSS propio. El resultado es prácticamente el mismo:

Figura 1. La misma tarjeta escrita de las dos formas. Las diferencias de color son de un tono: la de la izquierda usa hexadecimales, la de la derecha la paleta oklch de Tailwind.
📘 Concepto: Una clase de utilidad hace una sola cosa y su nombre describe qué aspecto da, no qué es el elemento.
text-gray-900no dice “esto es un precio”, dice “este texto es gris muy oscuro”. Una clase semántica como.tarjeta-producto__preciohace lo contrario.
Qué se gana
- No hay que inventar nombres para cada
div. - El CSS deja de crecer con el proyecto. Una página nueva reutiliza
p-4ytext-sm, que ya están en la hoja. Lo que crece es el HTML, y cuando borras un HTML sus clases desaparecen solas del CSS si nadie más las usa. - Los cambios son locales. Tocar las clases de este botón no rompe ningún otro botón.
- El diseño tiene restricciones.
p-4yp-5son valores de una escala, no una cifra cualquiera. Los colores salen de una paleta. Un equipo entero acaba usando los mismos valores sin esfuerzo. - Todo está a la vista. Lees el HTML y sabes cómo se ve el elemento.
Qué se paga
Tailwind recibe críticas razonables y conviene conocerlas:
| Crítica | Lo que tiene de verdad | Cómo se mitiga |
|---|---|---|
| “El HTML queda ilegible” | Es cierto: una tarjeta real puede tener 15 clases en un elemento | Extraer componentes (React, Angular, Vue…) y ordenar las clases con Prettier |
| “Repites las mismas clases” | También: si copias el botón 20 veces en HTML puro, repites 20 veces | Un componente o un bucle. Se ve en Reutilización y componentes |
| “Mezcla contenido y presentación” | Sí, desde el punto de vista del HTML | En la práctica, cambiar el aspecto de un sitio casi siempre implica tocar también su estructura |
| “Hay que aprender otro vocabulario” | Sí, aunque mt-4 es margin-top y no otra cosa | Saber CSS es requisito: Tailwind no lo sustituye |
| “Con el CSS del navegador ya puedo hacer todo” | Cierto: Tailwind no da nada que CSS no pueda hacer | Lo que aporta es organización y restricciones, no capacidades nuevas |
💡 Consejo: Utility-first no significa utility-only. Si un patrón se repite y no tienes un framework de componentes, escribir
.botonen@layer componentses correcto. Tailwind da el sitio exacto para hacerlo, como verás en la sección 5.
“Pero esto es lo mismo que style=""”
Es la primera objeción de casi todo el mundo, y es falsa por cuatro motivos:
style="..." | Clases de utilidad | |
|---|---|---|
| Valores | Cualquiera: padding: 13px | Los de la escala y el tema |
Estados (:hover, :focus) | Imposible | hover:bg-indigo-700 |
Responsive (@media) | Imposible | md:p-8 |
| Especificidad | Muy alta: difícil de sobrescribir | Una sola clase, sobrescribible |
| Tamaño | Se repite el texto completo en cada elemento | Cada regla está una sola vez en el CSS |
Una utilidad se parece a un estilo en línea en dónde se escribe, no en lo que hace.
3. Anatomía de una clase
Toda clase de Tailwind tiene una gramática. Si la aprendes, puedes leer cualquiera aunque nunca la hayas visto:
Figura 2. Las cinco partes de una clase. Solo la utilidad es obligatoria.
| Parte | Ejemplo | Qué hace | Sección |
|---|---|---|---|
| Variantes | md:, hover: | Condición en la que se aplica | Módulos 10 y 11 |
| Utilidad | bg-, p-, flex | Qué propiedad CSS se toca | Sección 3 |
| Valor | indigo-600, 4 | Con qué valor | Secciones 7 a 10 |
| Modificador | /50 | Ajusta el valor | Sección 11 |
| Importante | ! | Añade !important | Sección 12 |
Utilidades estáticas y funcionales
Hay dos tipos de utilidad:
- Estáticas: nombre fijo, sin valor.
flex,hidden,uppercase,truncate. - Funcionales: un nombre y un valor.
p-4,bg-red-500,rounded-lg.
/* Estática */
.flex {
display: flex;
}
/* Funcional: el 4 entra en la cuenta */
.p-4 {
padding: calc(var(--spacing) * 4);
}
Una clase, una idea
Casi siempre una clase genera una sola declaración CSS. Hay excepciones cuando una idea necesita varias, como truncate (tres propiedades) o text-lg (tamaño y altura de línea):
.text-lg {
font-size: var(--text-lg);
line-height: var(--tw-leading, var(--text-lg--line-height));
}
Fíjate en var(--tw-leading, ...): text-lg trae su altura de línea, pero si otra clase define --tw-leading, se usa esa. Por eso text-lg leading-8 funciona. Tailwind se apoya mucho en estas variables internas --tw-* para que las clases colaboren entre sí.
Cómo adivinar el nombre
Los nombres son abreviaturas muy regulares. Con este patrón aciertas la mayoría:
| Patrón | Significado | Ejemplos |
|---|---|---|
m, p | margin, padding | m-4, p-2 |
t, r, b, l | Lados físicos: arriba, derecha, abajo, izquierda | mt-4, pl-2, border-b |
x, y | Eje horizontal, eje vertical | px-4, my-2 |
s, e | Lados lógicos: inicio y fin de línea | ms-2, pe-4 |
w, h, size | Ancho, alto, ambos | w-32, h-8, size-4 |
bg, text, border | Fondo, texto, borde | bg-sky-300 |
⚠️ Importante:
text-es una utilidad con varias caras.text-lgda tamaño,text-red-500da color ytext-centeralinea. Tailwind deduce la propiedad por el tipo del valor. Por esotext-lg text-red-500no es un conflicto: son propiedades distintas.
Valores negativos
Los márgenes y algunas propiedades admiten un guion delante del nombre. Si hay variantes, el guion va después de ellas:
<div class="-mt-4">Sube 1 rem</div>
<div class="md:-mt-4">Sube 1 rem desde el punto de ruptura md</div>
.-mt-4 {
margin-top: calc(var(--spacing) * -4);
}
No todas las utilidades lo admiten: el relleno (padding) no puede ser negativo en CSS, así que -p-4 no existe.
4. Cómo se genera el CSS 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) hay que decirle al compilador dónde mirar. Si usas la LTS: declara
content: ['./src/**/*.{html,js}']entailwind.config.js; en v4 la detección es automática y solo se ajusta con@source.
Una idea central: Tailwind no lleva en el navegador ninguna lista de clases. La hoja final solo contiene lo que tu proyecto usa, y se fabrica antes de que el navegador vea nada.
Figura 3. El recorrido de una clase desde tu HTML hasta la hoja final.
Paso a paso
- Tus ficheros. Todo parte de la línea
@import "tailwindcss". Desde ahí, Tailwind recorre el proyecto saltándose lo que ignora Git,node_modulesy los binarios. Lo viste en el módulo 01. - Escáner. El motor Oxide, escrito en Rust, lee cada fichero como texto plano. No entiende HTML ni JavaScript: busca cadenas que podrían ser clases. Se llaman candidatos.
- Compilador. Cada candidato se analiza con la gramática de la sección 3. Si encaja, se genera su regla. Si no (
hola,Mesa), se descarta en silencio. - Ordenación. Las reglas se colocan en un orden fijo dentro de
@layer utilities, y se añaden solo las variables del tema que se usan. - Lightning CSS. Convierte la sintaxis moderna para los navegadores objetivo, añade prefijos y, al compilar para producción, minifica.
Lo que produce para class="flex p-4 bg-indigo-600", sin Preflight para abreviar:
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--color-indigo-600: oklch(51.1% 0.262 276.966);
--spacing: 0.25rem;
}
}
@layer utilities {
.flex {
display: flex;
}
.bg-indigo-600 {
background-color: var(--color-indigo-600);
}
.p-4 {
padding: calc(var(--spacing) * 4);
}
}
Observa tres cosas:
- Solo están
--spacingy--color-indigo-600, no los casi trescientos colores del tema. - El orden no es el del atributo
class. La sección 13 lo explica. - Cada clase aparece una vez aunque la uses en mil elementos.
El escáner lee texto, no entiende código
Esta es la consecuencia práctica más importante del módulo. Mira este fichero:
const color = 'lime';
const a = `bg-${color}-500`;
const b = { sky: 'text-sky-400', pink: 'text-pink-400' };
// comentario con underline y border-fuchsia-700
const c = "mt-[17px]";
export { a, b, c };
Y esta página:
<p style="color: var(--color-teal-600)">table flex</p>
Compilando de verdad estos dos ficheros, esto es lo que hay en el CSS final:
| Texto en el fichero | ¿Está en el CSS? | Por qué |
|---|---|---|
`bg-${color}-500` | No | La clase bg-lime-500 no aparece escrita entera en ningún sitio |
'text-sky-400', 'text-pink-400' | Sí | Están enteras, aunque el código no las use nunca |
underline, border-fuchsia-700 | Sí | El comentario es texto como otro cualquiera |
table, flex | Sí | Son palabras del contenido de la página, pero también utilidades |
mt-[17px] | Sí | Un valor arbitrario se detecta igual que los demás |
var(--color-teal-600) | Sí, se añade la variable | El escáner también recoge nombres de variables del tema |
⚠️ Importante: Las clases deben aparecer enteras en el código fuente. Nunca construyas nombres por partes. Tailwind no ejecuta tu JavaScript, de modo que
bg-${color}-500es para él solo un trozo de texto.
La solución es escribir la clase completa en algún sitio, normalmente en un objeto:
// Mal: el escáner no ve ninguna clase bg-*
const fondo = `bg-${estado}-500`;
// Bien: todas las clases están escritas enteras
const fondos = {
disponible: 'bg-emerald-500',
agotado: 'bg-rose-500',
reservado: 'bg-amber-500',
};
const fondo = fondos[estado];
Este problema se trata a fondo en Tailwind en React, Angular y otros, junto con @source inline().
💡 Consejo: Que una palabra de tu texto genere una regla (
table,flex,hidden) no es grave: son bytes de más y la regla no se aplica a nada mientras no esté en un atributoclass. Tailwind prefiere detectar de más antes que dejarse una clase.
¿Cuánto ocupa?
Hemos compilado para producción la página del ejemplo anterior y la tienda de la sección 15:
| Página | CSS minificado | Comprimido con gzip |
|---|---|---|
| Ejemplo anterior, con unas pocas clases | 4,6 kB | 1,6 kB |
| Tienda con tres tarjetas | 17,8 kB | 4,6 kB |
La mayor parte de la primera fila es Preflight. Como la hoja depende de las clases distintas que usas, no del número de elementos, crece muy despacio. Lo medirás con más detalle en Producción y rendimiento.
Una rareza de los nombres: los escapes
Clases como w-1/2 o mt-[17px] contienen caracteres especiales en CSS. En la hoja aparecen escapados:
.w-1\/2 {
width: calc(1 / 2 * 100%);
}
.mt-\[17px\] {
margin-top: 17px;
}
No tienes que hacer nada en el HTML: ahí se escriben tal cual. Solo lo notarás si las buscas desde JavaScript, porque document.querySelector('.w-1/2') falla. Usa CSS.escape('w-1/2').
5. Las capas @layer 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4)
@layeres una directiva de Tailwind que desaparece al compilar, y el orden depende de la posición en el fichero. Si usas la LTS: escribe@tailwind base; @tailwind components; @tailwind utilities;y coloca tu CSS sin capa antes de esas líneas si quieres que las utilidades lo sobrescriban.
Qué es una capa de cascada
@layer es una función del propio CSS. Permite agrupar reglas y fijar el orden de prioridad entre grupos. Lo decisivo es esto:
📘 Concepto: Entre dos capas distintas, la especificidad no cuenta. Gana la regla de la capa que se declaró más tarde, aunque su selector sea mucho más débil.
Es lo que resuelve la vieja guerra de especificidad. Un selector article#oferta.tarjeta.destacada dentro de una capa pierde frente a .bg-sky-300 si esa clase está en una capa posterior.
Las capas de Tailwind
@import "tailwindcss" es un atajo. Equivale a esto:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);
La primera línea fija el orden: de menos a más prioridad.
| Capa | Contenido | Quién escribe en ella |
|---|---|---|
theme | Variables: --color-*, --spacing… | Tailwind, a partir de @theme |
base | Preflight | Tailwind y tú, con estilos para etiquetas (h1, a) |
components | Vacía por defecto | Tú, con clases como .boton o .tarjeta |
utilities | Las clases generadas | Tailwind |
Figura 4. Orden de prioridad. Una utilidad vence a cualquier selector de components o base.
La trampa: el CSS sin capa
Una regla fuera de toda capa gana a todas las capas. Es una regla del estándar CSS, no algo de Tailwind, y sorprende porque invierte la intuición de que “las utilidades siempre ganan”.
Vamos a comprobarlo. Crea app-capas.css:
@import "tailwindcss";
@layer base {
h1 { color: var(--color-red-600); }
}
@layer components {
.tarjeta-capa { background-color: var(--color-amber-200); }
}
.caja-sin-capa { background-color: var(--color-rose-300); }
/* Selector muy específico, pero dentro de una capa */
@layer components {
article#oferta.tarjeta-capa.destacada { background-color: var(--color-amber-400); }
}
Y capas.html:
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Capas</title>
<link rel="stylesheet" href="/app-capas.css">
</head>
<body class="bg-white p-6">
<div class="grid grid-cols-2 gap-4 font-mono text-sm">
<div class="tarjeta-capa rounded-lg p-4">.tarjeta-capa (components)<br>sin utilidad: ámbar</div>
<div class="tarjeta-capa rounded-lg bg-sky-300 p-4">.tarjeta-capa + bg-sky-300<br>gana la utilidad</div>
<article id="oferta" class="tarjeta-capa destacada rounded-lg bg-sky-300 p-4">selector con id (components) + bg-sky-300<br>sigue ganando la utilidad</article>
<div class="caja-sin-capa rounded-lg bg-sky-300 p-4">.caja-sin-capa (sin capa) + bg-sky-300<br>gana el CSS sin capa</div>
<div class="caja-sin-capa rounded-lg bg-sky-300! p-4">.caja-sin-capa + bg-sky-300!<br>!important invierte: gana la utilidad</div>
<div class="rounded-lg bg-sky-300 p-4" style="background-color: #bbf7d0">style="..." + bg-sky-300<br>gana el estilo en línea</div>
</div>
</body>
</html>

Figura 5. Seis duelos reales. El color de fondo indica qué regla ha ganado.
| Caja | Reglas en juego | Gana |
|---|---|---|
| 1 | .tarjeta-capa | Ella, porque no compite con nadie |
| 2 | .tarjeta-capa frente a bg-sky-300 | La utilidad: utilities va después de components |
| 3 | Selector con id y tres clases frente a bg-sky-300 | La utilidad: la especificidad no cuenta entre capas |
| 4 | CSS sin capa frente a bg-sky-300 | El CSS sin capa |
| 5 | CSS sin capa frente a bg-sky-300! | La utilidad con !important |
| 6 | style="..." frente a bg-sky-300 | El atributo style |
⚠️ Importante: Si importas una hoja antigua o una librería (un
normalize.css, un tema, un datepicker) sin capa, sus reglas vencerán a tus utilidades. Es el error más desconcertante de v4: la clase está en el HTML, está en el CSS y no hace nada. Importa esa hoja dentro de una capa:@import "./libreria.css" layer(components);.
Dónde va cada cosa
@import "tailwindcss";
@layer base {
/* Etiquetas del documento */
h1 { font-size: var(--text-3xl); }
}
@layer components {
/* Clases que agrupan varias decisiones */
.boton { padding: --spacing(2) --spacing(4); border-radius: var(--radius-lg); }
}
La regla es sencilla: escribe siempre dentro de una capa. Así una utilidad puntual puede ajustar tu componente: <button class="boton px-8">. Es el objetivo de todo el sistema. La sintaxis --spacing(2) se explica en la sección 7. Ten en cuenta que @utility, la forma de crear tus propias utilidades, se verá en Utilidades y plugins propios.
Qué cambia en la LTS
En v3 la directiva @layer existe, pero no llega al navegador. Este CSS:
.antes { color: red; }
@tailwind components;
@tailwind utilities;
@layer components {
.usada { padding: 1rem; }
.sinusar { margin: 1rem; }
}
.despues { color: blue; }
Con <div class="usada p-2 text-center"> produce en 3.4.19:
.antes{color:red}.usada{padding:1rem}.p-2{padding:0.5rem}.text-center{text-align:center}.despues{color:blue}
De aquí salen dos diferencias prácticas con v4:
| Tailwind 3.4 | Tailwind 4.3 | |
|---|---|---|
@layer | Se resuelve al compilar | Capa real de CSS, llega al navegador |
| CSS sin capa frente a una utilidad | Depende de la posición en el fichero y de la especificidad: .antes pierde, .despues gana | El CSS sin capa gana siempre |
Selector fuerte en components frente a una utilidad | Gana el selector fuerte | Gana la utilidad |
Clases de @layer components que nadie usa | Se eliminan (.sinusar no está) | Se conservan: es CSS normal y va a la hoja |
6. Preflight a fondo
Preflight es la hoja de reinicio de Tailwind. Se carga en la capa base y elimina las diferencias entre navegadores y casi todos los estilos por defecto. Se basa en modern-normalize, con añadidos propios.
Compara un mismo documento HTML, con cero clases, sin Preflight y con él:
<h1>Tienda EMPRESA</h1>
<h2>Portátiles</h2>
<p>Nuestros equipos más vendidos, con <a href="#">envío gratuito</a> y <strong>dos años</strong> de garantía.</p>
<ul>
<li>Portátil UltraBook 14</li>
<li>Portátil Gaming 16</li>
</ul>
<ol>
<li>Elige tu equipo</li>
<li>Paga y recíbelo</li>
</ol>
<hr>
<img src="/foto.svg" alt="Portátil"> <img src="/foto.svg" alt="Portátil">
<p><button>Comprar</button> <input placeholder="Tu correo"></p>

Figura 6. El mismo HTML. Con Preflight, el navegador deja de opinar sobre el aspecto.
La primera reacción es pensar que está roto. Es intencionado: si cada h1 tuviera su tamaño por defecto, tendrías que anularlo cada vez. Con Preflight, un elemento solo tiene el aspecto que le des con clases, y eso permite elegir la etiqueta por su significado y no por cómo se ve. Un <h3> puede ser enorme y un <h1>, discreto.
Las medidas reales
Medido con getComputedStyle en Chromium:
| Elemento | Propiedad | Sin Preflight | Con Preflight |
|---|---|---|---|
h1 | font-size | 32 px | 16 px |
h1 | font-weight | 700 | 400 |
h1 | margin | 21,44 px arriba y abajo | 0 |
ul | margin | 16 px arriba y abajo | 0 |
ul | list-style-type | disc | none |
img | display | inline | block |
button | font-size | 13,33 px | 16 px |
Regla a regla
Estas son las reglas de Tailwind 4.3.3 que más te afectan. El fichero completo tiene unas 400 líneas y lo tienes en node_modules/tailwindcss/preflight.css. Es muy legible, con comentarios que explican cada línea.
Caja universal
*,
::after,
::before,
::backdrop,
::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
box-sizing: border-box:w-32mide 8 rem incluyendo relleno y borde, algo mucho más predecible.margin: 0ypadding: 0: ningún elemento trae espacios de serie.border: 0 solid: el borde tiene ancho 0 pero estilosolid. Por eso la clasebordersolo necesita dar el ancho.
Documento
html,
:host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: --theme(--default-font-family, ...);
font-feature-settings: --theme(--default-font-feature-settings, normal);
font-variation-settings: --theme(--default-font-variation-settings, normal);
-webkit-tap-highlight-color: transparent;
}
Fija una altura de línea legible y la familia tipográfica del tema. --theme() lee una variable del tema con un valor por defecto: si cambias --default-font-family, cambias la fuente de todo el documento.
Títulos, enlaces y listas
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
ol, ul, menu {
list-style: none;
}
Los títulos heredan el tamaño y el peso del padre, los enlaces no son azules ni subrayados y las listas no llevan viñetas.
⚠️ Importante: Los enlaces de la figura 6 no se distinguen del texto. Si no les pones estilo, el usuario no sabrá que son enlaces. Es un olvido de accesibilidad muy frecuente con Tailwind.
💡 Consejo: Safari con VoiceOver deja de anunciar como lista un
<ul>conlist-style: none. Si es una lista de contenido real, añaderole="list"al elemento.
Las viñetas se recuperan con list-disc y list-inside, como verás en el módulo de Tipografía.
Imágenes y contenido reemplazado
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
display: block evita el pequeño hueco bajo las imágenes en línea, pero tiene un efecto que sorprende: en la figura 6, las dos imágenes del HTML ya no van seguidas, sino una debajo de otra. Si las quieres en línea, usa inline-block o colócalas con flex.
max-width: 100% y height: auto hacen que las imágenes nunca se salgan de su contenedor.
Controles de formulario
button,
input,
select,
optgroup,
textarea,
::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
Un <button> sin clases deja de parecer un botón: no tiene fondo, ni borde, ni relleno. En la figura 6 es solo la palabra “Comprar”. También un <input> pierde su borde, así que el campo es invisible hasta que le das border. Los formularios se tratan en Formularios y tablas.
El atributo hidden
[hidden]:where(:not([hidden='until-found'])) {
display: none !important;
}
Cualquier elemento con el atributo hidden queda oculto incluso si tiene class="flex". Es útil porque en el CSS normal display: flex anularía el hidden.
Diferencias con Preflight de v3 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) el borde es gris claro, el placeholder es gris 400 y los botones llevan
cursor: pointer; v4 cambia los tres sin renombrar nada. Si usas la LTS: no tienes que hacer nada; al pasar a v4, indícalos con clases o en@layer base.
| Elemento | Tailwind 3.4 | Tailwind 4.3 |
|---|---|---|
Color de border sin más clases | gray-200 | currentColor |
| Color del placeholder | gray-400 | Color del texto al 50 % |
Cursor de <button> | pointer | default, el del navegador |
Medido con Chromium en v4: el cursor de un botón es default y el color del placeholder sale como oklab(0 0 0 / 0.5), negro al 50 %.
Los tres cambios tienen un porqué. El borde gris obligaba a anularlo muchas veces. Y el navegador usa default en los botones, de modo que v4 se alinea con él. Si quieres el comportamiento de v3 en un proyecto de v4:
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
::placeholder {
color: var(--color-gray-400);
}
button:not(:disabled) {
cursor: pointer;
}
}
Ampliar Preflight con @layer base
Si todos tus títulos o enlaces van a tener el mismo aspecto, no repitas clases: añade una regla base. Es lo correcto para estilos de etiquetas:
@import "tailwindcss";
@layer base {
h1 {
font-size: var(--text-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-gray-900);
}
a {
color: var(--color-marca-600);
text-decoration: underline;
text-underline-offset: 2px;
}
}
⚠️ Importante: Escríbelo dentro de
@layer base. Si dejash1 { font-size: ... }sin capa, ganará a tus utilidades y<h1 class="text-4xl">no surtirá efecto, por lo que has visto en la sección 5.
Desactivar Preflight 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) Preflight se desactiva en la configuración; en v4
corePluginsya no existe y basta con no importarlo. Si usas la LTS: añadecorePlugins: { preflight: false }atailwind.config.js.
Cuando añades Tailwind a un sitio que ya existe, Preflight puede romper lo anterior. Sustituye @import "tailwindcss" por sus piezas, sin la de Preflight:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);
Este es exactamente el fichero que genera la columna izquierda de la figura 6.
⚠️ Importante: Sin Preflight, las medidas cambian. Vuelve
box-sizing: content-box, yw-32 p-4 bordermide 162 px en lugar de 128 (128 + 32 de relleno + 2 de borde). Añadebox-borderdonde lo necesites o una regla propia conbox-sizing: border-box. El borde sigue dibujándose en v4, porquebordertrae su propio estilo mediante--tw-border-style; en la LTS no, porque allí solo da el ancho y hace faltaborder-solid.
No se puede quitar una sola regla de Preflight. Si una te molesta, sobrescríbela en @layer base, que se carga después.
7. Las variables del tema 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) el tema es un objeto JavaScript y no genera variables CSS. Si usas la LTS: define los valores en
theme.extenddetailwind.config.jsy léelos en tu CSS contheme(colors.red.500), que se sustituye por el valor al compilar.
En v4 el tema vive en CSS. El fichero node_modules/tailwindcss/theme.css tiene poco más de 500 líneas y empieza así:
@theme default {
--font-sans:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
"Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol", "Noto Color Emoji";
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
--font-mono:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
--color-red-50: oklch(97.1% 0.013 17.38);
--color-red-100: oklch(93.6% 0.032 17.717);
/* ... */
}
@theme no es solo una lista de variables. Cada variable que declara crea utilidades:
@import "tailwindcss";
@theme {
--color-marca: oklch(0.55 0.2 270);
}
Con esto, ya existen bg-marca, text-marca, border-marca y todas las demás utilidades de color. Y aparece la variable:
@layer theme {
:root, :host {
--color-marca: oklch(0.55 0.2 270);
}
}
@layer utilities {
.bg-marca {
background-color: var(--color-marca);
}
}
Los espacios de nombres
El prefijo de la variable decide a qué utilidades alimenta:
| Variable | Utilidades que alimenta | Ejemplo |
|---|---|---|
--color-* | bg-, text-, border-, ring-… | --color-red-500 → bg-red-500 |
--font-* | font- (familia) | --font-mono → font-mono |
--text-* | text- (tamaño) | --text-lg → text-lg |
--font-weight-* | font- (peso) | --font-weight-bold → font-bold |
--tracking-* | tracking- | --tracking-wide |
--leading-* | leading- | --leading-loose |
--breakpoint-* | Variantes sm:, md:… | --breakpoint-md: 48rem |
--container-* | max-w-, @md:… | --container-xs → max-w-xs |
--spacing | p-, m-, w-, gap-… | Sección 8 |
--radius-* | rounded- | --radius-lg |
--shadow-* | shadow- | --shadow-md |
--blur-* | blur- | --blur-sm |
--ease-*, --animate-* | ease-, animate- | --animate-spin |
Personalizarlos a fondo es el tema de Personalizar el tema. Aquí basta con saber que existen y cómo leerlos.
@theme frente a :root
Parecen lo mismo, pero no lo son. Una variable en :root es una variable CSS normal y no crea utilidades:
:root {
--color-foo: red;
}
Si escribes bg-foo no ocurre nada: la clase no se genera. Usa @theme cuando quieras una clase, y :root para valores auxiliares que solo vaya a leer tu CSS.
Solo llegan las variables usadas
Tailwind quita del resultado las variables que nadie usa. Esto incluye las que usas tú a mano:
@import "tailwindcss";
.etiqueta {
color: var(--color-red-500);
}
/* Salida */
:root, :host {
--color-red-500: oklch(63.7% 0.237 25.331);
}
.etiqueta {
color: var(--color-red-500);
}
También detecta los nombres en HTML y JavaScript, como viste en la sección 4 con style="color: var(--color-teal-600)". Si construyes el nombre de la variable por partes (`--color-${tono}`) no la verá, igual que con las clases. Para esos casos, @theme static emite todas las variables del bloque aunque nadie las use:
@theme static {
--color-marca: red;
--color-otra: blue;
}
Leer el tema desde tu CSS
Tienes tres formas, y las tres están comprobadas en 4.3.3. Lo más natural es var(). Las funciones --spacing(), --alpha() y --theme() cubren los casos en que var() no basta:
@import "tailwindcss";
@theme {
--color-marca: oklch(0.55 0.2 270);
}
.x {
color: var(--color-marca);
margin: --spacing(4);
padding: --theme(--spacing);
background: --alpha(var(--color-marca) / 50%);
}
Salida:
.x {
color: var(--color-marca);
margin: calc(var(--spacing) * 4);
padding: var(--spacing);
background: color-mix(in srgb, oklch(0.55 0.2 270) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background: color-mix(in oklab, var(--color-marca) 50%, transparent);
}
}
| Función | Uso | Equivalente en 3.4 |
|---|---|---|
var(--color-red-500) | Leer un valor en tiempo de ejecución | theme(colors.red.500) |
--spacing(4) | Un múltiplo de la escala | theme(spacing.4) |
--alpha(color / 50%) | Un color con opacidad | theme(colors.red.500 / 50%) |
--theme(--color-red-500) | Un valor del tema | theme(colors.red.500) |
La función theme() de v3 sigue funcionando en v4, pero conserva la sintaxis antigua y produce el valor literal, sin var(). En código nuevo, prefiere las formas de arriba.
📘 Concepto: Al ser variables CSS reales, los valores del tema se pueden cambiar en el navegador: dentro de un selector, en un
style, con JavaScript. En v3 se resolvían al compilar y desaparecían. Esto abre la puerta a los temas múltiples del módulo de Modo oscuro.
8. La escala de espaciado: --spacing 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) la escala es una lista cerrada de valores en
rem, escritos en el CSS final. Si usas la LTS: para cambiarla, redefinetheme.spacingo amplíala contheme.extend.spacingentailwind.config.js; no puedes cambiarla en tiempo de ejecución.
Casi todas las medidas de Tailwind salen de una sola variable:
@theme default {
--spacing: 0.25rem;
}
Con el tamaño de letra por defecto, 0.25rem son 4 px. Las utilidades multiplican esa unidad:
.p-4 {
padding: calc(var(--spacing) * 4);
}
Esta fórmula es la misma en p-, m-, w-, h-, gap-, size-, inset-, leading- y otras familias:
| Clase | Multiplicador | rem | px |
|---|---|---|---|
p-0 | 0 | 0 | 0 |
p-0.5 | 0,5 | 0,125 | 2 |
p-1 | 1 | 0,25 | 4 |
p-2 | 2 | 0,5 | 8 |
p-4 | 4 | 1 | 16 |
p-6 | 6 | 1,5 | 24 |
p-8 | 8 | 2 | 32 |
p-12 | 12 | 3 | 48 |
p-16 | 16 | 4 | 64 |
p-px | No usa la escala | 1 px exacto |
💡 Consejo: La cuenta mental es sencilla: el número de la clase por 4 da los píxeles.
mt-6son 24 px yw-32son 128 px.
Dos detalles de la salida a partir de 4.3.1. El multiplicador 0 da un valor fijo, sin variable, y el multiplicador 1 no necesita calc():
.m-0 {
margin: 0px;
}
.m-1 {
margin: var(--spacing);
}
Por qué rem
Si un usuario sube el tamaño de letra del navegador, todo lo medido en rem crece con él, incluidos márgenes y rellenos. La página conserva sus proporciones. Si hubieras escrito padding: 16px, el texto crecería, pero el espacio no.
Cambiar la escala de todo un proyecto
Cambia una variable y se reajusta toda la interfaz:
@theme {
--spacing: 0.3rem;
}
La salida solo cambia en la variable. Las utilidades siguen siendo iguales:
:root, :host {
--spacing: 0.3rem;
}
.p-2 {
padding: calc(var(--spacing) * 2);
}
Cambiarla en una zona
Como es una variable real, también puedes redefinirla en un subárbol. Esta sección usa una escala mayor sin tocar ninguna clase:
<section style="--spacing: 0.35rem">
<article class="mt-5 max-w-64 rounded-lg border border-slate-300 p-4">
<h3 class="font-bold">Torre Oficina i5</h3>
<p class="mt-2 text-sm text-slate-600">Ordenador de sobremesa</p>
<button class="mt-4 rounded-md bg-orange-600 px-4 py-2 text-sm text-white">Añadir</button>
</article>
</section>

Figura 7. Mismas clases, dos valores de --spacing. Las barras miden 192 px (w-48) a la izquierda y 268,8 px a la derecha, y la tarjeta crece también. El tamaño de letra no cambia, porque no depende de esta variable.
Es un recurso útil para una vista “compacta” en una tabla o un panel.
⚠️ Importante:
--spacingafecta también a lo que no parece espaciado.max-w-64,size-4,leading-6ytext-base/7dependen de ella. Pruébalo en toda la interfaz antes de cambiar el valor global.
Valores con nombre
Una variable --spacing-<nombre> crea un valor propio para todas estas utilidades:
@theme {
--spacing-huge: 5rem;
}
.p-huge {
padding: var(--spacing-huge);
}
Y si quieres un sistema cerrado, --spacing-*: initial elimina la escala: p-4 y w-4 dejan de generarse. Se estudia en Personalizar el tema.
Escala en v3
En 3.4.19, p-4 genera directamente el valor:
.p-4 {
padding: 1rem;
}
Y un valor nuevo se añade en la configuración:
// tailwind.config.js
export default {
content: ['./*.html'],
theme: {
extend: {
spacing: { 18: '4.5rem' },
},
},
};
Comprobado en 3.4.19: genera .mt-18 { margin-top: 4.5rem }.
9. Valores dinámicos 🆕 v4.0
🆕 Desde Tailwind 4.0. No existe en la LTS (Tailwind 3.4): cada valor debía estar en una lista, y
mt-17no genera nada. Si usas la LTS: escribe un valor arbitrario, comomt-[4.25rem], o añade el valor entheme.extend.
En v3, el compilador consultaba una lista: si 17 no estaba en theme.spacing, no había clase. En v4 muchas utilidades son funciones: reciben un número, comprueban que es válido y hacen la cuenta. Ya no hay lista.
.mt-17 {
margin-top: calc(var(--spacing) * 17);
}
.grid-cols-15 {
grid-template-columns: repeat(15, minmax(0, 1fr));
}
.w-3\/7 {
width: calc(3 / 7 * 100%);
}
.opacity-37 {
opacity: 37%;
}
.z-37 {
z-index: 37;
}
Todo lo que sigue se ha pasado por el comprobador de clases en ambas versiones:
| Clase en v4 | ¿Existe en 3.4? | Equivalente en 3.4 |
|---|---|---|
mt-17 | No | mt-[4.25rem] |
mt-1.25 | No | mt-[0.3125rem] |
w-13 | No | w-[3.25rem] |
grid-cols-15 | No | grid-cols-[repeat(15,minmax(0,1fr))] |
w-3/7 | No | w-[42.857143%] |
opacity-37 | No | opacity-[.37] |
z-37 | No | z-[37] |
rotate-37 | No | rotate-[37deg] |
duration-137 | No | duration-[137ms] |
scale-117 | No | scale-[1.17] |
border-3 | No | border-[3px] |
aspect-4/3 | No | aspect-[4/3] |
bg-red-500/37 | No | bg-red-500/[.37] |
Los límites
Dinámico no significa cualquier cosa:
- Solo múltiplos de 0,25.
mt-1.25ymt-1.75existen;mt-1.3no genera nada, ni en v4. - No todas las utilidades.
text-17oshadow-17no tienen sentido: su familia usa nombres (text-lg,shadow-md), no números. - Sigue sin haber aviso. Si te equivocas, la clase simplemente no existe.
💡 Consejo: Que
mt-17funcione no significa que debas usarlo. La escala existe para que los valores sean coherentes. Si un diseño llevamt-16,mt-17ymt-18, probablemente sean el mismo margen mal medido.
10. Valores arbitrarios
Cuando un valor no está en el tema, lo escribes entre corchetes. Tailwind lo copia al CSS:
<div class="rounded-[10px] bg-[#0f766e] p-3 text-[15px] text-white">
Color y tamaños de una marca externa
</div>
.rounded-\[10px\] {
border-radius: 10px;
}
.bg-\[\#0f766e\] {
background-color: #0f766e;
}
.text-\[15px\] {
font-size: 15px;
}
Esto funciona igual en v3 y v4. La figura resume todas las formas que estudiaremos:

Figura 8. Cada ejemplo muestra su clase encima. La caja de bg-[--marca] no tiene fondo: esa es la sintaxis de v3.
Espacios: el guion bajo
Una clase no puede contener espacios, porque el espacio separa clases. Se escribe _ y Tailwind lo convierte:
<div class="grid grid-cols-[120px_1fr]">...</div>
.grid-cols-\[120px_1fr\] {
grid-template-columns: 120px 1fr;
}
Dentro de url(), el guion bajo se conserva, porque es normal en las rutas:
.bg-\[url\(\/img\/a_b\.png\)\] {
background-image: url(/img/a_b.png);
}
Las operaciones de calc() no necesitan guiones bajos: Tailwind añade los espacios que exige CSS.
<div class="w-[calc(100%-3rem)]">Ancho calculado</div>
.w-\[calc\(100\%-3rem\)\] {
width: calc(100% - 3rem);
}
Variables: () frente a [] 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) la forma corta para variables usa corchetes; en v4 usa paréntesis, y la antigua compila sin error, pero genera CSS no válido. Si usas la LTS: escribe
bg-[--marca], que allí producevar(--marca), donde v4 escribebg-(--marca).
Es uno de los falsos amigos del curso. La misma clase compila en las dos versiones y no avisa:
| Clase | CSS en 3.4.19 | CSS en 4.3.3 |
|---|---|---|
bg-[--marca] | background-color: var(--marca) | background-color: --marca (no válido) |
bg-(--marca) | No existe | background-color: var(--marca) |
bg-[var(--marca)] | background-color: var(--marca) | background-color: var(--marca) |
Medido en Chromium: con bg-[--marca] en v4 el fondo es rgba(0, 0, 0, 0), transparente.
<div style="--marca: #7c3aed; --relleno: 1.1rem">
<div class="rounded bg-(--marca) p-(--relleno) text-white">Variable CSS con paréntesis</div>
</div>
💡 Consejo: Si tu código debe compilar en ambas versiones, usa
bg-[var(--marca)]. Es más largo, pero tiene el mismo significado en las dos.
Indicar el tipo
A veces Tailwind no puede deducir qué propiedad quieres. text- admite color y tamaño, y una variable no dice cuál es:
<p class="text-(length:--tamano)">Tamaño</p>
<p class="text-(color:--tono)">Color</p>
<p class="text-[length:var(--tamano)]">Tamaño, con corchetes</p>
.text-\(length\:--tamano\) {
font-size: var(--tamano);
}
.text-\(color\:--tono\) {
color: var(--tono);
}
Otra pista frecuente es image, para fondos: bg-(image:--fondo) genera background-image.
Sin pista, Tailwind elige por defecto. text-[var(--x)] genera color, de modo que si tu variable era un tamaño no tendrá efecto.
Propiedades arbitrarias
Si Tailwind no tiene utilidad para una propiedad, escribe la declaración CSS completa entre corchetes:
<div class="[writing-mode:vertical-rl]">Vertical</div>
<div class="[mask-type:luminance]">...</div>
.\[mask-type\:luminance\] {
mask-type: luminance;
}
Admiten variantes y modificadores: hover:[writing-mode:vertical-rl].
Variantes arbitrarias
También se puede dar un selector propio. & representa al elemento:
<ul class="[&>li]:py-1 [&>li]:ps-2 [&>li:nth-child(even)]:bg-slate-100">
<li>Ratón inalámbrico</li>
<li>Teclado mecánico</li>
<li>Monitor 27</li>
</ul>
.\[\&\>li\]\:py-1 > li {
padding-block: var(--spacing);
}
Algunas variantes aceptan valores arbitrarios:
<div data-estado="agotado" class="data-[estado=agotado]:opacity-50">Sin stock</div>
.data-\[estado\=agotado\]\:opacity-50[data-estado="agotado"] {
opacity: 50%;
}
Lo verás con detalle en Variantes y estados.
Cuándo usar valores arbitrarios
Son una salida de emergencia, no una forma normal de trabajar:
| Situación | Qué hacer |
|---|---|
| Un valor único en toda la aplicación (el ancho de un banner de 327 px) | Valor arbitrario |
| Una propiedad sin utilidad | Propiedad arbitraria |
| Un color de marca que se repite | Variable en @theme, y luego bg-marca |
Un valor que ya existe en la escala (p-[16px]) | p-4, sin duda |
| Un valor que calcula JavaScript | style o una variable: w-(--ancho) |
La última fila merece una explicación. Un valor generado en tiempo de ejecución no puede ir en una clase arbitraria (w-[${ancho}px]), porque el escáner no lo verá. La forma correcta es:
<div class="w-(--ancho)" style="--ancho: 120px"></div>
11. Modificadores
Un modificador va tras una barra / y ajusta el valor de la utilidad. No es lo mismo que una variante: la variante dice cuándo, el modificador dice cómo.
hover:bg-indigo-600/50
└ variante └ modificador
Opacidad del color 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) el modificador genera
rgb(239 68 68 / 0.5)y solo admite múltiplos de 5; v4 usacolor-mix()y acepta cualquier número. Si usas la LTS: la clase se escribe igual, y para otro valor usa corchetes:/37pasa a ser/[.37].
Es el modificador más común. Funciona en cualquier utilidad de color:
<div class="bg-indigo-600/50 text-white">Fondo al 50 %</div>
<div class="border-2 border-rose-500/30">Borde al 30 %</div>
.bg-red-500\/50 {
background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
}
}
Hay dos declaraciones por compatibilidad: la segunda, con oklab, da mezclas más regulares y la usan los navegadores que la soportan. El módulo de Color y fondos explica color-mix() en detalle.
No confundas el modificador con la utilidad opacity-*:

Figura 9. Arriba, opacidad del color. En el centro, la diferencia con opacity-50, que aclara también el texto. Abajo, altura de línea con modificador.
bg-indigo-600/50 | opacity-50 | |
|---|---|---|
| Qué se vuelve transparente | Solo el fondo | Todo el elemento: texto, hijos y bordes |
| Propiedad | background-color | opacity |
| Efecto secundario | Ninguno | Crea un contexto de apilamiento |
Altura de línea en el tamaño de texto
text-* admite un modificador para fijar a la vez la altura de línea:
<p class="text-base/5">Más apretado</p>
<p class="text-base/7">Más aireado</p>
<p class="text-base/loose">Con nombre</p>
.text-lg\/7 {
font-size: var(--text-lg);
line-height: calc(var(--spacing) * 7);
}
.text-lg\/loose {
font-size: var(--text-lg);
line-height: var(--leading-loose);
}
El número usa la misma escala de espaciado: /7 son 1,75 rem. Se prefiere a text-base leading-7 porque el tamaño y la altura de línea son una sola decisión.
Otros modificadores
| Clase | Qué ajusta | Dónde se estudia |
|---|---|---|
bg-linear-to-r/oklch | Espacio de color del degradado | Módulo 05 |
shadow-lg/20 | Opacidad de la sombra | Módulo 06 |
group/tarjeta | Nombre del grupo | Módulo 11 |
Además se admiten valores arbitrarios en el modificador: bg-red-500/[0.33], text-lg/[1.3]. Y con variables: bg-(--marca)/50.
⚠️ Importante: No toda barra es un modificador. En
w-1/2yaspect-4/3, la barra forma parte del valor: es una fracción.
12. Forzar con ! 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) el signo
!va delante de la utilidad; en v4 va al final de la clase. Si usas la LTS: escribe!flexyhover:!flex, porque allíflex!no se aplica y no da ningún error.
Añadir ! al final de una clase genera !important:
<div class="bg-red-500 bg-blue-500!">Azul</div>
.flex\! {
display: flex !important;
}
El signo va al final de toda la clase, tras variantes y modificadores:
| Clase en v4 | Clase en v3 | Significado |
|---|---|---|
flex! | !flex | display: flex !important |
hover:flex! | hover:!flex | Con hover |
mt-4! | !mt-4 | Margen importante |
bg-red-500/50! | !bg-red-500/50 | Con modificador de opacidad |
En v4, la forma antigua !flex sigue compilando como clase obsoleta. Al revés no ocurre: en 3.4.19, flex! genera un .flex normal que no coincide con la clase flex! del HTML, y mt-4! no genera nada. No hay error, simplemente no se aplica.
!important invierte las capas
Una declaración importante siempre vence a una normal, sea cual sea su capa. Por eso, en la figura 5, bg-sky-300! gana al CSS sin capa.
Hay además un detalle poco conocido del estándar CSS. Si las dos declaraciones son importantes, el orden de las capas se invierte: gana la de la capa anterior, y las de capa ganan a las de fuera de capa. Así, un !important en @layer base vencería a bg-sky-300!.
Marcar todo como importante 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) la opción está en la configuración; en v4 pasa a la importación. Si usas la LTS: añade
important: trueatailwind.config.js.
Si tu CSS convive con otra hoja de la que no puedes prescindir, puedes marcar todas las utilidades:
@import "tailwindcss" important;
.p-4 {
padding: calc(var(--spacing) * 4) !important;
}
⚠️ Importante:
!es un último recurso. Si necesitas!en muchas clases, el problema suele ser CSS sin capa o mal colocado. Arregla eso primero: una sobrecarga de!importanthará imposible sobrescribir nada más adelante.
13. El orden de las clases
El orden del atributo class no importa
Es una propiedad del propio CSS que mucha gente no tiene clara. Si dos reglas tienen la misma especificidad, gana la última de la hoja de estilos, no la última del atributo class. Estos dos elementos se ven iguales:
<div class="bg-red-500 bg-blue-500">...</div>
<div class="bg-blue-500 bg-red-500">...</div>

Figura 10. Los pares son idénticos: da igual en qué orden se escriban las clases. Solo ! cambia el resultado.
Ambos son rojos. La hoja tiene este orden:
.bg-blue-500 { background-color: var(--color-blue-500); }
.bg-red-500 { background-color: var(--color-red-500); }
Cómo ordena Tailwind
Tailwind fija el orden de su hoja con criterios estables. Esta es la salida real para px-2 p-4 pt-1 m-2 mx-auto flex hidden block text-lg font-bold hover:p-1 md:p-2 focus:p-3 sm:p-5 dark:p-6, en el orden en que salen:
m-2 mx-auto block flex hidden p-4 px-2 pt-1 text-lg font-bold
hover:p-1 focus:p-3 sm:p-5 md:p-2 dark:p-6
De ahí salen las reglas que importan:
- Primero las utilidades simples, luego las variantes. Así
md:p-2gana ap-4, sin importar dónde lo escribas. - Por grupos de propiedades. Márgenes,
display, relleno, tipografía… - Lo general antes que lo particular.
p-4va antes quepx-2, y este antes quept-1. Esto es muy útil:class="p-8 px-2"significa “32 px, salvo los laterales, que son 8 px”. - Puntos de ruptura de menor a mayor.
sm:antes quemd:.
Los pares amarillos de la figura 10 lo confirman: padding-top es de 32 px y padding-left de 8 px en los dos casos.
Dos clases para la misma propiedad 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) las clases rivales se ordenan como texto; en v4 los números se ordenan por valor, y
mt-2 mt-10cambia de ganador sin aviso. Si usas la LTS: no dependas del orden; deja solo una de las dos clases y obtendrás lo mismo en las dos versiones.
Si escribes dos clases que tocan la misma propiedad (p-2 p-10, flex hidden), es un conflicto y debes evitarlo. Gana una de ellas por un criterio accidental. Comparando las salidas:
4.3.3 .p-2 .p-4 .p-10 .mt-2 .mt-10 .text-center .text-left
3.4.19 .p-10 .p-2 .p-4 .mt-10 .mt-2 .text-left .text-center
v4 ordena los números por su valor; v3, como texto, por lo que 10 va antes que 2. Como gana la última:
| Clases | Gana en 3.4.19 | Gana en 4.3.3 |
|---|---|---|
p-2 p-10 | p-2 | p-10 |
mt-2 mt-10 | mt-2 | mt-10 |
text-center text-left | text-center | text-left |
flex hidden | hidden | hidden |
bg-red-500 bg-blue-500 | bg-red-500 | bg-red-500 |
El caso real: clases condicionales
El conflicto casi nunca está escrito a mano. Aparece así:
// Mal: el elemento destacado lleva p-4 y p-8, y p-8 gana por casualidad
const clases = `p-4 ${destacado ? 'p-8' : ''}`;
// Bien: cada caso aporta una sola clase de relleno
const clases = destacado ? 'p-8' : 'p-4';
El caso más difícil es un componente que acepta clases desde fuera. Si la Tarjeta lleva p-4 y le pasas p-8, ¿quién gana? Para eso existe tailwind-merge, que elimina la clase perdedora. Lo verás en Reutilización y componentes.
Ordenar con Prettier
Que el orden no afecte al resultado no significa que dé igual para quien lee. prettier-plugin-tailwindcss, instalado en el módulo 01, ordena las clases siguiendo el mismo criterio del compilador:
<!-- Antes -->
<div class="text-white p-4 hover:bg-indigo-700 bg-indigo-600 md:p-6 flex rounded-lg font-semibold mi-clase mt-2 hover:underline focus:outline-2 sm:flex-col items-center"></div>
<!-- Después de ejecutar Prettier -->
<div
class="mi-clase mt-2 flex items-center rounded-lg bg-indigo-600 p-4 font-semibold text-white hover:bg-indigo-700 hover:underline focus:outline-2 sm:flex-col md:p-6"
></div>
Las clases propias (mi-clase) van delante y las variantes, al final. Esta salida es real, de Prettier 3.9.10 con el plugin 0.8.1.
💡 Consejo: Como el plugin ordena igual que Tailwind, una clase que se pisa con otra queda al lado de su rival. Las incoherencias saltan a la vista.
Las variantes se apilan 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) las variantes apiladas se aplican de derecha a izquierda; en v4, de izquierda a derecha. Si usas la LTS: invierte las combinaciones con selectores: escribe
first:*:pt-0donde v4 escribe*:first:pt-0.
Puedes encadenar variantes (md:hover:bg-red-500), y la salida anida las condiciones:
@media (width >= 48rem) {
@media (hover: hover) {
.md\:hover\:bg-red-500:hover {
background-color: var(--color-red-500);
}
}
}
En casi todas las combinaciones el orden es indiferente. Importa con selectores como *: o first:, y lo estudiarás en Variantes y estados, junto con el @media (hover: hover) que ves aquí.
14. Convivir con otro CSS: el prefijo 🔄 v4.0
🔄 Cambia en Tailwind 4.0. En la LTS (Tailwind 3.4) el prefijo va pegado a la utilidad; en v4 funciona como una variante al inicio de la clase. Si usas la LTS: añade
prefix: 'tw-'atailwind.config.js; las clases sontw-p-4yhover:tw-p-2.
Si añades Tailwind a un proyecto con Bootstrap o con un CSS antiguo, las clases flex, hidden o p-4 pueden chocar. Un prefijo evita la colisión:
@import "tailwindcss" prefix(tw);
<div class="tw:p-4 tw:hover:bg-red-500">Con prefijo</div>
<div class="p-4">Sin efecto: no es una clase de Tailwind</div>
:root, :host {
--tw-color-red-500: oklch(63.7% 0.237 25.331);
--tw-spacing: 0.25rem;
}
.tw\:p-4 {
padding: calc(var(--tw-spacing) * 4);
}
@media (hover: hover) {
.tw\:hover\:bg-red-500:hover {
background-color: var(--tw-color-red-500);
}
}
Tres observaciones comprobadas:
- El prefijo va siempre primero:
tw:hover:p-2, nuncahover:tw:p-2. - Las variables de salida también llevan prefijo:
--tw-color-red-500. Si las lees en tu CSS, debes escribirlo. - En
@themesigues declarando--color-marca, sin prefijo. La salida es--tw-color-marca.
Para añadir Tailwind a un sitio existente, lo habitual es combinar prefijo y ausencia de Preflight:
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme) prefix(tw);
@import "tailwindcss/utilities.css" layer(utilities) prefix(tw);
| Objetivo | Tailwind 4.3 | Tailwind 3.4 |
|---|---|---|
| Quitar Preflight | Importar por piezas | corePlugins: { preflight: false } |
| Prefijo | prefix(tw), clases tw:p-4 | prefix: 'tw-', clases tw-p-4 |
| Todo importante | @import "tailwindcss" important | important: true |
15. Caso práctico: el catálogo de la tienda
Juntamos todo el módulo en una página real de la tienda de EMPRESA. Usa Preflight, una marca propia en @theme, estilos de etiquetas en @layer base, modificadores y escala de espaciado.

Figura 11. El catálogo terminado, con 960 px de ancho.
Crea app-tienda.css:
@import "tailwindcss";
@theme {
--color-marca-100: oklch(94% 0.03 275);
--color-marca-600: oklch(50% 0.18 275);
--color-marca-700: oklch(42% 0.16 275);
--color-agotado: oklch(55% 0.2 25);
}
@layer base {
h1 {
font-size: var(--text-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-gray-900);
}
h2 {
font-size: var(--text-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-900);
}
a {
color: var(--color-marca-600);
text-decoration: underline;
text-underline-offset: 2px;
}
button:not(:disabled) {
cursor: pointer;
}
}
Y tienda.html:
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Tienda EMPRESA</title>
<link rel="stylesheet" href="/app-tienda.css">
</head>
<body class="bg-gray-100 p-8 text-gray-700">
<main class="mx-auto max-w-4xl">
<h1>Portátiles y sobremesas</h1>
<p class="mt-1 text-sm">Precios con IVA. Consulta las <a href="#">condiciones de envío</a>.</p>
<ul class="mt-6 grid grid-cols-3 gap-5">
<li>
<article class="flex h-full flex-col rounded-xl border border-gray-200 bg-white p-5">
<p class="text-xs font-semibold tracking-wider text-marca-600 uppercase">Portátiles</p>
<h2 class="mt-1">Portátil UltraBook 14</h2>
<p class="mt-2 text-sm/6">Portátil ligero de 14 pulgadas, 16 GB RAM</p>
<p class="mt-auto pt-4 text-2xl font-bold text-gray-900">899 €</p>
<button class="mt-3 rounded-lg bg-marca-600 px-4 py-2 text-sm font-medium text-white hover:bg-marca-700">Añadir a la cesta</button>
</article>
</li>
<li>
<article class="flex h-full flex-col rounded-xl border border-gray-200 bg-white p-5">
<p class="text-xs font-semibold tracking-wider text-marca-600 uppercase">Sobremesas</p>
<h2 class="mt-1">Torre Workstation</h2>
<p class="mt-2 text-sm/6">Estación de trabajo para diseño y CAD</p>
<p class="mt-auto pt-4 text-2xl font-bold text-gray-900">1899 €</p>
<p class="mt-1 w-fit rounded-full bg-amber-500/15 px-2.5 py-0.5 text-xs font-medium text-amber-800">Quedan 4</p>
<button class="mt-3 rounded-lg bg-marca-600 px-4 py-2 text-sm font-medium text-white hover:bg-marca-700">Añadir a la cesta</button>
</article>
</li>
<li>
<article class="flex h-full flex-col rounded-xl border border-gray-200 bg-white p-5">
<p class="text-xs font-semibold tracking-wider text-marca-600 uppercase">Portátiles</p>
<h2 class="mt-1">Portátil Básico 15</h2>
<p class="mt-2 text-sm/6">Portátil de 15 pulgadas para ofimática</p>
<p class="mt-auto pt-4 text-2xl font-bold text-gray-400 line-through">499 €</p>
<p class="mt-1 w-fit rounded-full bg-agotado/10 px-2.5 py-0.5 text-xs font-medium text-agotado">Agotado</p>
<button disabled class="mt-3 rounded-lg bg-marca-600 px-4 py-2 text-sm font-medium text-white opacity-50">No disponible</button>
</article>
</li>
</ul>
</main>
</body>
</html>
Qué aplica de lo visto
- Preflight: el
<ul>no lleva viñetas, por lo que sirve como contenedor semántico de la rejilla. Los<button>no traen aspecto propio. @layer base:h1,h2yatienen estilo sin una sola clase. Medido en el navegador:h1de 24 px y peso 700,h2de 18 px y peso 600. Están en una capa para que puedas cambiarlos context-3xlen una página concreta.- Tema:
--color-marca-600creabg-marca-600ytext-marca-600. Si cambias de marca, solo tocas cuatro líneas. - Modificadores:
bg-amber-500/15da el fondo suave de la etiqueta ytext-sm/6fija la altura de línea. - Escala:
px-2.5 py-0.5usa múltiplos de 0,25. - Sin conflictos: el botón agotado no sobrescribe nada. Simplemente lleva otras clases.
mt-auto empuja el precio hacia abajo dentro del flex, para que todos los botones se alineen. Lo estudiarás en Flexbox.
💡 Consejo: Estas tres tarjetas repiten muchas clases. En un proyecto real serían un bucle o un componente, nunca tres copias. Se ve en Reutilización y componentes.
Equivalente en v3
Compilado en 3.4.19 con el mismo HTML. La configuración sustituye a @theme, y las reglas base usan theme():
// tailwind.config.js
export default {
content: ['./*.html'],
theme: {
extend: {
colors: {
marca: { 100: '#e0e2f6', 600: '#4a50c7', 700: '#3a3fa3' },
agotado: '#c8102e',
},
},
},
};
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1 {
font-size: theme(fontSize.2xl);
font-weight: theme(fontWeight.bold);
color: theme(colors.gray.900);
}
a {
color: theme(colors.marca.600);
text-decoration: underline;
}
}
⚠️ Importante: En v3 los colores van en hexadecimal por un motivo. Con un valor
oklch(...)en la configuración, la clasebg-marca/10no genera nada, porque v3 solo sabe aplicar opacidad a los formatos que puede descomponer. Con hexadecimal,bg-agotado/10producergb(200 16 46 / 0.1). Además, elborderno necesita color: en v3 ya es gris.
16. Depurar una clase que no funciona
Cuando una clase no hace lo que esperas, sigue estos pasos en orden:
- Mira el elemento en las herramientas de desarrollo. ¿La clase está en el atributo
class, y escrita como crees? - Busca la regla en el panel de estilos.
- Si no aparece, Tailwind no la generó: pasos 3 y 4.
- Si aparece tachada, otra regla gana: paso 5.
- Comprueba que existe. Prueba a escribirla en HTML con la extensión de VS Code: si no se autocompleta, sospecha.
- Comprueba que el escáner la ve. ¿Está escrita entera? ¿Está en un fichero que Tailwind recorre? ¿La versión es la que crees?
- Mira quién gana. Busca CSS sin capa, un
styleo una clase rival. El panel “Computado” (Computed) te lleva a la regla ganadora.
Errores frecuentes
| Síntoma | Causa | Solución |
|---|---|---|
| La clase no está en el CSS | Nombre construido por partes: `bg-${c}-500` | Escribir las clases enteras |
mt-17 no hace nada | Estás en v3 | mt-[4.25rem] |
mt-1.3 no hace nada | No es múltiplo de 0,25 | mt-[0.325rem] o un valor de la escala |
bg-[--marca] no da color | Sintaxis de v3 en v4 | bg-(--marca) |
flex! no hace nada | Sintaxis de v4 en v3 | !flex |
text-[var(--x)] no cambia el tamaño | Se interpreta como color | text-(length:--x) |
| La clase está en el CSS y no gana | CSS sin capa | Moverlo a @layer components o base |
| Dos clases se pisan al azar | Conflicto de la misma propiedad | Dejar una, o tailwind-merge |
Un <h1 class="text-4xl"> no crece | Regla h1 sin capa | Pasarla a @layer base |
| Un título no se ve grande sin clases | Preflight funcionando | Añadir clases o una regla en @layer base |
| La lista perdió las viñetas | Preflight | list-disc list-inside |
border no dibuja nada en v3 | Preflight desactivado | border-solid |
| Las cajas miden más de lo previsto | Preflight desactivado: content-box | box-border |
<input> invisible | Preflight quita el borde | border y un color |
bg-foo no funciona | La variable está en :root | Declararla en @theme |
| Variable del tema vacía en JavaScript | Tailwind la quitó por no usarse | Usarla en el código o @theme static |
w-1/2 falla en querySelector | La barra debe escaparse | CSS.escape('w-1/2') |
💡 Consejo: Una prueba rápida para saber si es problema de generación o de cascada: añade
!a la clase. Si sigue sin funcionar, no se ha generado o no coincide con el elemento. Si funciona, hay una regla que gana.
17. Ejercicios
Ejercicio 1: Disección
Identifica variante, utilidad, valor, modificador e importante en cada clase, e indica en qué versiones funciona:
lg:focus:text-rose-700/80!-mt-4text-sm/6dark:bg-(--marca)[&>li]:py-1
Ver solución
| Clase | Variantes | Utilidad y valor | Modificador | Importante | v3.4 | v4.3 |
|---|---|---|---|---|---|---|
lg:focus:text-rose-700/80! | lg:, focus: | text- con rose-700 | /80 | Sí | No: habría que escribir lg:focus:!text-rose-700/80 | Sí |
-mt-4 | mt- con 4 negativo | Sí | Sí | |||
text-sm/6 | text- con sm | /6, altura de línea | Sí | Sí | ||
dark:bg-(--marca) | dark: | bg- con variable | No: dark:bg-[--marca] | Sí | ||
[&>li]:py-1 | [&>li]:, variante arbitraria | py- con 1 | Sí | Sí |
El punto clave es que la barra de text-sm/6 no es opacidad: en un tamaño de texto, el modificador es la altura de línea.
Ejercicio 2: De CSS a utilidades
Convierte esta regla en clases de Tailwind. Usa la escala y la paleta.
.aviso {
padding: 0.75rem 1rem;
border-radius: 0.5rem;
border-left: 4px solid; /* color ámbar 500 */
background-color: /* ámbar 50 */;
color: /* ámbar 900 */;
font-size: 0.875rem;
font-weight: 500;
}
Ver solución
<p class="rounded-lg border-l-4 border-amber-500 bg-amber-50 px-4 py-3 text-sm font-medium text-amber-900">
Quedan pocas unidades
</p>
Valores medidos en el navegador: padding-left: 16px, padding-top: 12px, border-left-width: 4px, font-size: 14px, font-weight: 500 y border-radius: 8px.
No hace falta border-solid, porque Preflight ya deja el estilo en solid. text-sm también fija la altura de línea, cosa que la regla original no hacía, así que el aviso puede variar un poco en altura.
Ejercicio 3: ¿Qué genera el escáner?
Tienes este fichero src/etiquetas.js. Di qué clases estarán en el CSS final, y corrígelo para que funcione con cualquier estado.
const estados = ['disponible', 'agotado'];
function claseFondo(estado) {
const color = estado === 'agotado' ? 'rose' : 'emerald';
return `bg-${color}-500`;
}
// Recuerda: el diseño de la etiqueta es text-white y rounded-full
Ver solución
Se generan text-white y rounded-full, porque el comentario se lee como texto. No se generan bg-rose-500 ni bg-emerald-500: esos nombres no aparecen en ningún sitio. Las palabras rose y emerald no son clases.
const fondos = {
disponible: 'bg-emerald-500',
agotado: 'bg-rose-500',
};
function claseFondo(estado) {
return fondos[estado];
}
Y añade text-white rounded-full en el HTML o en el código, no lo dejes en un comentario: funcionaría por casualidad y dejaría de hacerlo si alguien borra la nota.
Ejercicio 4: Títulos con estilo propio
En una página de la tienda, los h1 y h2 deben verse grandes por defecto, pero en la cabecera un h1 concreto debe ser más pequeño con text-lg. Escribe el CSS y justifica dónde lo pones.
Ver solución
@import "tailwindcss";
@layer base {
h1 {
font-size: var(--text-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-gray-900);
}
h2 {
font-size: var(--text-lg);
font-weight: var(--font-weight-semibold);
color: var(--color-gray-900);
}
}
<h1 class="text-lg">Cabecera con título discreto</h1>
Va en @layer base porque son estilos de etiquetas. Al estar en esa capa, text-lg de utilities lo sobrescribe. Sin la capa, el h1 sin capa ganaría, y text-lg no tendría efecto.
Medido sin la clase: h1 de 24 px y peso 700, h2 de 18 px y peso 600.
Ejercicio 5: Vista compacta
Crea una clase .densa que reduzca a 0.2rem la escala de un bloque sin cambiar ninguna utilidad de sus hijos. ¿Cuánto medirá un p-4 dentro?
Ver solución
@import "tailwindcss";
.densa {
--spacing: 0.2rem;
}
<section class="densa">
<div class="p-4">Tarjeta</div>
</section>
p-4 es calc(var(--spacing) * 4), que aquí vale 0,8 rem. Medido en el navegador: 12,8 px.
Esta regla está sin capa a propósito: solo cambia una variable y no compite con ninguna utilidad, porque ninguna clase define --spacing en el elemento. En v3 no se puede hacer así, porque p-4 lleva 1rem escrito en el CSS.
Ejercicio 6: Limpieza de valores arbitrarios
Este HTML se ha escrito sin mirar la documentación, y una línea ni siquiera funciona. Mejóralo en Tailwind 4.3.
<div class="h-[3.75rem] bg-[#4f46e5] text-[0.875rem]">a</div>
<div style="--ancho: 12rem" class="w-[var(--ancho)] bg-[--color-teal-600]">b</div>
<div class="grid grid-cols-[2fr_1fr]">c</div>
<div class="[transform:skewY(-3deg)]">d</div>
Ver solución
<div class="h-15 bg-indigo-600 text-sm">a</div>
<div style="--ancho: 12rem" class="w-(--ancho) bg-teal-600">b</div>
<div class="grid grid-cols-[2fr_1fr]">c</div>
<div class="-skew-y-3">d</div>
| Antes | Después | Motivo |
|---|---|---|
h-[3.75rem] | h-15 | 15 × 0,25 rem. Mismo valor: 60 px |
bg-[#4f46e5] | bg-indigo-600 | Paleta del tema. Cuidado: el tono es ligeramente distinto, porque ese hexadecimal es el índigo de v3 |
text-[0.875rem] | text-sm | Mismo tamaño, y además fija la altura de línea |
w-[var(--ancho)] | w-(--ancho) | Forma corta: 192 px |
bg-[--color-teal-600] | bg-teal-600 | Esta era la línea rota: en v4 genera CSS no válido |
grid-cols-[2fr_1fr] | Se queda igual | No hay utilidad para esas columnas, y este uso es correcto |
[transform:skewY(-3deg)] | -skew-y-3 | Existe utilidad, y además se combina con otras transformaciones |
Ejercicio 7: El botón que no cambia de color
Un compañero tiene esto y dice que Tailwind no funciona, porque el botón sale índigo:
@import "tailwindcss";
.boton-marca {
background-color: var(--color-indigo-600);
color: white;
}
<button class="boton-marca bg-emerald-600">Pagar</button>
Explica la causa y arréglalo sin usar !.
Ver solución
.boton-marca está fuera de toda capa, y el CSS sin capa gana a @layer utilities, sea cual sea la especificidad. Al moverla a components, la utilidad gana:
@import "tailwindcss";
@layer components {
.boton-marca {
background-color: var(--color-indigo-600);
color: white;
}
}
Medido en Chromium: sin capa, oklch(0.511 0.262 276.966) (índigo); con capa, oklch(0.596 0.145 163.225) (esmeralda).
En v3 el mismo fichero habría funcionado si la regla se escribía antes de @tailwind utilities, y por eso este error aparece tanto al migrar.
Ejercicio 8: Un botón con modificadores
Escribe un botón con fondo índigo 600 al 90 %, texto blanco, tamaño base con altura de línea de 24 px, y relleno de 20 px en horizontal y 8 px en vertical. Usa el menor número de clases posible.
Ver solución
<button class="rounded-lg bg-indigo-600/90 px-5 py-2 text-base/6 font-semibold text-white">
Comprar
</button>
bg-indigo-600/90: solo el fondo es translúcido, de modo que el texto sigue siendo blanco puro. Conopacity-90también se aclararía el texto.text-base/6: 16 px de tamaño y6 × 4 = 24px de altura de línea en una sola clase.px-5:5 × 4 = 20px.py-2: 8 px.
Medido: line-height: 24px, font-size: 16px, y el fondo con opacidad 0,9.
Ejercicio 9: Predice el ganador
Sin ejecutar nada, di qué valor final tiene cada caso en Tailwind 4.3, y si cambiaría en 3.4.
block hiddenpx-2 p-8mt-2 mt-10text-left text-centertext-sm text-lgtext-lg leading-8rounded-lg rounded-t-none
Ver solución
Salida real de ambas versiones, en el orden de la hoja:
4.3.3 .mt-2 .mt-10 .block .flex .hidden .rounded-lg .rounded-t-none ...
.text-center .text-left .text-lg .text-sm .leading-8
3.4.19 .mt-10 .mt-2 .block .flex .hidden .rounded-lg .rounded-t-none ...
.text-left .text-center .text-lg .text-sm .leading-8
| Caso | Resultado en 4.3 | ¿Cambia en 3.4? |
|---|---|---|
| 1 | hidden: va después de block | No |
| 2 | 32 px arriba y abajo, 8 px a los lados: px-2 va después de p-8 | No |
| 3 | mt-10: orden numérico | Sí, gana mt-2 |
| 4 | text-left | Sí, gana text-center |
| 5 | text-sm | No |
| 6 | Tamaño de text-lg, altura de leading-8 | No |
| 7 | Solo las esquinas inferiores redondeadas | No |
Los casos 2, 6 y 7 son combinaciones correctas: lo particular ajusta lo general. Los casos 1, 3, 4 y 5 son conflictos que no deberían estar en tu código. Que tengan respuesta no significa que sea buena idea depender de ella.
Ejercicio 10 (propuesto): Una tarjeta de empleado
Con los datos de la API de EMPRESA, escribe una tarjeta de empleado con nombre, puesto y departamento, sin una sola línea de CSS propio salvo un color de marca en @theme. Usa un modificador de opacidad en el fondo de la insignia de departamento.
Ejercicio 11 (propuesto): Tailwind dentro de Bootstrap
Crea una página con Bootstrap por CDN y añade Tailwind con prefijo tw y sin Preflight. Comprueba que el .btn de Bootstrap se conserva. Prueba luego a activar Preflight y observa qué se rompe.
Ejercicio 12 (propuesto): El catálogo en v3
Compila la tienda de la sección 15 con Tailwind 3.4. Comprueba qué cambia en la figura: el color del borde, si quitas border-gray-200, y el cursor de los botones, ya sin la regla de @layer base.
Resumen
| Concepto | API | Idea clave |
|---|---|---|
| Utility-first | p-4, text-sm, flex | Componer clases pequeñas en el HTML, con valores de una escala |
| Anatomía | md:hover:bg-red-500/50! | Variantes, utilidad, valor, modificador e importante |
| Generación | Escáner y compilador | Lee texto plano y solo crea lo que encuentra |
| Clases dinámicas | `bg-${c}-500` | No funciona: la clase debe aparecer entera |
| Detección 🔄 v4.0 | @source / content | Automática en v4; lista manual en v3 |
| Capas 🔄 v4.0 | @layer | Reales en v4: la especificidad no cuenta entre capas |
| CSS sin capa | Regla fuera de @layer | Vence a todas las capas: escribe siempre dentro de una |
| Preflight | @import "tailwindcss" | Quita los estilos del navegador: cada aspecto se da con clases |
| Extender Preflight | @layer base | Estilos de etiquetas, siempre en la capa |
| Quitar Preflight 🔄 v4.0 | Importar por piezas / corePlugins | Sin él, las cajas vuelven a content-box |
| Valores por defecto 🔄 v4.0 | border, placeholder, cursor | currentColor, 50 % y default en v4 |
| Tema 🔄 v4.0 | @theme / tailwind.config.js | Las variables crean utilidades |
| Variables usadas | @theme static | Solo se emiten las usadas salvo con static |
| Funciones | --spacing(), --alpha(), --theme() | Leer el tema desde CSS; en v3, theme() |
| Escala 🔄 v4.0 | --spacing: 0.25rem | p-4 es calc(var(--spacing) * 4); cambiable en un subárbol |
| Valores dinámicos 🆕 v4.0 | mt-17, grid-cols-15 | Las utilidades calculan; múltiplos de 0,25 |
| Valores arbitrarios | w-[327px], [&>li]:p-2 | Salida de emergencia; _ es un espacio |
| Variables en clases 🔄 v4.0 | bg-(--marca) | bg-[--marca] es un falso amigo: no da error ni color |
| Modificadores | /50, /7 | Ajustan el valor; no son variantes |
| Importante 🔄 v4.0 | flex!, important | Al final en v4; al principio en v3 |
| Orden | Hoja de estilos | Decide la hoja, no el atributo class; evita los conflictos |
| Conflictos 🔄 v4.0 | mt-2 mt-10 | Cambian de ganador de v3 a v4: deja solo una clase |
| Prettier | prettier-plugin-tailwindcss | Ordena como el compilador |
| Prefijo 🔄 v4.0 | prefix(tw) | tw:p-4 en v4; tw-p-4 en v3 |