Los componentes de React se estilizan con CSS normal. Dale una clase a los elementos con la prop className y escribe las reglas en una hoja de estilos, usa la prop style para valores que vienen de JavaScript, y recurre a CSS Modules o Tailwind cuando el proyecto crezca. El ejemplo de abajo define su CSS en un elemento <style> para poder ejecutarse en esta página; en un proyecto estaría en un archivo .css.
Cambia #2563eb por #16a34a en la regla .btn y el botón se vuelve verde. Todo lo que sabes de CSS se aplica: selectores, media queries, estados hover, animaciones.
className, no class
JSX se compila a JavaScript, donde class es una palabra reservada, así que React usa className, el nombre que el propio DOM usa para esa propiedad (element.className). El navegador igual recibe un atributo class normal. El mismo cambio de nombre se aplica a htmlFor en lugar de for en las etiquetas label. React 19 acepta class con una advertencia en desarrollo, pero className es el nombre correcto y el que usa todo el código.
className recibe una cadena, así que varias clases van separadas por espacios: className="btn btn-primary". Como el valor es JavaScript, se puede calcular.
Clases condicionales
Para agregar una clase solo en algunos casos, construye la cadena con un ternario o un template literal. Una barra de pestañas que resalta la pestaña seleccionada es el caso clásico.
Haz clic en Lessons y el subrayado se mueve, porque ahora solo la cadena de ese botón contiene active. Reviews recibe la clase disabled y el atributo disabled, así que se ve gris e ignora los clics. El template literal deja espacios extra cuando una condición es falsa, que los navegadores ignoran. Cuando un componente tiene muchas clases condicionales, un array queda más ordenado:
const classes = ['tab', isActive && 'active', isDisabled && 'disabled']
.filter(Boolean)
.join(' ');
Muchos proyectos usan el pequeño paquete clsx para lo mismo: clsx('tab', { active: isActive, disabled: isDisabled }).
La prop style
La prop style recibe un objeto de JavaScript, no una cadena. Por eso lleva llaves dobles: el par exterior abre una expresión de JavaScript, el par interior es el literal de objeto. Los nombres de las propiedades van en camelCase (backgroundColor, fontSize, borderRadius), y los valores son cadenas o números. Un número en una propiedad de longitud se convierte en píxeles, así que padding: 12 significa 12px.
Los estilos en línea brillan cuando un valor se calcula al renderizar, como el ancho de una barra de progreso.
Haz clic en Add 10% hasta que la barra se llene y se vuelve verde. Fíjate en width: percent + '%': un número solo significaría píxeles, así que las unidades distintas de px van en una cadena. Las propiedades sin unidad se quedan como números simples, como opacity: 0.8, zIndex: 2, flexGrow: 1 y lineHeight: 1.5 (que es un multiplicador, no píxeles).
Los estilos en línea tienen límites. No pueden expresar :hover, :focus, media queries ni animaciones con keyframes, y cada elemento lleva su propia copia de las reglas. Úsalos para los valores dinámicos y deja el resto en CSS.
Variables CSS desde props
Un buen punto intermedio es definir una propiedad personalizada de CSS en style y usarla desde tu hoja de estilos. Las reglas se quedan en CSS, estados hover incluidos, y solo el valor viene de JavaScript.
Elige un color nuevo y ambos botones lo siguen, y el relleno al pasar el cursor sigue funcionando porque es CSS normal. Los nombres de las propiedades personalizadas conservan sus guiones en el objeto style, así que van entre comillas: { '--accent': accent }.
Archivos CSS
En un proyecto real escribes los estilos en archivos .css y los importas desde el componente que los necesita. Con Vite, la importación es suficiente: la herramienta de build ve import './Button.css', agrega la hoja de estilos a la página durante el desarrollo y la empaqueta en un archivo CSS para producción. No hay ninguna exportación que usar; la importación existe solo por su efecto secundario.
// Button.jsx
import './Button.css';
export default function Button({ children }) {
return <button className="button">{children}</button>;
}
/* Button.css */
.button {
background: #2563eb;
color: white;
border-radius: 6px;
}
El problema es que cada hoja de estilos importada es global. Una regla .button en Button.css da estilo a todos los elementos con esa clase, en cualquier parte de la app, y dos archivos que definen .title se sobrescriben entre sí. Los equipos lo manejan con convenciones de nombres como BEM (.card__title), o con CSS Modules.
CSS Modules
Un archivo que termina en .module.css es un CSS Module. Importarlo te da un objeto que asocia cada nombre de clase que escribiste con un nombre único generado, así que una clase solo puede aplicarse donde la usas. Vite y Next.js lo admiten sin configuración.
// Card.jsx
import styles from './Card.module.css';
export default function Card({ title, children, featured }) {
return (
<section className={featured ? `${styles.card} ${styles.featured}` : styles.card}>
<h3 className={styles.title}>{title}</h3>
{children}
</section>
);
}
/* Card.module.css */
.card { border: 1px solid #ddd; border-radius: 10px; padding: 16px; }
.featured { border-color: #2563eb; }
.title { margin: 0; }
En el navegador la clase sale como algo parecido a _card_1x2ab_1. Otro componente puede definir su propio .title y los dos nunca chocan. Escribes CSS normal, con reglas hover y media queries incluidas, y por eso los CSS Modules son una opción habitual por defecto para los estilos de componentes.
Tailwind CSS con React
Tailwind sigue otro enfoque: en lugar de escribir reglas, combinas pequeñas clases utilitarias directamente en className. px-4 define el padding horizontal, rounded-lg redondea las esquinas, hover:bg-blue-700 cambia el fondo al pasar el cursor. Tailwind escanea tus archivos y genera CSS solo para las clases que usas.
Configurarlo en una app de React con Vite (Tailwind v4) lleva tres pasos:
npm install tailwindcss @tailwindcss/vite
// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
export default defineConfig({
plugins: [react(), tailwindcss()],
});
/* src/index.css, imported from main.jsx */
@import "tailwindcss";
Luego da estilo a los componentes con clases:
function UpgradeCard({ active }) {
return (
<div className="max-w-xs rounded-xl border border-gray-200 p-4">
<h3 className="mb-2 text-lg font-semibold">Pro plan</h3>
<button
className={`rounded-md px-4 py-2 text-white ${active ? 'bg-green-600' : 'bg-blue-600 hover:bg-blue-700'}`}
>
{active ? 'Active' : 'Upgrade'}
</button>
</div>
);
}
Tailwind no está disponible en los editores de esta página, así que se muestra como código estático. Las clases condicionales funcionan exactamente como en el ejemplo de pestañas de arriba, pero escribe cada nombre de clase completo: Tailwind encuentra las clases escaneando el texto de tu código, así que un nombre armado en tiempo de ejecución como `bg-${color}-600` nunca se genera.
Cuándo usar cuál
- Los archivos CSS normales sirven para apps pequeñas y estilos globales (resets, fuentes, layout de página). Vigila los choques de nombres de clase a medida que la app crece.
- Los CSS Modules sirven para la mayoría de los estilos de componentes: CSS normal, con ámbito automático, sin librería extra.
- Tailwind sirve para equipos que prefieren dar estilo en el marcado y quieren una escala de diseño compartida para espaciado y color.
- La prop
stylees para valores calculados al renderizar: un ancho, una posición, el color que eligió un usuario. Combínala con variables CSS para mantener las reglas hover y de media en CSS.
Puedes combinarlos. Una app típica tiene una hoja de estilos global, CSS Modules o Tailwind para los componentes, y algunos estilos en línea para los valores dinámicos. Las librerías CSS-in-JS como styled-components y Emotion son otra opción que encontrarás en código antiguo. La página sobre instalar React muestra cómo empezar un proyecto de Vite donde todo esto funciona.
Preguntas frecuentes
¿Por qué React usa className en lugar de class?
JSX se compila a JavaScript, y ahí class es una palabra reservada. React usa el nombre de la propiedad del DOM, className, que es también como se llama element.className en JavaScript puro. Igual se convierte en un atributo class normal en el HTML.
¿Cómo escribo estilos en línea en React?
Pasa un objeto a la prop style, con nombres de propiedad en camelCase: style={{ backgroundColor: 'teal', fontSize: 18 }}. Las llaves exteriores abren una expresión de JavaScript y las interiores son el objeto. Los números se convierten en píxeles en las propiedades que reciben una longitud.
¿Cómo agrego una clase de forma condicional en React?
Construye la cadena en JavaScript: className={isActive ? 'tab active' : 'tab'} o un template literal como className={`tab ${isActive ? 'active' : ''}`}. Para muchas condiciones, una pequeña utilidad como el paquete clsx lo mantiene legible.
¿Los archivos CSS en React son globales?
Sí. import './App.css' agrega la hoja de estilos a toda la página, así que sus nombres de clase pueden chocar con los de otros archivos. Los CSS Modules (App.module.css) renombran cada clase a un nombre único para que solo se aplique donde la importas.
¿Cómo uso Tailwind con React?
En un proyecto de Vite, instala tailwindcss y @tailwindcss/vite, agrega el plugin a vite.config.js y pon @import "tailwindcss"; al principio de tu archivo CSS principal. Luego escribe clases utilitarias en className, como className="px-4 py-2 rounded bg-blue-600 text-white".