Conceptos básicos
Construyendo componentes complejos a partir de un conjunto restringido de utilidades primitivas.
Estilizas elementos con Tailwind combinando muchas clases de presentación de un solo propósito (clases de utilidad) directamente en tu marcado:
¡Tienes un mensaje nuevo!
<div class="mx-auto flex max-w-sm items-center gap-x-4 rounded-xl bg-white p-6 shadow-lg outline outline-black/5 dark:bg-slate-800 dark:shadow-none dark:-outline-offset-1 dark:outline-white/10"> <img class="size-12 shrink-0" src="/img/logo.svg" alt="Logo de ChitChat" /> <div> <div class="text-xl font-medium text-black dark:text-white">ChitChat</div> <p class="text-gray-500 dark:text-gray-400">¡Tienes un mensaje nuevo!</p> </div></div>
Por ejemplo, en la interfaz de usuario anterior hemos utilizado:
flex
, shrink-0
y p-6
) para controlar el diseño generalmax-w-sm
y mx-auto
) para restringir el ancho de la tarjeta y centrarla horizontalmentebg-white
, rounded-xl
y shadow-lg
) para estilizar la apariencia de la tarjetasize-12
) para establecer el ancho y alto de la imagen del logogap-x-4
) para manejar el espaciado entre el logo y el textotext-xl
, text-black
, font-medium
, etc.) para estilizar el texto de la tarjetaEstilizar las cosas de esta manera contradice muchas de las mejores prácticas tradicionales, pero una vez que lo intentes notarás rápidamente algunos beneficios realmente importantes:
Estos beneficios marcan una gran diferencia en proyectos pequeños, pero son aún más valiosos para equipos que trabajan en proyectos a largo plazo a escala.
Una reacción común a este enfoque es preguntarse, "¿no son solo estilos inline?" y, en cierto modo, lo son — estás aplicando estilos directamente a los elementos en lugar de asignarles un nombre de clase y luego estilizar esa clase.
Pero usar clases de utilidad tiene muchas ventajas importantes sobre los estilos inline, por ejemplo:
Este componente es completamente responsive e incluye un botón con estilos hover y active, y está construido completamente con clases de utilidad:
Erin Lindford
Ingeniera de Producto
<div class="flex flex-col gap-2 p-8 sm:flex-row sm:items-center sm:gap-6 sm:py-4 ..."> <img class="mx-auto block h-24 rounded-full sm:mx-0 sm:shrink-0" src="/img/erin-lindford.jpg" alt="" /> <div class="space-y-2 text-center sm:text-left"> <div class="space-y-0.5"> <p class="text-lg font-semibold text-black">Erin Lindford</p> <p class="font-medium text-gray-500">Ingeniera de Producto</p> </div> <button class="border-purple-200 text-purple-600 hover:border-transparent hover:bg-purple-600 hover:text-white active:bg-purple-700 ..."> Mensaje </button> </div></div>
Para estilizar un elemento en estados como hover o focus, prefija cualquier utilidad con el estado al que deseas apuntar, por ejemplo hover:bg-sky-700
:
Pasa el cursor sobre este botón para ver el cambio de color de fondo
<button class="bg-sky-500 hover:bg-sky-700 ...">Guardar cambios</button>
Estos prefijos se llaman variantes en Tailwind, y solo aplican los estilos de una clase de utilidad cuando la condición para esa variante coincide.
Así es como se ve el CSS generado para la clase hover:bg-sky-700
:
.hover\:bg-sky-700 { &:hover { background-color: var(--color-sky-700); }}
¿Notas cómo esta clase no hace nada a menos que el elemento esté en hover? Su único trabajo es proporcionar estilos hover — nada más.
Esto es diferente de cómo escribirías CSS tradicional, donde una sola clase usualmente proporcionaría los estilos para muchos estados:
<button class="btn">Guardar cambios</button><style> .btn { background-color: var(--color-sky-500); &:hover { background-color: var(--color-sky-700); } }</style>
Incluso puedes apilar variantes en Tailwind para aplicar una utilidad cuando coinciden múltiples condiciones, como combinar hover:
y disabled:
<button class="bg-sky-500 disabled:hover:bg-sky-500 ...">Guardar cambios</button>
Aprende más en la documentación sobre estilizar elementos en hover, focus y otros estados.
Al igual que los estados hover y focus, puedes estilizar elementos en diferentes breakpoints prefijando cualquier utilidad con el breakpoint donde quieres que se aplique ese estilo:
Redimensiona este ejemplo para ver el cambio de diseño
<div class="grid grid-cols-2 sm:grid-cols-3"> <!-- ... --></div>
In the example above, the sm:
prefix makes sure that grid-cols-3
only triggers at the sm
breakpoint and above, which is 40rem out of the box:
.sm\:grid-cols-3 { @media (width >= 40rem) { grid-template-columns: repeat(3, minmax(0, 1fr)); }}
Aprende más en la documentación de responsive design.
Estilizar un elemento en modo oscuro es solo cuestión de agregar el prefijo dark:
a cualquier utilidad que quieras aplicar cuando el modo oscuro esté activo:
Modo claro
Escribe al revés
El Zero Gravity Pen se puede usar para escribir en cualquier orientación, incluso al revés. Incluso funciona en el espacio exterior.
Modo oscuro
Escribe al revés
El Zero Gravity Pen se puede usar para escribir en cualquier orientación, incluso al revés. Incluso funciona en el espacio exterior.
<div class="bg-white dark:bg-gray-800 rounded-lg px-6 py-8 ring shadow-xl ring-gray-900/5"> <div> <span class="inline-flex items-center justify-center rounded-md bg-indigo-500 p-2 shadow-lg"> <svg class="h-6 w-6 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true" > <!-- ... --> </svg> </span> </div> <h3 class="text-gray-900 dark:text-white mt-5 text-base font-medium tracking-tight ">Escribe al revés</h3> <p class="text-gray-500 dark:text-gray-400 mt-2 text-sm "> El Zero Gravity Pen se puede usar para escribir en cualquier orientación, incluso al revés. Incluso funciona en el espacio exterior. </p></div>
Al igual que con los estados hover o las media queries, lo importante a entender es que una sola clase de utilidad nunca incluirá ambos estilos, claro y oscuro — estilizas cosas en modo oscuro usando múltiples clases, una para los estilos del modo claro y otra para los estilos del modo oscuro.
.dark\:bg-gray-800 { @media (prefers-color-scheme: dark) { background-color: var(--color-gray-800); }}
Aprende más en la documentación del modo oscuro.
Muchas veces con Tailwind incluso usarás múltiples clases para construir el valor de una sola propiedad CSS, por ejemplo, agregando múltiples filtros a un elemento:
<div class="blur-sm grayscale"> <!-- ... --></div>
Ambos efectos dependen de la propiedad filter
en CSS, por lo que Tailwind usa variables CSS para hacer posible componer estos efectos juntos:
.blur-sm { --tw-blur: blur(var(--blur-sm)); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}.grayscale { --tw-grayscale: grayscale(100%); filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-grayscale,);}
El CSS generado arriba está ligeramente simplificado, pero el truco aquí es que cada utilidad establece una variable CSS solo para el efecto que se supone que debe aplicar. Luego, la propiedad filter
mira todas estas variables, recurriendo a nada si la variable no ha sido establecida.
Tailwind usa este mismo enfoque para gradientes, colores de sombra, transformaciones, y más.
Muchas utilidades en Tailwind son impulsadas por variables de tema, como bg-blue-500
, text-xl
y shadow-md
, que mapean a tu paleta de colores subyacente, escala tipográfica y sombras.
Cuando necesites usar un valor único fuera de tu tema, usa la sintaxis especial de corchetes para especificar valores arbitrarios:
<button class="bg-[#316ff6] ..."> Iniciar sesión con Facebook</button>
Esto puede ser útil para colores únicos fuera de tu paleta de colores (como el azul de Facebook anterior), pero también cuando necesitas un valor personalizado complejo como una cuadrícula muy específica:
<div class="grid grid-cols-[24rem_2.5rem_minmax(0,1fr)]"> <!-- ... --></div>
También es útil cuando necesitas usar características de CSS como calc()
, incluso si estás usando los valores de tu tema:
<div class="max-h-[calc(100dvh-(--spacing(6))]"> <!-- ... --></div>
Incluso hay una sintaxis para generar CSS completamente arbitrario incluyendo un nombre de propiedad arbitrario, lo cual puede ser útil para establecer variables CSS:
<div class="[--gutter-width:1rem] lg:[--gutter-width:2rem]"> <!-- ... --></div>
Aprende más en la documentación sobre usar valores arbitrarios.
Tailwind CSS no es una gran hoja de estilos estática como podrías estar acostumbrado con otros frameworks CSS — genera el CSS necesario basado en las clases que realmente estás usando cuando compilas tu CSS.
Lo hace escaneando todos los archivos en tu proyecto buscando cualquier símbolo que parezca que podría ser un nombre de clase:
export default function Button({ size, children }) { let sizeClasses = { md: "px-4 py-2 rounded-md text-base", lg: "px-5 py-3 rounded-lg text-lg", }[size]; return ( <button type="button" className={`font-bold ${sizeClasses}`}> {children} </button> );}
Después de encontrar todas las clases potenciales, Tailwind genera el CSS para cada una y lo compila todo en una sola hoja de estilos con solo los estilos que realmente necesitas.
Dado que el CSS se genera basándose en el nombre de la clase, Tailwind puede reconocer clases que usan valores arbitrarios como bg-[#316ff6]
y generar el CSS necesario, incluso cuando el valor no es parte de tu tema.
Aprende más sobre cómo funciona esto en detectar clases en archivos fuente.
A veces necesitas estilizar un elemento bajo una combinación de condiciones, por ejemplo en modo oscuro, en un breakpoint específico, cuando está en hover, y cuando el elemento tiene un atributo de datos específico.
Aquí tienes un ejemplo de cómo se ve eso con Tailwind:
<button class="dark:lg:data-current:hover:bg-indigo-600 ..."> <!-- ... --></button>
@media (prefers-color-scheme: dark) and (width >= 64rem) { button[data-current]:hover { background-color: var(--color-indigo-600); }}
Tailwind también soporta cosas como group-hover
, que te permiten estilizar un elemento cuando un padre específico está en hover:
<a href="#" class="group rounded-lg p-8"> <!-- ... --> <span class="group-hover:underline">Leer más…</span></a>
@media (hover: hover) { a:hover span { text-decoration-line: underline; }}
Esta sintaxis group-*
también funciona con otras variantes, como group-focus
, group-active
, y muchas más.
Para escenarios realmente complejos (especialmente al estilizar HTML que no controlas), Tailwind soporta variantes arbitrarias que te permiten escribir cualquier selector que quieras, directamente en un nombre de clase:
<div class="[&>[data-active]+span]:text-blue-600 ..."> <span data-active><!-- ... --></span> <span>Este texto será azul</span></div>
div > [data-active] + span { color: var(--color-blue-600);}
Los estilos inline siguen siendo muy útiles en proyectos de Tailwind CSS, particularmente cuando un valor proviene de una fuente dinámica como una base de datos o API:
export function BrandedButton({ buttonColor, textColor, children }) { return ( <button style={{ backgroundColor: buttonColor, color: textColor, }} className="rounded-md px-3 py-1.5 font-medium" > {children} </button> );}
También podrías optar por un estilo inline para valores arbitrarios muy complicados que son difíciles de leer cuando se formatean como un nombre de clase:
<div class="grid-[2fr_max(0,var(--gutter-width))_calc(var(--gutter-width)+10px)]"><div style="grid-template-columns: 2fr max(0, var(--gutter-width)) calc(var(--gutter-width) + 10px)"> <!-- ... --></div>
Otro patrón útil es establecer variables CSS basadas en fuentes dinámicas usando estilos inline, y luego hacer referencia a esas variables con clases de utilidad:
export function BrandedButton({ buttonColor, buttonColorHover, textColor, children }) { return ( <button style={{ "--bg-color": buttonColor, "--bg-color-hover": buttonColorHover, "--text-color": textColor, }} className="bg-(--bg-color) text-(--text-color) hover:bg-(--bg-color-hover) ..." > {children} </button> );}
Cuando construyes proyectos enteros solo con clases de utilidad, inevitablemente te encontrarás repitiendo ciertos patrones para recrear el mismo diseño en diferentes lugares.
Por ejemplo, aquí las clases de utilidad para cada imagen de avatar se repiten cinco veces separadas:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Colaboradores</h4> <span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span> </div> <div class="mt-3 flex -space-x-2 overflow-hidden"> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1491528323818-fdd1faba62cc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1500648767791-00dcc994a43e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2.25&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src="https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" /> </div> <div class="mt-3 text-sm font-medium"> <a href="#" class="text-blue-500">+ otros 198</a> </div></div>
¡No entres en pánico! En la práctica, este no es el problema que te podría preocupar, y las estrategias para manejarlo son cosas que ya haces todos los días.
Muchas veces, un elemento de diseño que aparece más de una vez en la página renderizada solo se crea una vez porque el marcado real se renderiza en un bucle.
Por ejemplo, los avatares duplicados al principio de esta guía casi con seguridad se renderizarían en un bucle en un proyecto real:
<div> <div class="flex items-center space-x-2 text-base"> <h4 class="font-semibold text-slate-900">Colaboradores</h4> <span class="bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700 ...">204</span> </div> <div class="mt-3 flex -space-x-2 overflow-hidden"> {#each contributors as user} <img class="inline-block h-12 w-12 rounded-full ring-2 ring-white" src={user.avatarUrl} alt={user.handle} /> {/each} </div> <div class="mt-3 text-sm font-medium"> <a href="#" class="text-blue-500">+ otros 198</a> </div></div>
Cuando los elementos se renderizan en un bucle como este, la lista de clases real solo se escribe una vez, por lo que no hay un problema de duplicación real que resolver.
Cuando la duplicación se localiza en un grupo de elementos en un solo archivo, la forma más fácil de manejarla es usar edición multi-cursor para seleccionar y editar rápidamente la lista de clases para cada elemento a la vez:
Te sorprendería la frecuencia con la que esta termina siendo la mejor solución. Si puedes editar rápidamente todas las listas de clases duplicadas simultáneamente, no hay beneficio en introducir ninguna abstracción adicional.
<nav class="flex justify-center space-x-4"> <a href="/dashboard" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Home </a> <a href="/team" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Team </a> <a href="/projects" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Projects </a> <a href="/reports" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900"> Reports </a></nav>
Si necesitas reutilizar algunos estilos en múltiples archivos, la mejor estrategia es crear un componente si estás usando un framework front-end como React, Svelte o Vue, o un template partial si estás usando un lenguaje de plantillas como Blade, ERB, Twig o Nunjucks.
export function VacationCard({ img, imgAlt, eyebrow, title, pricing, url }) { return ( <div> <img className="rounded-lg" src={img} alt={imgAlt} /> <div className="mt-4"> <div className="text-xs font-bold text-sky-500">{eyebrow}</div> <div className="mt-1 font-bold text-gray-700"> <a href={url} className="hover:underline"> {title} </a> </div> <div className="mt-2 text-sm text-gray-600">{pricing}</div> </div> </div> );}
Ahora puedes usar este componente en tantos lugares como quieras, mientras sigues teniendo una única fuente de verdad para los estilos para que puedan actualizarse fácilmente juntos en un solo lugar.
Si estás usando un lenguaje de plantillas como ERB o Twig en lugar de algo como React o Vue, crear un template partial para algo tan pequeño como un botón puede parecer excesivo en comparación con una simple clase CSS como btn
.
Si bien es muy recomendable que crees template partials adecuados para componentes más complejos, escribir algo de CSS personalizado está totalmente bien cuando un template partial parece excesivo.
Así es como podría verse una clase btn-primary
, usando variables de tema para mantener el diseño consistente:
<button class="btn-primary">Guardar cambios</button>
@import "tailwindcss";@layer components { .btn-primary { border-radius: calc(infinity * 1px); background-color: var(--color-violet-500); padding-inline: --spacing(5); padding-block: --spacing(2); font-weight: var(--font-weight-semibold); color: var(--color-white); box-shadow: var(--shadow-md); &:hover { @media (hover: hover) { background-color: var(--color-violet-700); } } }}
De nuevo, para cualquier cosa que sea más complicada que un solo elemento HTML, recomendamos encarecidamente usar parciales de plantilla para que los estilos y la estructura puedan encapsularse en un solo lugar.
Cuando agregas dos clases que apuntan a la misma propiedad CSS, la clase que aparece más tarde en la hoja de estilos gana. Así que en este ejemplo, el elemento recibirá display: grid
aunque flex
viene al final en el atributo class
real:
<div class="grid flex"> <!-- ... --></div>
.flex { display: flex;}.grid { display: grid;}
En general, nunca deberías añadir dos clases conflictivas al mismo elemento — añade solo la que realmente quieres que tenga efecto:
export function Example({ gridLayout }) { return <div className={gridLayout ? "grid" : "flex"}>{/* ... */}</div>;}
Usando bibliotecas basadas en componentes como React o Vue, esto a menudo significa exponer props específicos para personalizaciones de estilo en lugar de permitir que los consumidores agreguen clases adicionales desde fuera de un componente, ya que esos estilos a menudo entrarán en conflicto.
Cuando realmente necesitas forzar que una clase de utilidad específica tenga efecto y no tienes otra forma de gestionar la especificidad, puedes añadir !
al final del nombre de la clase para hacer que todas las declaraciones sean !important
:
<div class="bg-teal-500 bg-red-500!"> <!-- ... --></div>
.bg-red-500\! { background-color: var(--color-red-500) !important;}.bg-teal-500 { background-color: var(--color-teal-500);}
Si estás añadiendo Tailwind a un proyecto que tiene CSS complejo existente con reglas de alta especificidad, puedes usar la bandera important
al importar Tailwind para marcar todas las utilidades como !important
:
@import "tailwindcss" important;
@layer utilities { .flex { display: flex !important; } .gap-4 { gap: 1rem !important; } .underline { text-decoration-line: underline !important; }}
Si tu proyecto tiene nombres de clase que entran en conflicto con las utilidades de Tailwind CSS, puedes prefijar todas las clases y variables CSS generadas por Tailwind usando la opción prefix
:
@import "tailwindcss" prefix(tw);
@layer theme { :root { --tw-color-red-500: oklch(0.637 0.237 25.331); }}@layer utilities { .tw\:text-red-500 { color: var(--tw-color-red-500); }}