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

Estilizar componentes

React no tiene un sistema de estilos propio. Escribes CSS ordinario, y JSX te da dos formas de adjuntarlo 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 ambas, junto con el patrón que se encuentra entre ellas: construir un nombre de clase a partir de props o estado, para que un elemento se restilifique conforme 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 simple, 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 lo fue.

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 sin cambios.

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 en la parte superior de Button.jsx mantiene los estilos junto al componente que los usa. Las reglas en sí siguen siendo globales: un selector .title coincide con cada elemento que lleva esa clase en cualquier parte de la página, sin importar qué archivo la importó. Convenciones de nombres como BEM existen para mantener ese espacio de nombres compartido manejable, y CSS Modules y librerías de CSS-in-JS limitan las reglas para ti una vez que una aplicación crece más allá de la disciplina de nombres solamente.

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 está dentro de llaves como cualquier otra expresión JSX, así que la cadena puede ser computada. Toma un juego de dados donde cada dado puede ser mantenido entre tiradas. Si un dado está mantenido vive en state 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 mantenido también obtiene die--held. La hoja de estilos contiene los colores y bordes reales, y el único trabajo del componente es decidir cuál clase se aplica. Cuando isHeld cambia, React actualiza el atributo de clase y el navegador repinta. Nada dentro del componente sabe qué aspecto tiene "mantenido", 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 más allá de 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 descarta, 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 CSS, y las llaves dobles son el slot de expresión usual con un literal de objeto 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 usualmente 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 unidades mantienen el número crudo: lineHeight, opacity, flexGrow, zIndex y fontWeight todas pasan directamente. Cualquier otra unidad tiene que escribirse como una cadena, como en width: '60%' o margin: '2rem'.

La mayoría del estilizado 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 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 todos viven 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 inline. 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 permanecen en .hero, y solo la URL viene a través de la prop.

Los estilos inline también tienen un techo duro. Un objeto de estilo 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ámico sea el resto del estilizado.

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 backticks 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 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 permanece 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 retrocede 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 cientos de bytes, con una forma de objeto para las partes condicionales: clsx('btn', { 'btn--disabled': isDisabled }). Son una conveniencia sobre la versión array arriba, que vale la pena entender primero.

La prop style toma un objeto porque se mapea directamente a cómo el DOM expone el estilizado. Cada elemento tiene una propiedad style, una CSSStyleDeclaration, cuyos miembros son los nombres de propiedad CSS camelCase: node.style.backgroundColor. React asigna esos miembros individualmente. Una cadena significaría escribir cssText, que reparsa 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 llave por llave: las llaves cuyos valores coinciden se dejan solas, las llaves cambiadas se escriben, y las llaves que desaparecieron se reinician 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 capitalizada, como en WebkitLineClamp, con ms como la excepción que permanece minúscula (msOverflowStyle). React no añade prefijos en tu nombre.

Las propiedades personalizadas son el único caso donde la llave permanece 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 textualmente. No se añade px, así que las unidades son tuyas. Este es un puente útil entre los dos enfoques: establece una propiedad personalizada inline desde datos y deja que la hoja de estilos la consuma en tantas reglas, pseudo-clases y media queries como quieras.

El costo de identidad vale la pena conocerlo. Un objeto de estilo inline escrito directamente en el JSX, como en los ejemplos arriba, asigna un objeto fresco cada renderizado. En un elemento host eso 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 prop superficial ve una nueva referencia cada renderizado y re-renderiza el hijo de todos modos, derrotando la memoización. Una cadena className no tiene tal problema, porque las cadenas se comparan por valor.

Las soluciones son ordinarias: coloca objetos de estilo estático en el alcance del 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 derivan (ver Hooks). Ambas valen la pena solo donde un profile muestra el costo del re-renderizado en algo, y alcanzar un nombre de clase elimina la pregunta en conjunto. Las clases escalan mejor en el navegador de todos modos, ya que un conjunto de reglas coincidentes se comparte entre cada elemento que lleva la clase, mientras que las declaraciones inline se repiten en cada elemento e inflan el HTML renderizado en el servidor correspondiente.

JunoRecurre primero a una clase, estilo inline en segundo lugar El estilizado 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 en la parte superior del archivo, pon tus reglas en ella, y deja que el componente decida cuál clase se 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.

JunoRecurre primero a una clase, estilo inline en segundo lugarclassName es un slot 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 permanezca escaneable, y usa un objeto de búsqueda cuando las clases se excluyen mutuamente.

La prop style toma llaves camelCase, añade px a números simples para propiedades de longitud, y deja los sin unidades como opacity solas. Gana su lugar para valores de tiempo de ejecución, y no puede expresar estados hover o media queries en absoluto.

JunoRecurre primero a una clase, estilo inline en segundo lugar El objeto style existe porque se mapea a CSSStyleDeclaration, que permite a React diferenciar y escribir propiedades individuales en lugar de reparsar una cadena cssText. Las llaves que comienzan con -- van a través de setProperty sin cambios, lo que hace que una propiedad personalizada sea el puente más limpio desde datos de tiempo de ejecución a una hoja de estilos.

La trampa es la identidad: un literal de objeto fresco cada renderizado es invisible en un elemento host y fatal para un límite memo, así que coloca objetos estático en el alcance del módulo, useMemo los dinámicos, y prefiere un nombre de clase donde uno hará el trabajo.

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