🎨 3.3 · Estilos en React: de CSS plano a design systems

⏱ 5h 00min ⚡ 100 XP 🏅 Mid Junior 📖 Frontend Moderno y Proyecto Final
"El estilo en React no es un archivo global: vive con cada componente."

🎯 Objetivo del tema

Al terminar este tema serás capaz de: Al terminar este tema serás capaz de aplicar estilos a componentes React de forma escalable: CSS Modules para encapsulación, Styled Components para CSS-in-JS dinámico, y Tailwind CSS para utility-first. Aprenderás cuándo usar cada uno, cómo crear un design system coherente, y cómo evitar los problemas del CSS global. El resultado: una UI profesional, mantenible, y consistente.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

🗺️ Mapa del tema

En el Tema 1.5 aprendiste CSS3 con selectores, Box Model, y responsive. Ahora viene el reto: ¿cómo se aplica CSS en una app de React con 50+ componentes? El CSS global de siempre no escala: las clases colisionan, el orden importa, y refactorizar es un infierno. React ofrece 3 soluciones modernas: CSS Modules (encapsulación nativa), Styled Components (CSS-in-JS con lógica), y Tailwind (utility-first sin escribir CSS). Cada una tiene su lugar. Vamos a verlas con ejemplos reales.

1. Por qué el CSS global no escala en React

El problema de las clases que colisionan y el orden de carga.

2. CSS Modules: encapsulación nativa y simple

Cada componente tiene sus propios estilos sin colisiones.

3. Styled Components: CSS-in-JS con lógica de React

Estilos dinámicos basados en props del componente.

4. Tailwind CSS: utility-first, el más popular de 2025

Clases pequeñas combinadas en el JSX, sin escribir CSS.

5. Design system: variables CSS y tokens compartidos

Cómo crear un sistema coherente de colores, fuentes, y espacios.

6. Proyecto integrador: estilizar la app de países con las 3 técnicas

Compara las 3 opciones con un ejemplo real.

3.3.1 Por qué el CSS global no escala en React

En HTML clásico, tenías UN archivo styles.css con TODAS las clases. En una app pequeña funciona. En una app de React con 50+ componentes, se vuelve un infierno: las clases colisionan (un .card en un componente afecta a otro), el orden de carga importa (el último estilo gana), y refactorizar es peligroso. Por eso React ofrece soluciones que ENCAPSULAN los estilos a cada componente.

📷 Imagen referencial: Comparativa de 3 enfoques: CSS Modules (archivo .module.css con clases únicas), Styled Components (estilos en JS con template literals y props), Tailwind CSS (utility classes combinadas en JSX).
ProblemaCSS globalSolución moderna
Colisión de clases: dos .card en distintos componentes.Sí, frecuente.CSS Modules: cada .card se vuelve .card_a3f8b9x.
Estilos que afectan a otros componentes sin querer.Sí, 'cascada' en el CSS.CSS Modules o CSS-in-JS: estilos encapsulados.
No sabes qué estilos usa un componente.Tienes que buscar en el CSS global.Estilos en el mismo archivo que el componente.
Tema oscuro/claro dinámico.Requiere sobrescribir todo.CSS variables con custom properties.
Estilos condicionales (hover, active, disabled).Tienes que escribir las variantes.Styled Components: lógica en JS.
⭐ El problema es la CASCADA: El problema fundamental del CSS global es la CASCADA y el ORDEN: si .card está en tu styles.css y en otro componente también .card, el último gana. Y si modificas una clase global, puedes romper 20 componentes sin saberlo. La solución: ENCAPSULAR. Cada componente debe tener sus propios estilos, sin afectar a los demás. CSS Modules, Styled Components y Tailwind resuelven esto de 3 formas diferentes. Ninguno es 'mejor': depende de tu equipo y proyecto.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.3.2 CSS Modules: encapsulación nativa y simple

CSS Modules es la solución más SIMPLE y NATIVA: creas un archivo .module.css junto a tu componente, importas los estilos como objeto, y React los encapsula automáticamente con un hash único. Es la elección de muchos equipos por su simplicidad: sientes que escribes CSS normal, pero sin colisiones. Funciona con Vite sin configuración adicional.

// CharacterCard.module.css: estilos encapsulados
.card {
  background: white;
  border: 1px solid #e0e0e0;
  border-radius: 12px;
  padding: 16px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  transition: transform 0.2s;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.imagen {
  width: 100%;
  border-radius: 8px;
  margin-bottom: 12px;
}
.nombre {
  font-size: 1.25rem;
  font-weight: 600;
  color: #222;
  margin: 0 0 8px 0;
}
.especie {
  color: #666;
  font-size: 0.875rem;
}
.estadoVivo { color: #4caf50; font-weight: 500; }
.estadoMuerto { color: #f44336; font-weight: 500; }
.estadoDesconocido { color: #9e9e9e; font-style: italic; }

// CharacterCard.jsx: usando los estilos
import styles from './CharacterCard.module.css';

function CharacterCard({ character }) {
  const claseEstado = {
    Alive: styles.estadoVivo,
    Dead: styles.estadoMuerto,
    unknown: styles.estadoDesconocido,
  }[character.status] || styles.estadoDesconocido;

  return (
    <div className={styles.card}>  {/* card_a3f8b9x automáticamente */}
      <img src={character.image} alt={character.name} className={styles.imagen} />
      <h3 className={styles.nombre}>{character.name}</h3>
      <p className={styles.especie}>{character.species}</p>
      <p className={claseEstado}>{character.status}</p>
    </div>
  );
}

CSS Modules: estilos encapsulados con hash automático

⭐ CSS Modules: simple y poderoso: CSS Modules funciona automáticamente con Vite: cualquier archivo .module.css se trata como módulo. Las clases que exportas se vuelven un objeto con nombres únicos (.card -> .CharacterCard_card__a3f8b9x). Esto elimina el 100% de las colisiones. Para los nombres de clases, React/Vite usa convenciones como camelCase (borderRadius en vez de border-radius) o kebab-case, según el linter. Es la solución más PROFESIONAL para equipos que quieren CSS simple sin aprender un nuevo paradigma.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.3.3 Styled Components: CSS-in-JS con lógica

Styled Components lleva el CSS a JavaScript: defines los estilos en el MISMO archivo del componente, usando template literals, con la posibilidad de usar PROPS para estilos dinámicos. Es la elección cuando tus componentes tienen muchos estados visuales (primario/secundario/deshabilitado, hover/active, tamaños sm/md/lg). El trade-off: curva de aprendizaje ligeramente mayor y bundle más grande.

import styled, { css } from 'styled-components';

// Estilos con props dinámicos
const Button = styled.button`
  background: ${props => props.variant === 'primary' ? '#0066ff' : 'white'};
  color: ${props => props.variant === 'primary' ? 'white' : '#333'};
  border: 1px solid #ccc;
  padding: ${props => props.size === 'small' ? '6px 12px' : '10px 20px'};
  border-radius: 8px;
  font-size: ${props => props.size === 'small' ? '0.875rem' : '1rem'};
  cursor: pointer;
  transition: all 0.2s;
  
  &:hover { opacity: 0.85; transform: translateY(-1px); }
  &:active { transform: translateY(0); }
  &:disabled { opacity: 0.5; cursor: not-allowed; }
  
  ${props => props.fullWidth && css`
    width: 100%;
  `}
`;

// Uso con props: cada variante es un botón diferente
function App() {
  return (
    <div>
      <Button variant="primary" size="small">Guardar</Button>
      <Button variant="primary" size="large" fullWidth>Crear cuenta</Button>
      <Button variant="secondary" disabled>Cancelar</Button>
    </div>
  );
}

// HEREDAR estilos: extensión de un componente existente
const PrimaryButton = styled(Button)`
  background: linear-gradient(135deg, #0066ff, #00aaff);
  box-shadow: 0 2px 8px rgba(0, 102, 255, 0.3);
`;

// THEMING con ThemeProvider: variables globales desde JS
import { ThemeProvider } from 'styled-components';

const temaClaro = { primary: '#0066ff', bg: 'white', text: '#222' };
const temaOscuro = { primary: '#4d9aff', bg: '#1a1a1a', text: '#eee' };

function App() {
  const [oscuro, setOscuro] = useState(false);
  return (
    <ThemeProvider theme={oscuro ? temaOscuro : temaClaro}>
      <Boton>Click me</Boton>
    </ThemeProvider>
  );
}

Styled Components: CSS en JS con props dinámicos

⭐ Styled Components: ideal para variantes: Styled Components es IDEAL cuando tienes muchos componentes con VARIANTES: un Botón con primary/secondary/tertiary, un Card con sm/md/lg, un Alert con success/warning/error. La lógica de las variantes vive en el componente, no en 50 selectores CSS. El trade-off: bundle más grande (cada styled component genera CSS), curva de aprendizaje mayor, y los classNames se generan automáticamente (no los puedes copiar del DevTools directamente). En 2024-2025 ha perdido popularidad frente a Tailwind, pero sigue siendo muy usado en proyectos enterprise.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.3.4 Tailwind CSS: utility-first, el más popular de 2025

Tailwind CSS es el framework de estilos más popular de 2024-2025: en vez de escribir CSS, aplicas clases pequeñas (utility classes) directamente en el JSX. bg-blue-500 text-white p-4 rounded-lg es todo lo que necesitas para un botón azul. La ventaja: desarrollo RÁPIDO, sin cambiar de archivo. La desventaja: el JSX se vuelve verboso con muchas clases. Es cuestión de gustos: muchos developers lo aman, otros lo odian.

// ANTES (con CSS Modules o CSS normal):
// 1. Escribir un archivo CSS
// 2. Importar el archivo
// 3. Aplicar className
// 4. Pensar qué nombre de clase
// Total: 4 pasos

// CON TAILWIND: solo JSX
function App() {
  return (
    <div className="min-h-screen bg-gray-50 p-8">
      <h1 className="text-4xl font-bold text-gray-900 mb-6">
        🌍 Buscador de Países
      </h1>
      
      <div className="flex gap-4 mb-8">
        <input
          type="text"
          placeholder="Buscar país..."
          className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
        />
        <button className="px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors">
          Buscar
        </button>
      </div>
      
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        <article className="bg-white rounded-xl shadow-md overflow-hidden hover:shadow-lg transition-shadow">
          <img src="https://flagcdn.com/w320/ar.png" alt="Argentina" className="w-full h-48 object-cover" />
          <div className="p-4">
            <h2 className="text-xl font-semibold text-gray-900 mb-2">Argentina</h2>
            <p className="text-gray-600 text-sm mb-1"><strong>Capital:</strong> Buenos Aires</p>
            <p className="text-gray-600 text-sm"><strong>Población:</strong> 45,808,747</p>
          </div>
        </article>
      </div>
    </div>
  );
}

// Instalar en Vite:
// npm install -D tailwindcss postcss autoprefixer
// npx tailwindcss init -p
// Configurar tailwind.config.js con los paths
// Agregar directivas en src/index.css:
// @tailwind base;
// @tailwind components;
// @tailwind utilities;

Tailwind CSS: utility classes directamente en JSX

⭐ Tailwind: amado y odiado, pero rápido: Tailwind es controversial: muchos lo aman (velocidad de desarrollo brutal), otros lo odian (JSX verboso, difícil de leer con muchas clases). La realidad: para apps pequeñas o prototipos, es INCREÍBLE. Para apps grandes con muchos componentes compartidos, puede ser menos mantenible que CSS Modules. Mi recomendación: empieza con Tailwind para prototipos, y si tu equipo lo prefiere, quédate con él. Si no, CSS Modules es más estándar y simple. Styled Components solo si tienes muchas variantes dinámicas. Las 3 son válidas.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.3.5 Design system: variables CSS y tokens compartidos

Un design system es un CONJUNTO de tokens compartidos (colores, fuentes, espacios, sombras) que se usan en TODOS los componentes. Es la diferencia entre una UI consistente y profesional, y una que parece hecha por 5 personas diferentes. La implementación moderna: CSS custom properties (--color-primario, --esp-md) que se definen en :root y se usan en cualquier componente. Funciona con cualquier técnica de estilos (CSS Modules, Styled Components, Tailwind).

// styles/tokens.css (o index.css): variables globales
:root {
  /* PALETA DE COLORES (basada en tu marca) */
  --color-primario: hsl(220, 70%, 50%);
  --color-primario-hover: hsl(220, 70%, 40%);
  --color-secundario: hsl(180, 60%, 45%);
  --color-exito: hsl(140, 50%, 45%);
  --color-error: hsl(0, 70%, 50%);
  --color-warning: hsl(35, 80%, 50%);
  
  /* TIPOGRAFÍA */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.875rem;
  --text-4xl: 2.25rem;
  
  /* ESPACIOS (escala de 4px) */
  --esp-1: 0.25rem;  /* 4px */
  --esp-2: 0.5rem;   /* 8px */
  --esp-3: 0.75rem;  /* 12px */
  --esp-4: 1rem;     /* 16px */
  --esp-6: 1.5rem;   /* 24px */
  --esp-8: 2rem;     /* 32px */
  --esp-12: 3rem;    /* 48px */
  --esp-16: 4rem;    /* 64px */
  
  /* RADIOS */
  --radio-sm: 4px;
  --radio-md: 8px;
  --radio-lg: 16px;
  --radio-full: 9999px;
  
  /* SOMBRAS */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
  
  /* TRANSICIONES */
  --trans-fast: 0.15s ease;
  --trans-base: 0.25s ease;
  --trans-slow: 0.4s ease;
}

/* Tema oscuro: simplemente redefine las variables */
[data-theme='oscuro'] {
  --color-primario: hsl(220, 70%, 60%);
  --color-fondo: hsl(220, 15%, 12%);
  --color-texto: hsl(0, 0%, 95%);
}

/* Uso en cualquier componente (CSS Modules): */
.button {
  background: var(--color-primario);
  color: white;
  padding: var(--esp-3) var(--esp-6);
  border-radius: var(--radio-md);
  box-shadow: var(--shadow-sm);
  transition: all var(--trans-base);
}

/* Cambio de tema = automático en TODA la app */
function ToggleTema() {
  const [oscuro, setOscuro] = useState(false);
  useEffect(() => {
    document.documentElement.dataset.theme = oscuro ? 'oscuro' : 'claro';
  }, [oscuro]);
  return <button onClick={() => setOscuro(!oscuro)}>Cambiar tema</button>;
}

Design system: variables CSS globales con tema claro/oscuro

⭐ Custom properties: el estándar moderno: Las custom properties (variables CSS) son LA FORMA MODERNA de hacer design systems. Funcionan con cualquier técnica de estilos (CSS Modules, Tailwind, Styled Components), se pueden cambiar dinámicamente con JavaScript (tema oscuro), y son MUY performantes (el navegador las maneja nativamente). Antes existían preprocesadores como Sass con $variables, pero las CSS variables son la opción ESTÁNDAR desde 2017. Si aprendes una sola cosa de design systems, que sea: usa custom properties en :root.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

3.3.6 Proyecto integrador: estilizar la app de países con las 3 técnicas

Para consolidar, vamos a aplicar las 3 técnicas a la misma app: el buscador de países con REST Countries del Tema 3.2. Vas a ver el mismo componente (PaisCard) escrito 3 veces: con CSS Modules, Styled Components, y Tailwind. La comparación directa te dejará claro cuál prefieres para cada caso. Después, en tu proyecto, eliges la que mejor se adapte a tu equipo.

El MISMO componente con las 3 técnicas

// ==================================================================
// OPCIÓN 1: CSS MODULES (archivo separado)
// ==================================================================
// PaisCard.module.css
.card {
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  overflow: hidden;
  transition: transform 0.2s;
}
.card:hover { transform: translateY(-4px); }
.flag { width: 100%; height: 200px; object-fit: cover; }
.body { padding: 1rem; }
.name { font-size: 1.25rem; font-weight: 600; margin: 0 0 0.5rem; }
.info { color: #666; font-size: 0.875rem; margin: 0.25rem 0; }

// PaisCard.jsx (CSS Modules)
import styles from './PaisCard.module.css';
function PaisCard({ pais }) {
  return (
    <article className={styles.card}>
      <img src={pais.flags.png} alt={pais.name.common} className={styles.flag} />
      <div className={styles.body}>
        <h2 className={styles.name}>{pais.name.common}</h2>
        <p className={styles.info}><strong>Capital:</strong> {pais.capital?.[0]}</p>
        <p className={styles.info}><strong>Población:</strong> {pais.population.toLocaleString()}</p>
      </div>
    </article>
  );
}

// ==================================================================
// OPCIÓN 2: STYLED COMPONENTS (todo en JS)
// ==================================================================
import styled from 'styled-components';

const Card = styled.article`
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  overflow: hidden;
  transition: transform 0.2s;
  &:hover { transform: translateY(-4px); }
`;
const Flag = styled.img`
  width: 100%;
  height: 200px;
  object-fit: cover;
`;
const Body = styled.div`
  padding: 1rem;
`;
const Name = styled.h2`
  font-size: 1.25rem;
  font-weight: 600;
  margin: 0 0 0.5rem;
`;
const Info = styled.p`
  color: #666;
  font-size: 0.875rem;
  margin: 0.25rem 0;
`;

function PaisCard({ pais }) {
  return (
    <Card>
      <Flag src={pais.flags.png} alt={pais.name.common} />
      <Body>
        <Name>{pais.name.common}</Name>
        <Info><strong>Capital:</strong> {pais.capital?.[0]}</Info>
        <Info><strong>Población:</strong> {pais.population.toLocaleString()}</Info>
      </Body>
    </Card>
  );
}

El mismo componente con CSS Modules vs Styled Components

La versión con Tailwind (la más concisa)

// ==================================================================
// OPCIÓN 3: TAILWIND (utility classes en JSX)
// ==================================================================
function PaisCard({ pais }) {
  return (
    <article className="bg-white rounded-xl shadow-md overflow-hidden transition-transform hover:-translate-y-1">
      <img src={pais.flags.png} alt={pais.name.common} className="w-full h-48 object-cover" />
      <div className="p-4">
        <h2 className="text-xl font-semibold text-gray-900 mb-2">{pais.name.common}</h2>
        <p className="text-gray-600 text-sm mb-1"><strong>Capital:</strong> {pais.capital?.[0]}</p>
        <p className="text-gray-600 text-sm"><strong>Población:</strong> {pais.population.toLocaleString()}</p>
      </div>
    </article>
  );
}

// COMPARACIÓN VISUAL:
// CSS Modules: archivo .css separado, encapsulación con hash, escribir CSS normal
// Styled Components: 0 archivos CSS, todo en JS, ideal para props dinámicos
// Tailwind: 0 archivos CSS, utility classes en JSX, más rápido de escribir pero JSX verboso

Tailwind: el mismo componente, sin CSS, todo en JSX

⭐ Elige la que se adapte a tu equipo: Comparando las 3 versiones del mismo componente: CSS Modules tiene un archivo CSS separado (más ordenado, pero más lento de iterar). Styled Components no tiene CSS separado (todo en JS, ideal para componentes con muchos estados dinámicos). Tailwind no tiene CSS Y el JSX es más verboso (muchas clases). ¿Cuál elegir? Depende del proyecto: para prototipos y apps pequeñas, Tailwind es el más rápido. Para apps grandes con muchos componentes compartidos, CSS Modules o Styled Components. Para componentes con muchas variantes, Styled Components. Mi recomendación para empezar: Tailwind por velocidad, y si te estorba, cambia a CSS Modules. La elección es reversible, no te cases con ninguna.
🎬
Video del instructor

El administrador aún no ha insertado un video para esta sección.

📚 Contenido ampliado

Material adicional, referencias externas verificadas y ejemplos extendidos.

🤖 AI Mission

El estilo profesional se nota: UI coherente, no solo 'que se vea bien'.

Misión: Toma la app de países del Tema 3.2 y aplicationa las 3 técnicas de estilos en versiones diferentes: una página con Tailwind, otra con CSS Modules, otra con Styled Components. Compara la velocidad de desarrollo, la legibilidad del JSX, y el peso del bundle. Pídele a la IA su opinión. Anota: ¿cuál técnica se adapta mejor a TU forma de trabajar?

Pasos sugeridos

  1. Parte del proyecto paises-app del Tema 3.2.
  2. Crea 3 branches en Git: feat/tailwind, feat/css-modules, feat/styled-components.
  3. Branch feat/tailwind: instala tailwindcss, configura, estiliza PaisCard y ListaPaises solo con utility classes. Sin CSS files.
  4. Branch feat/css-modules: desinstala Tailwind. Crea *.module.css para cada componente. Estiliza con clases encapsuladas.
  5. Branch feat/styled-components: desinstala CSS Modules. Crea styled components. Pasa props a los estilos (variant, size).
  6. Compara los 3 branches: ¿cuál JSX es más legible? ¿cuál bundle es más pequeño? (npm run build, revisa dist/.)
  7. Compara la velocidad de desarrollo: ¿cuál te tomó menos tiempo?
  8. Pídele a la IA: 'Tengo el mismo componente con 3 técnicas de estilos. ¿Cuál recomendarías para una app de producción y por qué? Considera velocidad, mantenibilidad, y bundle size.'
  9. Merge la técnica que prefieras a main. Borra las otras 2 branches.
  10. Anota en tu cuaderno: 3 ventajas y 3 desventajas de cada técnica, según tu experiencia.

📓 Entregable: URL pública de la app con tu técnica favorita, capturas comparando los 3 enfoques, comparativa de bundle size, y media página de cuaderno con tu decisión razonada.

🚫 Errores típicos de razonamiento

Error 1: Usar CSS global sin convención de nombres.
Por qué: Si tienes .card en tu CSS global y otro componente también usa .card, el último gana. Sin convención (BEM, por ejemplo), es imposible saber qué clases colisionan. Solución: CSS Modules (encapsulación automática con hash), BEM (.card__title--active), o Tailwind (utility classes que no colisionan).
Error 2: Olvidar el box-sizing: border-box en el reset de Tailwind o CSS Modules.
Por qué: Sin el reset, un div con width: 200px, padding: 20px, border: 5px mide 250px de ancho total (el famoso 'mi layout se rompió'). Tailwind incluye el reset por defecto. CSS Modules necesitas poner * { box-sizing: border-box; } en el CSS global. Es el error #1 de principiantes en CSS.
Error 3: Mezclar Tailwind con CSS Modules en el mismo proyecto sin organización.
Por qué: Si usas Tailwind para algunos componentes y CSS Modules para otros, sin una convención clara, tu equipo se va a confundir: 'este botón usa bg-blue-500 o Button_primary?'. Decide UNA técnica principal y úsala consistentemente. Si necesitas ambas (ej: Tailwind para utilities + CSS Modules para componentes complejos), documenta CUÁNDO usar cada una.
Error 4: Estilos que dependen del orden de carga del CSS.
Por qué: Si tu styles.css tiene .button y luego otro componente agrega .button { background: red; }, el último gana. Esto es el 'CSS cascade hell'. Solución: CSS Modules (cada clase tiene hash único, no hay colisión) o specificity clara con BEM. El orden de carga NUNCA debería importar para el resultado.
Error 5: No definir custom properties para colores y fuentes.
Por qué: Si usas #0066ff en 20 componentes y quieres cambiarlo a #0077ff, tienes que buscar y reemplazar 20 veces. Con custom properties (--color-primario: #0066ff), cambias UNA línea y se actualiza en TODA la app. Es el equivalente a las variables en código, pero en CSS. Es la base de un design system mantenible.

🧪 Laboratorio práctico

La UI profesional se nota en los detalles: tipografía, espaciado, color.

Laboratorio: UI profesional con design system para la app de países

Objetivo: Tomar la app de países con REST Countries y darle un diseño PROFESIONAL: design system con custom properties, tema claro/oscuro, animaciones suaves, accesibilidad, y responsive. El resultado: una UI que parezca hecha por un estudio de diseño.

Pasos

  1. Crea un design system: styles/tokens.css con custom properties (colores, fuentes, espacios, sombras, radios).
  2. Implementa tema claro/oscuro: useState + dataset.theme en documentElement, redefine variables en [data-theme='oscuro'].
  3. Crea un componente ThemeToggle con icono sol/luna, persiste preferencia en localStorage.
  4. Estiliza PaisCard con hover elegante: scale, shadow, transition de 0.2s.
  5. Implementa skeleton loaders mientras se cargan los datos (mejor que spinners).
  6. Agrega animaciones de entrada: fade-in al cargar la lista, slide-up para cada card con stagger.
  7. Estiliza con Tailwind o CSS Modules (lo que prefieras).
  8. Verifica accesibilidad: contraste WCAG AA, focus visible, navegación con teclado.
  9. Prueba en 3 tamaños: móvil (375px), tablet (768px), desktop (1280px).
  10. Verifica en DevTools Performance: render inicial <1s, animaciones suaves (60fps).
  11. Sube a Vercel. Comparte URL.
  12. Haz commit: 'feat: UI profesional con design system, tema oscuro y animaciones'.

📓 Entregable: URL pública con UI profesional, capturas de: tema claro y oscuro, animaciones, responsive en 3 tamaños, Lighthouse Accessibility 95+, y commit en Git.

📓 Tu cuaderno: Anota pseudocódigo, diagramas, errores que encontraste y respuestas a "explica sin código". La escritura manual refuerza tu razonamiento. Tu profesor puede pedirte que subas fotos de páginas específicas.