Skip to content
This page has been auto-translated and may contain errors.View in English

Estilización de componentes

React no tiene un sistema de estilización propio. Escribes CSS ordinario, y JSX te da dos formas de aplicarlo a un elemento: un nombre de clase, que cubre casi todo, y una prop style para los pocos valores que una hoja de estilos no puede conocer de antemano. Este capítulo cubre ambos, junto con el patrón que está entre ellos: construir un nombre de clase a partir de props o estado, para que un elemento se restilice a medida que tus datos cambian.

Nombres de clase en JSX

class es una palabra reservada en JavaScript, así que JSX nombra el atributo className. El valor es una cadena ordinaria, y React la establece como el atributo class del elemento en el DOM, lo que significa que el CSS del otro lado se escribe exactamente como siempre.

jsx
function Avatar() {
  return <img className="avatar avatar--large" src={photo} alt="Ada Lovelace" />
}

Dos nombres de clase en una cadena, separados por un espacio, igual que en HTML. La regla camelCase de JSX se aplica a los nombres de atributo en tu marcado; los nombres de clase en sí son tuyos, así que kebab-case, BEM, o cualquier convención que tu hoja de estilos ya use se mantiene intacta.

Importar una hoja de estilos

Un archivo de componente trae el CSS que necesita con una importación simple al principio:

jsx
import './App.css'

export default function App() {
  return <h1 className="title">React Facts</h1>
}

No hay variable a la izquierda de esa importación, y el componente nunca lee el archivo. Es una instrucción para la herramienta de compilación: este módulo depende de ese CSS, así que inclúyelo. Vite inyecta las reglas en la página durante el desarrollo y emite un archivo .css real en una compilación de producción. Setting up a React project describe el mismo mecanismo que maneja las imágenes.

Dónde pones la importación es una cuestión de organización. Importar Button.css al principio de Button.jsx mantiene los estilos junto al componente que los usa. Las reglas en sí se mantienen globales: un selector .title coincide con cada elemento que lleve esa clase en cualquier lugar de la página, sea cual sea el archivo que la importó. Convenciones de nombrado como BEM existen para mantener ese espacio de nombres compartido manejable, y CSS Modules y librerías CSS-in-JS cierran las reglas por ti una vez que una aplicación crece más allá de la disciplina de nombrado sola.

Version note

React 19 added support for rendering <link rel="stylesheet"> and <style> from inside a component, with a precedence prop that lets React hoist them into the document head and order them predictably. Earlier versions needed a library such as react-helmet. The build-tool import above behaves the same way in every version.

Nombres de clase que dependen de datos

className se encuentra dentro de llaves como cualquier otra expresión JSX, así que la cadena puede ser computada. Tomemos un juego de dados donde cada dado puede retenerse entre lanzamientos. Si un dado está retenido vive en el estado más arriba y llega aquí como una prop:

jsx
function Die({ value, isHeld }) {
  return (
    <button className={`die ${isHeld ? 'die--held' : ''}`}>
      {value}
    </button>
  )
}

Cada dado obtiene die. Uno retenido también obtiene die--held. La hoja de estilos contiene los colores y bordes reales, y el único trabajo del componente es decidir qué clase aplica. Cuando isHeld cambia, React actualiza el atributo class y el navegador redibuja. Nada dentro del componente sabe qué aspecto tiene "retenido", así que esa apariencia puede cambiar sin que este archivo cambie en absoluto.

Un detalle: cuando isHeld es false, el template literal produce "die " con un espacio al final. Los navegadores lo ignoran, aunque se ve desordenado en devtools. Un array lo maneja más limpiamente y escala pasada una condición:

jsx
const classes = ['die', isHeld && 'die--held'].filter(Boolean).join(' ')

return <button className={classes}>{value}</button>

isHeld && 'die--held' se evalúa a false cuando la condición falla, filter(Boolean) lo elimina, y join(' ') pone un espacio simple entre lo que sobrevive.

La prop style

La prop style toma un objeto JavaScript. Los nombres de propiedad son las versiones camelCase de los de CSS, y las llaves dobles son la ranura de expresión habitual con un objeto literal dentro:

jsx
<div style={{ backgroundColor: 'darkslateblue', paddingTop: 12 }}>
  Styled inline
</div>

background-color se convierte en backgroundColor y padding-top se convierte en paddingTop. Los valores generalmente son cadenas. Pasa un número simple y React añade px para propiedades que toman una longitud, así que paddingTop: 12 se renderiza como padding-top: 12px. Las propiedades que CSS trata como sin unidad mantienen el número crudo: lineHeight, opacity, flexGrow, zIndex y fontWeight pasan directamente. Cualquier otra unidad debe escribirse como una cadena, como en width: '60%' o margin: '2rem'.

La mayoría de la estilización pertenece a una hoja de estilos, donde una clase puede llevar una docena de declaraciones y ser compartida por una docena de elementos. La prop style se gana su lugar cuando un valor solo se conoce en tiempo de ejecución y CSS no tiene forma de alcanzarlo:

jsx
function ProgressBar({ percent }) {
  return (
    <div className="progress">
      <div className="progress__fill" style={{ width: `${percent}%` }} />
    </div>
  )
}

El color del relleno, altura, transición y radio de esquina están todos en .progress__fill. El único valor que la hoja de estilos no puede conocer es el ancho, porque viene de datos, así que ese único valor va en línea. Una imagen de fondo cuya URL llega de una API tiene la misma forma:

jsx
<div className="hero" style={{ backgroundImage: `url(${photo.url})` }} />

El tamaño, posicionamiento y superposición se quedan en .hero, y solo la URL viene a través de la prop.

Los estilos en línea también tienen un techo duro. Un objeto style describe las declaraciones de un elemento, así que no hay forma de expresar un estado hover, una media query, o una animación keyframe en él. Esas pertenecen a CSS sin importar qué tan dinámica sea el resto de la estilización.

Un único ternario dentro de un template literal se lee bien. Dos o tres apilados en la misma cadena se convierten en una pared de comillas invertidas y signos de interrogación que nadie quiere editar. Una vez que un componente tiene más de una condición, mueve la computación arriba del return y deja que la forma de array haga el trabajo:

jsx
function Button({ variant, size, isDisabled, isLoading, children }) {
  const classes = [
    'btn',
    `btn--${variant}`,
    `btn--${size}`,
    isDisabled && 'btn--disabled',
    isLoading && 'btn--loading',
  ]
    .filter(Boolean)
    .join(' ')

  return (
    <button className={classes} disabled={isDisabled}>
      {children}
    </button>
  )
}

Cada línea es una decisión, así que añadir una cuarta condición significa añadir una línea en lugar de reestructurar una expresión, y el JSX se mantiene legible porque el atributo es un identificador simple.

Cuando varias clases se excluyen mutuamente, un objeto de búsqueda vence a una cadena de ternarios:

jsx
const statusClasses = {
  idle: 'card--idle',
  loading: 'card--loading',
  error: 'card--error',
}

const classes = ['card', statusClasses[status]].filter(Boolean).join(' ')

Un status no reconocido produce undefined, que filter(Boolean) elimina, así que el elemento vuelve a la clase base card en lugar de renderizar el texto literal "undefined" en el atributo.

Una vez que estés escribiendo esto en cada componente, los paquetes clsx y classnames hacen el mismo trabajo en unos pocos cientos de bytes, con una forma de objeto para las partes condicionales: clsx('btn', { 'btn--disabled': isDisabled }). Son una conveniencia sobre la versión de array arriba, que vale la pena entender primero.

La prop style toma un objeto porque eso se mapea directamente en cómo el DOM expone la estilización. Cada elemento tiene una propiedad style, un CSSStyleDeclaration, cuyos miembros son los nombres de propiedad CSS en camelCase: node.style.backgroundColor. React asigna esos miembros individualmente. Una cadena significaría escribir cssText, que reanaliza y reemplaza cada declaración en el elemento cada vez que cualquiera de ellas cambia.

Asignar por propiedad es lo que hace que las actualizaciones de estilo sean comparables. React compara el objeto de estilo anterior con el siguiente clave por clave: las claves cuyos valores coinciden se dejan solas, las claves cambiadas se escriben, y las claves que desaparecieron se restablecen a una cadena vacía. Solo las propiedades que realmente se movieron tocan el DOM. Una cadena CSS no ofrece forma de expresar esa actualización parcial.

Las propiedades con prefijo de proveedor siguen la misma regla camelCase con una primera letra mayúscula, como en WebkitLineClamp, con ms como la excepción que se mantiene en minúsculas (msOverflowStyle). React no añade prefijos en tu nombre.

Las propiedades personalizadas son el único caso donde la clave se mantiene exactamente como CSS la escribe:

jsx
<div className="card" style={{ '--accent': team.color }}>

React ve el -- inicial y lo enruta a través de setProperty, pasando el valor verbatim. No se añade px, así que las unidades son tuyas. Este es un puente útil entre los dos enfoques: establece una propiedad personalizada en línea desde datos y deja que la hoja de estilos la consuma en tantas reglas, pseudo-clases y media queries como le gusten.

El costo de identidad vale la pena conocer. Un objeto de estilo en línea escrito directamente en el JSX, como en los ejemplos arriba, asigna un objeto fresco cada renderizado. En un elemento host eso no cuesta nada medible, ya que React compara los valores en lugar de la referencia y no escribe nada cuando coinciden. Comienza a importar cuando el objeto cruza un límite de componente: pasa un objeto de estilo recién construido a un hijo envuelto en memo, y la comparación de props superficial ve una nueva referencia cada renderizado y re-renderiza el hijo de todas formas, derrotando la memorización. Una cadena className no tiene ese problema, porque las cadenas se comparan por valor.

Las correcciones son ordinarias: eleva objetos de estilo estáticos a alcance de módulo para que la referencia se cree una vez, y envuelve los dinámicos en useMemo con clave en los valores de los que se derivan (ver Hooks). Ambas valen la pena solo donde un perfil muestra que el re-renderizado cuesta algo, y recurrir a un nombre de clase elimina la pregunta por completo. Las clases escalan mejor en el navegador de todas formas, ya que un conjunto de reglas coincidente se comparte entre cada elemento que lleva la clase, mientras que las declaraciones en línea se repiten en cada elemento e inflan el HTML renderizado en el servidor en consecuencia.

JunoReach for a class first, inline style second La estilización en React es el CSS que ya escribes, adjunto a través de className porque class es una palabra reservada en JavaScript. Importa tu hoja de estilos al principio del archivo, pon tus reglas en ella, y deja que el componente decida qué clase aplica construyendo la cadena a partir de props o estado. Guarda la prop style para valores que tu archivo CSS no puede conocer de antemano, como un ancho que viene de datos.
JunoReach for a class first, inline style secondclassName es una ranura de expresión, así que un template literal cubre una condición y un array con filter(Boolean).join(' ') cubre el resto. Compútalo arriba del return para que el JSX se mantenga escaneable, y usa un objeto de búsqueda cuando las clases se excluyen mutuamente. La prop style toma claves en camelCase, añade px a números simples para propiedades de longitud, y deja solas las sin unidad como opacity. Se gana su lugar para valores en tiempo de ejecución, y no puede expresar estados hover o media queries en absoluto.
JunoReach for a class first, inline style second El objeto style existe porque se mapea a CSSStyleDeclaration, que deja a React comparar y escribir propiedades individuales en lugar de reparsar una cadena cssText. Las claves que comienzan con -- van a través de setProperty sin cambios, lo que hace una propiedad personalizada el puente más limpio de datos en tiempo de ejecución en una hoja de estilos. La trampa es la identidad: un objeto literal fresco cada renderizado es invisible en un elemento host y fatal para un límite memo, así que eleva objetos estáticos a alcance de módulo, envuelve los dinámicos en useMemo, y prefiere un nombre de clase donde uno funcionará.

Próximo: Props, donde los componentes comienzan a recibir datos de su padre.