Estados, variantes y dark mode
Una utility puede activarse solo en un estado (hover:), un ancestro (group-hover:), ARIA/datos, o esquema de color (dark:). No hace falta memorizar cien variants: unas cuantas cubren el 90 % de la UI.
Documentación: hover, focus and other states, dark mode, @custom-variant.
Composición
<button
type="button"
class="rounded-md bg-blue-700 px-4 py-2 text-sm font-medium text-white hover:bg-blue-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
>
Guardar
</button>hover:— puntero; Tailwind lo envuelve en@media (hover: hover)en muchos casos.focus-visible:— foco de teclado, sin el anillo al click de ratón. Prefiérelo afocus:en botones.disabled:— acoplado adisabledHTML, no solo a una clase visual.
Otras que sí usan equipos reales:
| Variant | Idea |
|---|---|
group + group-hover: | El padre se marca group; el hijo reacciona. |
peer + peer-checked: / peer-invalid: | Hermano (checkbox, input). |
aria-expanded: / aria-current: | Estado ya expuesto a AT. |
data-active: | data-active en el nodo (menús). |
motion-reduce: | Menos o nada de animación si el SO lo pide. |
<a href="/cursos" class="group flex items-center gap-2 text-zinc-800">
<span class="group-hover:underline">Ver catálogo</span>
</a>No sustituyas un <button> por <div onclick>.
Dark mode: default vs manual
Por defecto, dark: usa prefers-color-scheme. No hay darkMode: 'class' en un tailwind.config.js: eso es v3.
<div class="bg-white text-zinc-900 dark:bg-zinc-900 dark:text-zinc-50">
<p class="text-zinc-600 dark:text-zinc-300">Cuerpo con contraste en ambos esquemas.</p>
</div>Selector manual (clase)
Si el usuario elige tema, redefines la variant:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));Entonces dark: aplica cuando .dark está en un ancestro (<html class="dark">), no por el media query del SO.
data-theme
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));<html data-theme="dark">Flash (FOUC)
Si la preferencia está en localStorage, aplica la clase en el <head> (script inline) antes de pintar. Si esperas a React/Vue hidratar, hay un frame claro/oscuro incorrecto. La doc de dark mode muestra el classList.toggle al cargar. No hace falta un theme manager: sí hace falta orden.
Con @custom-variant de clase, el media query del SO deja de gobernar dark: salvo que tu script lo lea y escriba .dark.
Errores habituales
- Solo
hover:y olvidar teclado (focus-visible). dark:bg-zinc-900con el mismotext-zinc-900.- Receta v3
darkMode: 'class'en un proyecto v4.
Buenas prácticas
- Pares light/dark de fondo y texto y borde.
motion-reduce:transition-none(o duración 0) en animaciones.disabledreal + variant.
Ejercicio
- Añade
focus-visibleal botón y recorre con Tab. - Activa dark de sistema y corrige un texto que desaparezca.
- Cambia a
@custom-variant darkcon.darky un script mínimo en elhead.
Siguiente paso
Continúa con Personalización del tema.
