🎯 Objetivo del tema
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.
El problema de las clases que colisionan y el orden de carga.
Cada componente tiene sus propios estilos sin colisiones.
Estilos dinámicos basados en props del componente.
Clases pequeñas combinadas en el JSX, sin escribir CSS.
Cómo crear un sistema coherente de colores, fuentes, y espacios.
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.
| Problema | CSS global | Solució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 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
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
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
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
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 verbosoTailwind: el mismo componente, sin CSS, todo en JSX
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
- Parte del proyecto paises-app del Tema 3.2.
- Crea 3 branches en Git: feat/tailwind, feat/css-modules, feat/styled-components.
- Branch feat/tailwind: instala tailwindcss, configura, estiliza PaisCard y ListaPaises solo con utility classes. Sin CSS files.
- Branch feat/css-modules: desinstala Tailwind. Crea *.module.css para cada componente. Estiliza con clases encapsuladas.
- Branch feat/styled-components: desinstala CSS Modules. Crea styled components. Pasa props a los estilos (variant, size).
- Compara los 3 branches: ¿cuál JSX es más legible? ¿cuál bundle es más pequeño? (npm run build, revisa dist/.)
- Compara la velocidad de desarrollo: ¿cuál te tomó menos tiempo?
- 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.'
- Merge la técnica que prefieras a main. Borra las otras 2 branches.
- 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
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).
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.
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.
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.
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
- Crea un design system: styles/tokens.css con custom properties (colores, fuentes, espacios, sombras, radios).
- Implementa tema claro/oscuro: useState + dataset.theme en documentElement, redefine variables en [data-theme='oscuro'].
- Crea un componente ThemeToggle con icono sol/luna, persiste preferencia en localStorage.
- Estiliza PaisCard con hover elegante: scale, shadow, transition de 0.2s.
- Implementa skeleton loaders mientras se cargan los datos (mejor que spinners).
- Agrega animaciones de entrada: fade-in al cargar la lista, slide-up para cada card con stagger.
- Estiliza con Tailwind o CSS Modules (lo que prefieras).
- Verifica accesibilidad: contraste WCAG AA, focus visible, navegación con teclado.
- Prueba en 3 tamaños: móvil (375px), tablet (768px), desktop (1280px).
- Verifica en DevTools Performance: render inicial <1s, animaciones suaves (60fps).
- Sube a Vercel. Comparte URL.
- 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.