←

data-slot en shadcn: cambiar font-family por tema sin conditional classes

Contexto

En el tema "terminal" del portfolio, todos los botones deberian usar font monospace. Pero los botones son Server Components reutilizables que no deberian saber en que tema estan.

Lo que aprendi

Base UI emite atributos data-slot en el DOM (ej: data-slot="button"). Esto permite targeting global desde CSS sin modificar los componentes.

/* app/globals.css */
.theme-terminal [data-slot="button"] {
  font-family: var(--font-mono);
}

Que hace esto

Cuando el tema es terminal (clase .theme-terminal en <html>), todos los elementos con data-slot="button" automaticamente usan la font monospace. Sin condicionales en JSX, sin useTheme(), sin 'use client'.

Antes (enfoque JavaScript)

"use client";
import { useTheme } from "@/components/shared/ThemeProvider";

function Button({ children }) {
  const { theme } = useTheme();
  return (
    <button className={theme === "terminal" ? "font-mono" : "font-sans"}>
      {children}
    </button>
  );
}

Problemas: necesita 'use client', consume context, re-renders en cambio de tema.

Despues (enfoque CSS)

// Server Component - sin 'use client'
function Button({ children }) {
  return <button>{children}</button>;
}
/* Una sola regla CSS global */
.theme-terminal [data-slot="button"] {
  font-family: var(--font-mono);
}

El patron general

data-slot permite crear "hooks de styling" que CSS global puede targetear:

/* Todos los inputs en terminal usan mono */
.theme-terminal [data-slot="input"] {
  font-family: var(--font-mono);
}

/* Cards en editorial tienen sombra sutil */
.theme-editorial [data-slot="card"] {
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);
}

Cuando NO usar este patron

  • Si el cambio es unico a un componente especifico, usa una clase directa
  • Si necesitas logica compleja (no solo CSS), necesitas JavaScript
  • Si el componente no emite data-slot (no usa Base UI), agrega data-slot manualmente

Referencia