En ciertas ocasiones (sobre todo cuando creamos aplicaciones web) trabajar con HTML y CSS puede resultarnos insuficiente para los requisitos de construcción de aplicaciones y nos podría venir bien la flexibilidad de Javascript para organizar estilos y reutilizarlos de una forma más cómoda y adaptada a las herramientas que ya usamos.
Históricamente, CSS-in-JS surge (sobre todo) por la imposibilidad de utilizar CSS local de forma nativa. Sin embargo, hoy en día ya existen mecanismos nativos para limitar el scope o alcance en CSS.
¿Qué es CSS-in-JS?
Como su propio nombre indica, se le llama CSS-in-JS a una forma de trabajar con Javascript para añadir, modificar y gestionar los estilos CSS de una aplicación web. En lugar de utilizar exclusivamente HTML y CSS, podemos ayudarnos de Javascript para modificar los estilos de múltiples formas.
En la siguiente tabla tienes un resumen de las diferentes formas de trabajar con CSS-in-JS actualmente. Cada una de estas categorías incluye varias tecnologías, librerías o herramientas que utilizan ese mecanismo de trabajo:
Enfoque | Descripción | |
---|---|---|
Mediante Javascript nativo (bases o fundamentos) | ||
API .style | Método nativo para modificar estilos en línea (atributo style del HTML). | |
API .className / .classList | Método nativo para modificar clases del HTML desde Javascript. | |
API DOM + string templates | Método nativo para modificar estilos mediante el DOM de Javascript. | |
API CSSStyleSheet de hojas de estilo | Método nativo para modificar hojas de estilos desde Javascript. | |
Mediante estrategias o librerías CSS-in-JS | ||
CSS Modules | Importar estilos preprocesados en .module.css vía PostCSS, LightningCSS... | |
CSS-in-JS con objetos | Librerías CSS-in-JS basada en objetos: VanillaExtract, PandaCSS... | |
CSS-in-JS con templates | Librerías CSS-in-JS basada en templates: Emotion, Linaria, ECCStatic, StyledComponents... | |
Módulos CSS nativos | Utilizar módulos CSS nativos sin necesidad de preprocesadores. |
En primer lugar, tenemos varios métodos nativos que son la base y fundamentos de trabajar con CSS mediante Javascript. Es conveniente tener una pequeña idea de como funcionan antes de ver el resto, ya que nos dan un contexto importantísimo para entenderlo mejor.
Mecanismos nativos
Tenemos varias APIs nativas en Javascript según lo que queramos manipular desde Javascript:
La API .style
En primer lugar, desde Javascript podemos utilizar la API .style
para acceder a [estilos en línea] de un elemento HTML. Observa el siguiente ejemplo, donde accedemos a la propiedad .style
de un elemento HTML y le añadimos 3 propiedades CSS:
const title = document.querySelector("h1.title");
title.style.backgroundColor = "indigo";
title.style.color = "white";
title.style.padding = "1rem";
<h1 class="title" style="background-color:indigo;color:white;padding:1rem">
La web de Manz.dev
</h1>
Como puedes ver, en el HTML resultante, estos estilos se añaden como inline styles. En Javascript, escribimos el nombre en camelCase (por ejemplo: backgroundColor
) y al pasarse a CSS se convierte automáticamente en kebab-case background-color
. Una forma alternativa a esta sintaxis sería acceder mediante la nomenclatura de corchetes title.style["background-color"]
, donde si podríamos escribir la propiedad literalmente como en CSS, o incluso acceder a una variable.
También tenemos un método .style.setProperty(property, value)
que nos permite asignar propiedades mediante una función, en lugar de asignaciones. Es especialmente útil para setear variables de CSS.
Más información sobre la API de estilos en línea en el artículo El objeto style.
La API .className
/ .classList
Otra forma de trabajar estilos con Javascript menos intrusiva es utilizar las API para modificar las clases HTML de un elemento. Seguimos gestionando los estilos al margen, mediante CSS (u otra estrategia que queramos), pero con Javascript podemos modificar y gestionar las clases de forma dinámica y manual.
Por un lado, tenemos .className
que permite añadir o reemplazar el atributo class
de un elemento HTML por completo. Y por otro lado, tenemos el objeto .classList
que incorpora una serie de métodos para añadir, eliminar, verificar o intercambiar clases, lo que nos ofrece mucha más potencia:
const title = document.querySelector("h1.title");
// Reemplaza la clase por completo
title.className = "title primary-color";
// Gestiona de forma más flexible (conserva las anteriores)
title.classList.remove("primary-color");
title.classList.add("secondary-color");
title.classList.toggle("large");
Más información sobre estos métodos de modificar clases en className o classList.
La API DOM + String templates
Otra forma de trabajar con CSS es haciendo uso del DOM, normalmente, junto a string templates para mayor comodidad. Esta forma se basa en añadir los estilos en HTML, a través de strings de Javascript:
const styles = /* css */`
h1 {
background: indigo;
color: white;
padding: 1rem;
}
`;
const container = document.querySelector(".container");
container.setHTMLUnsafe(/* html */`
<style>${styles}</style>
<h1>Título de esta sección</h1>
<p>Este es un ejemplo para Manz.dev</p>
`);
document.body.append(container);
Ten en cuenta que en este caso, el CSS que estamos añadiendo es global a toda la aplicación, por lo que pueden haber colisiones.
Si nos interesa evitar las colisiones CSS y no añadir el CSS de forma global, echa un vistazo al ShadowDOM declarativo o a métodos de CSS en WebComponents.
La API CSSStyleSheet
Otra forma más moderna y potente de trabajar con los estilos CSS es utilizar la API CSSStyleSheet
. Esta API nativa de Javascript nos permite crear hojas de estilos mediante objetos y métodos específicos, lo que nos proporciona mucho más control sobre nuestro código:
<h1 class="title">La web de Manz.dev</h1>
<script type="module">
const styles = new CSSStyleSheet();
await styles.replace(`h1 { background: indigo; color: white; padding: 1rem }`);
document.adoptedStyleSheets.push(styles);
</script>
También tenemos otros métodos interesantes, como insertRule()
, deleteRule()
, formas de acceder a las reglas con .rules
o .media
para las media queries, etc. Además, esta API, junto con otras características del lenguaje nos permite importar los estilos CSS de archivos externos, utilizando import attributes
, adoptar los estilos en un Shadow DOM (siendo estilos locales a esa sección y no globales), etc.
Si te interesa este tema, en el artículo CSSStyleSheet te explico como utilizar módulos CSS de forma nativa.
CSS Modules
Uno de los grandes problemas de trabajar con CSS es que a medida que se escribe código CSS, se hace más difícil de manejar y mantener debido a la cantidad de líneas. Utilizar @import
puede ayudar a dividir en varios archivos, pero tiene la problemática de que son peticiones extra en cliente.
Una estrategia llamada CSS Modules nos permite crear ficheros con extensión .module.css
que indica que no son grandes ficheros .css
, sino pequeños módulos con estilos que afectan solo a ciertas partes de la web. CSS Modules nos permite importarlo desde Javascript como un fichero .js
y obtener un objeto que tiene como propiedades los nombres de las clases utilizadas:
import classes from "./component.module.css";
const header = document.createElement("h1");
header.classList.add(classes.title);
header.textContent = "Títular de la sección";
document.body.append(header);
.title {
background: indigo;
color: white;
padding: 1rem;
}
Ten en cuenta, que internamente, CSS Modules crea un objeto asociando las clases utilizadas de cada módulo con una clase con un hash alfanumérico autogenerado. Esto nos permite que no podamos crear nombres de clases repetidas que accidentamente hagan colisionar diferentes estilos.
Si te interesa este enfoque, lo explico más en detalle en el artículo CSS Modules.
CSS-in-JS
Aunque muchas veces generalizamos y nos referimos a CSS-in-JS como cualquier forma de escribir CSS desde Javascript, realmente se le suele considerar CSS-in-JS a librerías Javascript que nos permiten trabajar con estructuras Javascript alternativas para escribir CSS.
La mayoría de librerías utilizan una de estas dos formas de escribir CSS-in-JS:
- 1️⃣ Mediante sintaxis de objetos
- 2️⃣ Mediante sintaxis de string templates
Existen librerías que soportan sólo una de las dos, y librerías que soportan ambas. Veamos las características principales de cada una:
Sintaxis de objetos
La primera de las sintaxis de CSS-in-JS se basa en utilizar un objeto Javascript, donde aprovechamos el nombre de las keys o propiedades para asignar el nombre de la propiedad CSS en camelCase. Habitualmente, se utiliza un ayudante importado desde la librería, que es el que realiza todo el trabajo interno de la librería.
Esta sintaxis se suele utilizar para aprovechar la compatibilidad de Javascript o Typescript con el autocompletado y tener una forma más cómoda de escribir estilos desde Javascript. Como desventaja, el código no es compatible con CSS y hay que reescribirlo para adaptarlo.
Como ejemplo, puedes ver el siguiente fragmento de código, correspondiente a la librería Vanilla Extract:
import { style } from '@vanilla-extract/css';
export const button = style({
padding: '0.5rem 1rem',
border: 'none',
borderRadius: '0.25rem',
color: 'white',
background: '#333'
});
import { button } from "./button.css.ts";
const button = document.createElement("button");
button.classList.add(className);
document.body.append(button);
Ejemplos de estas librerías que trabajan con objetos son Vanilla Extract, JSS, Styled components o Panda CSS. Ten en cuenta que algunos de ellos, como por ejemplo Panda CSS, son compatibles también con la sintaxis de string templates que explicaremos en la siguiente sección.
Sintaxis de string templates
Otra sintaxis muy utilizada en el mundo de CSS-in-JS es utilizar string templates
para añadir el código CSS. Esto hace que sea algo más cómodo de añadir código, ya que es simplemente texto dentro de un
Observa este ejemplo, utilizando la librería Emotion. Utilizamos un helper css
mediante el que podemos añadir el código CSS y los editores aportan sintaxis de color:
import { css } from "@emotion/css";
export const styles = css`
background: indigo;
color: white;
padding: 1rem;
&:hover {
background: deeppink;
}
`;
import { styles } from "./button.css.js";
const button = document.createElement("button");
button.classList.add(styles);
document.body.append(button);
Ejemplos de librerías que utilizan la sintaxis de template strings son Emotion, Linaria o ECSStatic.