Utilidades personalizadas
Crea utilidades personalizadas con @utility, variantes personalizadas con @variant y usa plugins en Tailwind CSS v4.
Busca en todas las páginas de la documentación
Crea utilidades personalizadas con @utility, variantes personalizadas con @variant y usa plugins en Tailwind CSS v4.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida - lista para copiar y pegar.
/* globals.css */
@import "tailwindcss";
/* Utilidad personalizada - genera una sola clase */
@utility container-prose {
max-width: 65ch;
margin-inline: auto;
padding-inline: 1.5rem;
}
/* Utilidad personalizada con soporte responsive/hover (automático) */
@utility text-balance {
text-wrap: balance;
}
/* Variante personalizada */
@variant hocus (&:hover, &:focus-visible);
@variant scrolled (&:where([data-scrolled]));
/* Usar un plugin estilo v3 mediante @config */
@config "./tailwind.config.js";// Uso
<div className="container-prose">
<h1 className="text-balance hocus:text-blue-600">Utilidades personalizadas</h1>
</div>
<header data-scrolled className="scrolled:bg-white scrolled:shadow">Cuándo usarlo: Cuando las utilidades integradas de Tailwind no cubren un patrón específico que repites - crea una utilidad personalizada una vez y úsala en todas partes.
/* globals.css */
@import "tailwindcss";
/* Utilidades de layout */
@utility stack {
display: flex;
flex-direction: column;
}
@utility center {
display: flex;
align-items: center;
justify-content: center;
}
@utility container-narrow {
max-width: 42rem;
margin-inline: auto;
padding-inline: 1rem;
}
/* Utilidades visuales */
@utility glass {
background: rgb(255 255 255 / 0.8);
backdrop-filter: blur(12px);
border: 1px solid rgb(255 255 255 / 0.2);
}
@utility gradient-text {
background: linear-gradient(to right, var(--color-primary), var(--color-secondary, #8b5cf6));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
@utility scrollbar-hidden {
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}
/* Variantes interactivas */
@variant hocus (&:hover, &:focus-visible);
@variant group-hocus (:merge(.group):hover &, :merge(.group):focus-visible &);
@variant aria-current (&[aria-current="page"]);
@variant keyboard-focus (&:focus-visible);
/* Variantes de estado */
@variant loading (&[data-loading]);
@variant empty-state (&:where(:empty, [data-empty]));
@theme {
--color-primary: #2563eb;
}export function CustomUtilitiesDemo() {
return (
<div className="container-narrow stack gap-8 py-12">
<h1 className="gradient-text text-4xl font-bold">
Utilidades personalizadas en acción
</h1>
<nav className="glass sticky top-0 z-10 rounded-xl p-4">
<ul className="flex gap-4">
<li>
<a href="#" className="rounded px-3 py-1 hocus:bg-gray-100 aria-current:font-bold" aria-current="page">
Inicio
</a>
</li>
<li>
<a href="#" className="rounded px-3 py-1 hocus:bg-gray-100">Acerca de</a>
</li>
<li>
<a href="#" className="rounded px-3 py-1 hocus:bg-gray-100">Contacto</a>
</li>
</ul>
</nav>
<div className="scrollbar-hidden flex gap-4 overflow-x-auto pb-2">
{Array.from({ length: 10 }, (_, i) => (
<div key={i} className="shrink-0 center size-24 rounded-lg bg-gray-100 text-sm font-medium">
Tarjeta {i + 1}
</div>
))}
</div>
<button
data-loading
className="rounded bg-blue-600 px-4 py-2 text-white loading:opacity-50 loading:cursor-wait"
>
Enviar
</button>
</div>
);
}Qué demuestra esto:
@utility para clases de utilidad compuestas reutilizables@variant para selectores de estado/pseudo personalizadosaria-current, data-loading@utility name { ... } crea una clase de utilidad .name en la capa de utilidadesmd:name), de estado (hover:name) y otros@variant name (selector) crea un modificador que se aplica cuando el selector coincide@variant hocus (&:hover, &:focus-visible)& en los selectores de variantes representa el elemento al que se aplica la variante@layer components en v3Utilidad funcional (con valores) mediante @theme:
/* No puedes crear utilidades personalizadas de valor arbitrario con @utility.
En su lugar, define valores del tema y usa las utilidades integradas. */
@theme {
--spacing-page: 2rem;
--spacing-section: 4rem;
--width-content: 65ch;
--width-wide: 90rem;
}
/* Ahora usa: p-page, gap-section, max-w-content, max-w-wide */Usar plugins v3:
// tailwind.config.js (solo necesario para plugins)
export default {
plugins: [
require("@tailwindcss/typography"),
require("@tailwindcss/forms"),
],
};/* globals.css */
@import "tailwindcss";
@config "./tailwind.config.js";
/* Ahora las clases prose y form-* están disponibles */Combinar con anidamiento CSS:
@utility card {
border-radius: 0.75rem;
border: 1px solid var(--color-border);
padding: 1.5rem;
background: var(--color-surface);
& > h2 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
& > p {
color: var(--color-muted-foreground);
}
}Selectores de variantes complejos:
/* Estado abierto (para details/dialog) */
@variant open (&[open], &[data-state="open"]);
/* Variante de estado del padre */
@variant sidebar-open (:merge(.sidebar-open) &);
/* La variante print está integrada, pero media personalizada: */
@variant portrait (@media (orientation: portrait));
@variant touch (@media (pointer: coarse));// Las utilidades personalizadas son CSS - sin impacto en TS
// Pero puedes crear un mapa de utilidades para documentación:
const customUtilities = {
"container-prose": "max-width: 65ch centrado con padding inline",
"glass": "fondo de vidrio esmerilado con desenfoque de backdrop",
"gradient-text": "relleno con gradiente en el texto",
"scrollbar-hidden": "oculta la barra de desplazamiento en todos los navegadores",
} as const;
type CustomUtility = keyof typeof customUtilities;Los nombres de @utility deben ser identificadores únicos - Sin puntos, dos puntos ni espacios. @utility my-card funciona; @utility my.card no.
Las utilidades personalizadas no pueden recibir argumentos - @utility spacing($value) no existe. Solución: Define valores del tema y usa las utilidades integradas (p-page, gap-section).
Compatibilidad de plugins - No todos los plugins v3 funcionan con v4 todavía. Solución: Consulta la documentación del plugin para soporte v4, o replica el CSS del plugin con @utility.
@config solo para plugins - Usar @config con una configuración v3 completa puede entrar en conflicto con @theme. Solución: Pon solo el registro de plugins en la configuración JS; mueve todos los valores del tema a @theme.
Especificidad de variantes - @variant usa el selector que proporcionas. Si tu selector tiene alta especificidad, puede sobrescribir otros estilos de forma inesperada. Solución: Usa :where() para anular la especificidad cuando sea necesario.
| Alternativa | Úsala cuando | No la uses cuando |
|---|---|---|
@apply | Quieres componer utilidades Tailwind existentes en una clase | Puedes usar @utility directamente (más limpio) |
| Abstracción de componente | El patrón incluye estructura HTML, no solo estilos | Solo necesitas una clase CSS |
| Plugins de Tailwind (JS) | Necesitas generación dinámica de utilidades con valores | Una @utility estática cubre tu necesidad |
| Plugins PostCSS | Necesitas transformaciones más allá del alcance de Tailwind | Las funciones integradas de Tailwind son suficientes |
@utility name { ... } crea una nueva clase de utilidad en la capa de utilidades@apply compone utilidades Tailwind existentes en una clase@utility es más limpio y está diseñado específicamente para utilidades personalizadas en v4Sí. Una utilidad definida con @utility funciona automáticamente con md:, hover:, dark: y todos los demás modificadores.
@variant hocus (&:hover, &:focus-visible);Uso: <a className="hocus:text-blue-600">Enlace</a>
No. @utility no puede recibir argumentos. En su lugar, define valores del tema y usa las utilidades integradas:
@theme {
--spacing-page: 2rem;
}
/* Ahora usa p-page, gap-page, etc. */@import "tailwindcss";
@config "./tailwind.config.js";Pon solo el registro de plugins en la configuración JS. Mantén todos los valores del tema en @theme.
@utility card {
border-radius: 0.75rem;
padding: 1.5rem;
& > h2 {
font-size: 1.25rem;
font-weight: 600;
}
}Sí, el anidamiento CSS nativo está soportado dentro de @utility.
Los nombres deben ser identificadores únicos. Se permiten guiones (my-card), pero no puntos, dos puntos ni espacios (my.card fallará).
@variant loading (&[data-loading]);Uso: <button data-loading className="loading:opacity-50">
El selector de la variante tiene alta especificidad. Solución: usa :where() para anular la especificidad: @variant loading (&:where([data-loading])).
Los valores del tema de la configuración JS pueden entrar en conflicto con los valores de @theme en CSS. Usa @config solo para el registro de plugins; mueve todos los valores del tema a @theme.
const customUtilities = {
"container-prose": "max-width: 65ch centrado con padding inline",
"glass": "fondo de vidrio esmerilado con desenfoque de backdrop",
} as const;
type CustomUtility = keyof typeof customUtilities;Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥