1. Conceptos clave
  2. Diseño con clases de utilidad

Conceptos clave

Diseño con clases de utilidad

Construcción de componentes complejos a partir de un conjunto restringido de utilidades primitivas.

Generalidades

Para dar estilo a los elementos con Tailwind, combinas muchas clases de presentación de un solo propósito (clases de utilidad) directamente en tu marcado:

ChitChat

¡Tienes un nuevo mensaje!

<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="ChitChat Logo" />  <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 nuevo mensaje!</p>  </div></div>

Por ejemplo, en la interfaz de usuario de arriba hemos utilizado:

  • Las utilidades de display y padding (flex, shrink-0 y p-6) para controlar el diseño general
  • Las utilidades de max-width y margin (max-w-sm y mx-auto) para limitar el ancho de la tarjeta y centrarla horizontalmente
  • Las utilidades de background-color, border-radius y box-shadow (bg-white, rounded-xl y shadow-lg) para dar estilo al aspecto de la tarjeta
  • Las utilidades de width y height (size-12) para establecer el ancho y el alto de la imagen del logotipo
  • Las utilidades de gap (gap-x-4) para controlar el espaciado entre el logotipo y el texto
  • Las utilidades de font-size, color y font-weight (text-xl, text-black, font-medium, etc.) para dar estilo al texto de la tarjeta

Dar estilo a los elementos de esta manera contradice muchas de las mejores prácticas tradicionales, pero una vez que lo pruebes, notarás rápidamente algunos beneficios realmente importantes:

  • Haces las cosas más rápido — no pierdes tiempo inventando nombres de clase, tomando decisiones sobre selectores o cambiando entre archivos HTML y CSS, por lo que tus diseños se crean muy rápidamente.
  • Hacer cambios se siente más seguro — agregar o quitar una clase de utilidad a un elemento solo afecta a ese elemento, por lo que nunca tendrás que preocuparte por romper accidentalmente algo en otra página que esté usando el mismo CSS.
  • Mantener proyectos antiguos es más fácil — cambiar algo solo significa encontrar ese elemento en tu proyecto y cambiar las clases, en lugar de intentar recordar cómo funciona todo ese CSS personalizado que no has tocado en seis meses.
  • Tu código es más portable — dado que tanto la estructura como el estilo viven en el mismo lugar, puedes copiar y pegar fácilmente fragmentos enteros de interfaz de usuario, incluso entre proyectos diferentes.
  • Tu CSS deja de crecer — dado que las clases de utilidad son tan reutilizables, tu CSS no sigue creciendo linealmente con cada nueva característica que agregas a un proyecto.

Estos beneficios marcan una gran diferencia en proyectos pequeños, pero son aún más valiosos para equipos que trabajan en proyectos de gran envergadura a largo plazo.

¿Por qué no usar simplemente estilos inline?

Una reacción común a este enfoque es preguntarse: "¿no son esto simplemente estilos inline?" y, en cierto modo, lo es: estás aplicando estilos directamente a los elementos en lugar de asignarles un nombre de clase y luego dar estilo a esa clase.

But el uso de clases de utilidad tiene muchas ventajas importantes sobre los estilos inline, por ejemplo:

  • Diseñar con restricciones — al usar estilos inline, cada valor es un número mágico. Con las utilidades, eliges estilos de un sistema de diseño predefinido, lo que facilita mucho la creación de interfaces de usuario visualmente consistentes.
  • Hover, focus y otros estados — los estilos inline no pueden dirigirse a estados como hover o focus, pero las variantes de estado de Tailwind facilitan la aplicación de estilos a esos estados con clases de utilidad.
  • Media queries — no puedes usar media queries en estilos inline, pero puedes usar las variantes responsivas de Tailwind para crear interfaces totalmente responsivas fácilmente.

Este componente es completamente responsivo e incluye un botón con estilos hover y active, y está construido en su totalidad con clases de utilidad:

Woman's Face

Erin Lindford

Ingeniero 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">Ingeniero 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>

Pensar en clases de utilidad

Estilos para estados hover y focus

Para dar estilo a un elemento en estados como hover o focus, añade como prefijo a cualquier utilidad el estado al que quieres dirigirte, por ejemplo hover:bg-sky-700:

Pasa el cursor sobre este botón para ver cambiar el color de fondo

<button class="bg-sky-500 hover:bg-sky-700 ...">Guardar cambios</button>

Estos prefijos se denominan variantes en Tailwind, y solo aplican los estilos de una clase de utilidad cuando se cumple la condición de esa variante.

Así es como se ve el CSS generado para la clase hover:bg-sky-700:

Generated CSS
.hover\:bg-sky-700 {  &:hover {    background-color: var(--color-sky-700);  }}

¿Notas cómo esta clase no hace nada a menos que se pase el cursor sobre el elemento? Su único trabajo es proporcionar estilos hover, nada más.

Esto es diferente de cómo escribirías CSS tradicional, donde una sola clase generalmente proporcionaría los estilos para muchos estados:

HTML
<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>

Obtén más información en la documentación sobre cómo dar estilo a elementos en estados de hover, focus y otros estados.

Media queries y breakpoints

Al igual que los estados hover y focus, puedes dar estilo a los elementos en diferentes breakpoints anteponiendo a cualquier utilidad el breakpoint donde deseas que se aplique ese estilo:

Cambia el tamaño de este ejemplo para ver cambiar el diseño

01
02
03
04
05
06
<div class="grid grid-cols-2 sm:grid-cols-3">  <!-- ... --></div>

En el ejemplo anterior, el prefijo sm: se asegura de que grid-cols-3 solo se active en el breakpoint sm y superiores, que es de 40rem de forma predeterminada:

Generated CSS
.sm\:grid-cols-3 {  @media (width >= 40rem) {    grid-template-columns: repeat(3, minmax(0, 1fr));  }}

Obtén más información en la documentación de diseño responsivo.

Estilos para el modo oscuro

Dar estilo a un elemento en modo oscuro es solo cuestión de agregar el prefijo dark: a cualquier utilidad que desees aplicar cuando el modo oscuro esté activo:

Modo claro

Escribe boca abajo

El bolígrafo Zero Gravity se puede utilizar para escribir en cualquier orientación, incluso boca abajo. Incluso funciona en el espacio exterior.

Modo oscuro

Escribe boca abajo

El bolígrafo Zero Gravity se puede utilizar para escribir en cualquier orientación, incluso boca abajo. 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 boca abajo</h3>  <p class="text-gray-500 dark:text-gray-400 mt-2 text-sm ">    El bolígrafo Zero Gravity se puede utilizar para escribir en cualquier orientación, incluso boca abajo. Incluso funciona en el espacio exterior.  </p></div>

Al igual que con los estados hover o las media queries, lo importante es entender que una sola clase de utilidad nunca incluirá tanto los estilos claros como los oscuros: das estilo a los elementos en modo oscuro usando múltiples clases, una para los estilos del modo claro y otra para los estilos del modo oscuro.

Generated CSS
.dark\:bg-gray-800 {  @media (prefers-color-scheme: dark) {    background-color: var(--color-gray-800);  }}

Obtén más información en la documentación de modo oscuro.

Uso de composición de clases

Muchas veces con Tailwind incluso usarás múltiples clases para construir el valor de una sola propiedad CSS, por ejemplo, al agregar múltiples filtros a un elemento:

HTML
<div class="blur-sm grayscale">  <!-- ... --></div>

Ambos efectos se basan en la propiedad filter en CSS, por lo que Tailwind utiliza variables CSS para hacer posible la composición de estos efectos:

Generated CSS
.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 examina todas estas variables, recurriendo a nada si la variable no se ha establecido.

Tailwind utiliza este mismo enfoque para gradientes, colores de sombra, transformaciones y más.

Uso de valores arbitrarios

Muchas utilidades en Tailwind se basan en variables de tema, como bg-blue-500, text-xl y shadow-md, que se asignan 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:

HTML
<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 de arriba), pero también cuando necesitas un valor personalizado complejo como una cuadrícula muy específica:

HTML
<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:

HTML
<div class="max-h-[calc(100dvh-(--spacing(6)))]">  <!-- ... --></div>

Incluso existe una sintaxis para generar CSS completamente arbitrario, incluido un nombre de propiedad arbitrario, lo que puede ser útil para establecer variables CSS:

HTML
<div class="[--gutter-width:1rem] lg:[--gutter-width:2rem]">  <!-- ... --></div>

Obtén más información en la documentación sobre el uso de valores arbitrarios.

¿Cómo funciona esto?

Tailwind CSS no es una única y gran hoja de estilo estática a la que podrías estar acostumbrado con otros frameworks CSS: genera el CSS necesario en función de las clases que realmente estás usando cuando compilas tu CSS.

Lo hace escaneando todos los archivos de tu proyecto en busca de cualquier símbolo que parezca que podría ser un nombre de clase:

Button.jsx
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>  );}

Una vez que ha encontrado todas las clases potenciales, Tailwind genera the CSS para cada una y lo compila todo en una sola hoja de estilo con solo los estilos que realmente necesitas.

Dado que el CSS se genera en función del 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 forma parte de tu tema.

Obtén más información sobre cómo funciona esto en detección de clases en archivos fuente.

Selectores complejos

A veces necesitas dar estilo a un elemento bajo una combinación de condiciones, por ejemplo, en modo oscuro, en un breakpoint específico, al pasar el cursor (hover) y cuando el elemento tiene un atributo de datos (data attribute) específico.

Aquí tienes un ejemplo de cómo se ve eso con Tailwind:

HTML
<button class="dark:lg:data-current:hover:bg-indigo-600 ...">  <!-- ... --></button>
Simplified CSS
@media (prefers-color-scheme: dark) and (width >= 64rem) {  button[data-current]:hover {    background-color: var(--color-indigo-600);  }}

Tailwind también admite cosas como group-hover, que te permiten dar estilo a un elemento cuando se pasa el cursor sobre un padre específico:

HTML
<a href="#" class="group rounded-lg p-8">  <!-- ... -->  <span class="group-hover:underline">Leer más…</span></a>
Simplified CSS
@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 dar estilo a HTML que no controlas), Tailwind admite variantes arbitrarias que te permiten escribir cualquier selector que desees, directamente en un nombre de clase:

HTML
<div class="[&>[data-active]+span]:text-blue-600 ...">  <span data-active><!-- ... --></span>  <span>Este texto será azul</span></div>
Simplified CSS
div > [data-active] + span {  color: var(--color-blue-600);}

Cuándo usar estilos inline

Los estilos inline siguen siendo muy útiles en proyectos de Tailwind CSS, especialmente cuando un valor proviene de una fuente dinámica como una base de datos o una API:

branded-button.jsx
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 puedes recurrir a un estilo inline para valores arbitrarios muy complicados que son difíciles de leer cuando se formatean como un nombre de clase:

HTML
<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 utilizando estilos inline y luego hacer referencia a esas variables con clases de utilidad:

branded-button.jsx
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>  );}

Gestionar la duplicación

Cuando construyes proyectos enteros usando únicamente 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 distintas:

Colaboradores

204
<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">+ 198 otros</a>  </div></div>

¡No te preocupes! En la práctica, este no es el problema que podría preocuparte, y las estrategias para abordarlo son cosas que ya haces todos los días.

Uso de bucles

Muchas veces, un elemento de diseño que aparece más de una vez en la página renderizada en realidad solo se escribe una vez porque el marcado real se renderiza en un bucle (loop).

Por ejemplo, los avatares duplicados al principio de esta guía casi con seguridad se renderizarían en un bucle en un proyecto real:

Colaboradores

204
<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">+ 198 otros</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.

Uso de la edición multi-cursor

Cuando la duplicación se localiza en un grupo de elementos en un solo archivo, la forma más fácil de solucionarlo es usar la edición multi-cursor para seleccionar y editar rápidamente la lista de clases para cada elemento a la vez:

<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">    Inicio  </a>  <a href="/team" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900">    Equipo  </a>  <a href="/projects" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900">    Proyectos  </a>  <a href="/reports" class="font-medium rounded-lg px-3 py-2 text-gray-700 hover:bg-gray-100 hover:text-gray-900">    Reportes  </a></nav>

Te sorprendería saber con qué frecuencia esta termina siendo la mejor solución. Si puedes editar rápidamente todas las listas de clases duplicadas de forma simultánea, no hay ningún beneficio en introducir ninguna abstracción adicional.

Uso de componentes

Si necesitas reutilizar algunos estilos en varios archivos, la mejor estrategia es crear un componente si estás usando un framework de frontend como React, Svelte o Vue, o una plantilla parcial (template partial) si estás usando un lenguaje de plantillas como Blade, ERB, Twig o Nunjucks.

Beach
Villa Privada
$299 USD por noche
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 desees, mientras sigues teniendo una única fuente de verdad para los estilos, de modo que puedan actualizarse fácilmente juntos en un solo lugar.

Uso de CSS personalizado

Si estás usando un lenguaje de plantillas como ERB o Twig en lugar de algo como React o Vue, crear una plantilla parcial para algo tan pequeño como un botón puede parecer excesivo en comparación con una clase CSS simple como btn.

Aunque se recomienda encarecidamente crear plantillas parciales adecuadas para componentes más complejos, escribir un poco de CSS personalizado está totalmente bien cuando una plantilla parcial se siente un poco exagerada.

Así es como podría verse una clase btn-primary, utilizando variables de tema para mantener la consistencia del diseño:

HTML
<button class="btn-primary">Guardar cambios</button>
CSS
@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);      }    }  }}

Sin embargo, para cualquier elemento que sea más complicado que un simple elemento HTML, recomendamos encarecidamente utilizar plantillas parciales para que los estilos y la estructura puedan encapsularse en un solo lugar.

Gestionar conflictos de estilo

Conflicto de clases de utilidad

Cuando agregas dos clases que se dirigen a la misma propiedad CSS, gana la clase que aparece más tarde en la hoja de estilos. Así, en este ejemplo, el elemento recibirá display: grid aunque flex aparezca al final en el atributo class real:

HTML
<div class="grid flex">  <!-- ... --></div>
CSS
.flex {  display: flex;}.grid {  display: grid;}

En general, simplemente nunca deberías agregar dos clases en conflicto al mismo elemento: solo agrega la que realmente deseas que surta efecto:

example.jsx
export function Example({ gridLayout }) {  return <div className={gridLayout ? "grid" : "flex"}>{/* ... */}</div>;}

Al usar bibliotecas basadas en componentes como React o Vue, esto a menudo significa exponer props específicas 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.

Uso del modificador important

Cuando realmente necesitas forzar la aplicación de una clase de utilidad específica y no tienes otro medio de gestionar la especificidad, puedes agregar ! al final del nombre de la clase para hacer que todas las declaraciones sean !important:

HTML
<div class="bg-teal-500 bg-red-500!">  <!-- ... --></div>
Generated CSS
.bg-red-500\! {  background-color: var(--color-red-500) !important;}.bg-teal-500 {  background-color: var(--color-teal-500);}

Uso de la bandera important

Si estás agregando 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:

app.css
@import "tailwindcss" important;
Compiled CSS
@layer utilities {  .flex {    display: flex !important;  }  .gap-4 {    gap: 1rem !important;  }  .underline {    text-decoration-line: underline !important;  }}

Uso de la opción prefix

Si tu proyecto tiene nombres de clase que entran en conflicto con las utilidades de Tailwind CSS, puedes anteponer un prefijo a todas las clases y variables CSS generadas por Tailwind utilizando la opción prefix:

app.css
@import "tailwindcss" prefix(tw);
Compiled CSS
@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);  }}
Copyright © 2026 Tailwind Labs Inc.·Política de marcas registradas