---
title: "@scope en CSS para WordPress + Tailwind: resuelve la especificidad sin romper el sitio"
url: "https://flowtitude.com/css-scope-wordpress-tailwind/"
type: "post"
published: "2025-09-30T08:56:53+02:00"
modified: "2025-10-06T09:38:53+02:00"
author: "Angel Julian"
---

Si llevas un tiempo construyendo sitios en WordPress, te habrá pasado: preparas una sección bonita con el editor de bloques o con tu constructor visual favorito, te aseguras de que la tipografía y los botones luzcan perfectos y… al cambiar de plantilla o activar un plugin, **algo se desordena**. Tu botón “primario” deja de ser primario en una página concreta, las tarjetas de una sección se distorsionan o el héroe del “Home” pierde su equilibrio. El origen de ese caos tiene nombre y apellidos: **la cascada de CSS** y su **especificidad**.

La **cascada** determina qué regla “gana” cuando dos reglas intentan estilar lo mismo; la **especificidad** es el “peso” de cada selector. Durante años, la salida de emergencia fue subir la especificidad (encadenar clases, usar combinadores más profundos o, en el peor de los casos, añadir `!important`). Eso funciona a corto plazo… y destroza la mantenibilidad a medio plazo. Lo sabes si alguna vez has pasado una tarde refrescando una página y preguntándote por qué una regla que “debería” ganar no gana.

Aquí entra **`@scope`**, una novedad del lenguaje CSS que te deja **acotar el alcance de tus estilos a una zona concreta del documento**. Dicho de forma sencilla: puedes decirle al navegador “**todo lo que escriba aquí solo se aplica dentro de esta sección**”, de modo que no tienes que luchar contra reglas globales ni contra el CSS de terceros que no controlas. Es una herramienta **nativa** de CSS (no es un hack, ni un preprocesador, ni una librería) y forma parte de la “caja de herramientas moderna” junto con cosas como `:where()` o `:is()`. La documentación de MDN lo resume muy bien: `@scope` permite “seleccionar elementos dentro de subárboles específicos del DOM, sin tener que escribir selectores demasiado específicos ni acoplarte de más a la estructura del HTML”. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/%40scope?utm_source=chatgpt.com)

Además, este no es un movimiento teórico: **los navegadores ya lo soportan**. Chrome/Edge y Safari integran `@scope`; Apple lo incluyó en **Safari 17.4** (marzo de 2024), y el soporte global roza los porcentajes que ya permiten usarlo con confianza si ofreces un fallback sencillo para Firefox mientras termina de estabilizarse su implementación. [Apple Developer+1](https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes?utm_source=chatgpt.com)

Y con **Tailwind v4** el encaje es todavía mejor: el framework ha pasado a una filosofía **CSS‑first**, donde defines **tokens** y utilidades directamente en tu hoja de estilos con directivas como `@theme` o `@utility`. Eso quiere decir que puedes combinar lo mejor de ambos mundos: **roles reutilizables** (por ejemplo, tu estilo de botón) y **ajustes locales por `@scope`** donde haga falta. La propia guía oficial de Tailwind v4 describe cómo los **theme variables** influyen en las utilidades disponibles y cómo se exponen como CSS variables estándar. [Tailwind CSS](https://tailwindcss.com/docs/theme?utm_source=chatgpt.com)

Este artículo te guía de principio a fin: **entenderás el problema**, aprenderás **qué hace `@scope`** (y qué no hace), verás **cómo convivir con WordPress** (editor y frontend), cómo **encajarlo con Tailwind v4**, cómo **probar y publicar con fallback**, y qué **errores típicos** evitar. Todo ello con un enfoque narrativo, ejemplos comentados y un pequeño caso real que puedes replicar en tu sitio.

## Antes de `@scope`: qué está pasando realmente cuando “no me hace caso”

Imagina que la web es una ciudad y el CSS son **ordenanzas**. Hay ordenanzas **generales** (se aplican en todas las calles) y ordenanzas **locales** (solo valen en un barrio). Cuando dos ordenanzas chocan, la policía (el navegador) aplica un criterio: **¿qué norma tiene más rango? ¿Cuál es más reciente? ¿Cuál es más específica?** En CSS esas preguntas se traducen a **cascada**, **origen de estilo** (usuario, autor, UA), **orden en el que se cargaron las hojas** y **especificidad** (IDs > clases/atributos > elementos). MDN lo explica con claridad y, además, añade una excepción muy útil: la pseudo‑clase `:where()` **siempre** tiene especificidad **0**, lo que la convierte en un comodín excelente para escribir selectores “ligeros” que no se peleen con otros. [MDN Web Docs+1](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Specificity?utm_source=chatgpt.com)

¿Qué suele ir mal en WordPress?

- **Plugins** con CSS “fuerte” que se cargan **después** de tu tema.
- **Constructores** y bloques que generan **múltiples clases** en un mismo nodo (a veces con cadenas de selectores muy profundas).
- **Capas y minificado**: si usas un optimizador que cambia el orden de los archivos, cambia la “prioridad” de tus reglas.
- **Necesidades locales**: quieres un héroe diferente en la home, o un “look” distinto en la landing de un curso, **sin que eso afecte a todo lo demás**.

La antigua receta para salir del apuro era **subir la especificidad** (más clases, más profundidad, `!important`). Es como subir el volumen de la tele para oír por encima del ruido: funciona un rato… y luego te duele la cabeza. `@scope` cambia el enfoque: **no subes el volumen**, sino que **te llevas la conversación a una sala aislada** donde no entra el ruido exterior.

## Qué es `@scope`, explicado sin tecnicismos y por qué reduce la fricción

**`@scope` es una regla de CSS** que crea un **“acotado”** para tus estilos. Dentro de ese bloque dices: “voy a considerar **este elemento como raíz** del ámbito (scope root) y, opcionalmente, voy a marcar un **límite inferior** para no entrar en X parte”. Todo lo que escribas dentro **solo** se aplica **dentro** de ese ámbito. Y si hay dos ámbitos que afectan a la misma cosa, el navegador resuelve el conflicto con una idea nueva y muy intuitiva: **gana el ámbito que está más cerca del elemento** (lo que se denomina “**scoping proximity**”). La documentación de Chrome Developers sobre `@scope` utiliza precisamente ejemplos de “raíz del ámbito” y “límite inferior” para ilustrarlo; es una buena forma mental de entenderlo: **hasta aquí sí, de aquí para abajo no**. [Chrome for Developers](https://developer.chrome.com/docs/css-ui/at-scope?utm_source=chatgpt.com)

Hay tres ideas que conviene anclar:

1. **No es magia**, sigue siendo CSS. Si una hoja se carga después con reglas más específicas para el mismo elemento y en el mismo ámbito, puede anular lo anterior. `@scope` ayuda dentro de su zona, pero **no salta** por encima del **orden de carga**.
2. **No reemplaza** la herencia. Si tú defines un `color` en una caja, ese `color` podría heredarse a descendientes que estén **fuera** de tu ámbito, dependiendo del HTML. Esto es importante cuando haces “donut scopes” (explicaremos esto en un momento).
3. **Te devuelve selectores cortos** y legibles. En vez de `.landing .content .grid .card h2.title` puedes escribir `h2` dentro de un ámbito razonable, y eso ya es un alivio enorme.

Veámoslo con un ejemplo muy corto, centrado en la idea de “solo aquí”:

```
<section class="scope-landing">
  <h2>Testimonios</h2>
  <p>Lo que dicen nuestros alumnos…</p>
</section>
```

```
@scope (.scope-landing) {
  h2 { font-size: clamp(1.5rem, 2cqw + 1rem, 2rem); }
  p  { opacity: .85; }
}
```

Aquí no he tenido que subir especificidad para ganar a otras reglas; **simplemente he acotado**. Y, lo más interesante, puedo escribir `h2` “a secas” sin miedo a que afecte a otros `h2` fuera de la landing. La idea viene recogida y estandarizada en la documentación de MDN, que describe `@scope` como un at‑rule para seleccionar en subárboles de DOM y controlar el alcance de forma local. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/%40scope?utm_source=chatgpt.com)

### El «donut scope»: incluir casi todo… menos algo

A veces quieres: “aplica dentro de `.landing`, **pero no dentro de `.sidebar`**”. Para eso, `@scope` permite un **segundo selector**: el límite inferior. El patrón queda así:

```
@scope (.landing) to (.landing .sidebar) {
  h3 { text-decoration: underline; }
}
```

Este “donut scope” es útil cuando una sección tiene una parte que trae sus propias reglas (por ejemplo, un widget de terceros) y no quieres tocarlas. La guía de Chrome sobre `@scope` lo explica con dibujos muy claros; en esencia, el **primer selector** define la **raíz** y el **segundo** el **tope** por abajo. [Chrome for Developers](https://developer.chrome.com/docs/css-ui/at-scope?utm_source=chatgpt.com)

## Navegadores y «plan B» responsable (progresive enhancement)

«¿Puedo usarlo ya?» es la pregunta razonable. La respuesta corta es **sí, con cabeza**. **Safari 17.4** lo trae desde marzo de 2024, **Chrome/Edge** también lo soportan, y el soporte global es alto, según **caniuse** (el rastreador de compatibilidad más consultado). En el momento de escribir este artículo, la cobertura reportada por caniuse para `@scope` está en torno al **85%** (puede variar con el tiempo). Para el resto (especialmente **Firefox**, que va avanzando pero todavía no tiene una versión estable con `@scope`), aplicaremos un **fallback simple** que te enseñaré a escribir en minuto y medio. [Apple Developer+1](https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes?utm_source=chatgpt.com)

El **fallback recomendado** es **duplicar solo las reglas esenciales** utilizando `:where()`. ¿Por qué `:where()`? Porque, como explica MDN, `:where()` **siempre tiene especificidad 0**, lo que evita que “pese” de más y te deja la puerta abierta para sobrescribir con facilidad cuando lo necesites. Esa propiedad la convierte en una compañera perfecta de `@scope` para navegadores que aún no lo soportan. [MDN Web Docs+1](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_cascade/Specificity?utm_source=chatgpt.com)

Ejemplo de **ámbito con fallback**:

```
/* Versión moderna (Chrome, Edge, Safari) */
@scope (.scope-hero) {
  h1 { font-size: clamp(2rem, 4cqw + 1rem, 3.5rem); }
  .wp-block-button__link { padding: .9rem 1.25rem; border-radius: .75rem; }
}

/* Fallback universal (incluye Firefox) */
:where(.scope-hero) h1 { font-size: clamp(2rem, 4cqw + 1rem, 3.5rem); }
:where(.scope-hero) .wp-block-button__link { padding: .9rem 1.25rem; border-radius: .75rem; }
```

Con esto cubres ambos frentes: **aprovechas** el modelo moderno cuando existe y **no dejas tirado** al visitante cuyo navegador sigue en transición. Y como `:where()` pesa 0, más adelante podrás anular ese estilo sin montañas de selectores.

## ¿Sustituye `@scope` a las «capas» y al orden de estilos?

No. `@scope` **no elimina** la importancia de tener un **orden razonable** de estilos. Piensa en **dos niveles**:

- **Nivel global** (el “plano general”): resets, tipografías base, colores, variables, **roles** de diseño (botones, tarjetas) y utilidades de tu sistema. Aquí el orden **sí** importa (quién se carga antes o después).
- **Nivel local** (el “primer plano”): ajustes puntuales **de una zona** (una landing, un héroe, un bloque específico) que quieres mantener bajo control **sin** pelearte con todo lo demás. Aquí `@scope` **brilla**.

Un proyecto saludable combina ambos. Las “capas” (en el sentido amplio de ordenar tus reglas y hojas) siguen teniendo utilidad para el **esqueleto global** de tu sitio; `@scope` entra como **herramienta quirúrgica** para que **afinemos lo local** sin sobredosis de especificidad. MDN también recuerda que `@scope` no cambia reglas como **herencia** o **orden de carga**: no es un “cheat code”, es una forma más clara de expresar alcance.

## Tailwind v4: por qué ahora encaja particularmente bien

Tal vez ya uses Tailwind y pienses: “¿cómo lo junto con esto?”. Desde la versión **v4**, Tailwind se ha hecho **más nativo al CSS**: defines **tokens** y comportamientos directamente en tu hoja con **`@theme`** (para colores, tamaños, tipografías…), creas **utilidades personalizadas** con **`@utility`** y puedes incluso gestionar **variantes** con `@variant`. No necesitas un `tailwind.config.js` para todo: la configuración **CSS‑first** es una realidad y, de hecho, la documentación oficial la explica con ejemplos (por ejemplo, cómo añadir un color y obtener automáticamente las utilidades `bg-*` y `text-*` relacionadas). [Tailwind CSS+1](https://tailwindcss.com/docs/theme?utm_source=chatgpt.com)

Eso nos da un **camino claro**:

1. Definimos nuestros **tokens** (colores de marca, radios, ritmos, fuentes) con `@theme`.
2. Creamos **roles** con `@utility` (por ejemplo, un `btn` base y sus variantes).
3. Aplicamos esos roles **dentro de un `@scope`** cuando necesitamos ajustar **solo una zona**.

En otras palabras, **Tailwind v4** se ocupa de la **consistencia y reuso** (rol → muchos lugares), y `@scope` se ocupa de **la acotación local** cuando lo necesitas. Si vienes de v3, la guía de actualización oficial te cuenta qué cambia, qué se mantiene y qué navegadores están cubiertos por v4 (Safari 16.4+, Chrome 111+, Firefox 128+), lo que refuerza la idea de que **v4 ya es un terreno estable** para “CSS‑first”. [Tailwind CSS](https://tailwindcss.com/docs/upgrade-guide?utm_source=chatgpt.com)

A lo largo del artículo usaremos ejemplos con `@import "tailwindcss";`, `@theme` y `@utility` porque eso te permite **copiar y pegar** en tu proyecto v4 sin fricción. Si estás en v3, puedes adaptar los estilos globales y aplicar el patrón de `@scope` igualmente; lo importante es la **acotación semántica**.

## WordPress: cómo hacer que el editor y el frontend «hablen el mismo idioma»

Uno de los retos históricos en WordPress es **alinear** lo que ves en el **editor** con lo que ve el usuario en el **frontend**. Por suerte, WordPress ofrece una vía oficial para cargar estilos en el editor:

- Declaras soporte con `add_theme_support( 'editor-styles' );`
- Encolas la hoja concreta con `add_editor_style( 'style-editor.css' );`

Ambas funciones están documentadas en el Handbook y en la referencia de funciones. El matiz interesante: en el editor, **WordPress envuelve** los estilos con una clase especial, **`.editor-styles-wrapper`**, para que **solo** afecten al lienzo del contenido (y no a la interfaz del editor). Por eso, cuando miras el inspector dentro del editor ves selectores que comienzan con `.editor-styles-wrapper`. Esa encapsulación está apuntada en documentación y discusiones técnicas del propio proyecto Gutenberg y de la comunidad. [WordPress Developer Resources+2WordPress Developer Resources+2](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-support/?utm_source=chatgpt.com)

**¿Qué nos aporta esto en la práctica?** Que podemos **anidar `@scope` dentro del contexto del editor** para hacer ajustes **solo** ahí, sin alterar el frontend. O, al revés, aplicar `@scope` en el frontend, sabiendo que en el editor habrá un “prefijo” que añade WordPress. Vamos a verlo materializado en un mini proyecto.

## Caso práctico completo: una «Landing de curso» con héroe, pricing y FAQ

Para que no quede en teoría, construiremos una mini landing con tres zonas: **héroe**, **pricing** y **FAQ**. Lo haremos primero sin `@scope`, luego con `@scope`, y por último añadiremos un **fallback** para Firefox. También mostraremos cómo se integra en el **editor de bloques**.

> **Suposiciones mínimas**
>
>
> - Tienes un tema o child theme donde puedes añadir una hoja `app.css` (o como la llames).
> - Estás usando **Tailwind v4** (la instalación puede ser con CLI o con tu build habitual).
> - Entiendes cómo crear una **página** y añadir bloques como **Párrafo**, **Encabezado**, **Botones** o **Grupo**.

### Prepara tus tokens y dos roles básicos

Creamos una hoja `app.css` y declaramos Tailwind, tokens y utilidades. Los **tokens** van a evitar que metas valores “a ojo” repetidos; los **roles** (`heading`, `btn`) te permitirán escribir HTML más claro.

```
/* app.css */
@import "tailwindcss";

/* 1) Tokens (Tailwind v4): expuestos como CSS variables y utilidades */
@theme {
  --font-sans: ui-sans-serif, system-ui, Inter, "Segoe UI", sans-serif;
  --color-brand-600: oklch(0.63 0.17 264);
  --color-brand-700: oklch(0.57 0.17 264);
  --radius-button: .75rem;
}

/* 2) Roles (utilidades personalizadas) */
@utility heading {
  @apply text-3xl md:text-5xl font-semibold tracking-tight;
}

@utility btn {
  @apply inline-flex items-center gap-2 px-5 py-3 rounded-[var(--radius-button)]
         font-medium transition-colors;
}

@utility btn-primary {
  @apply bg-brand-600 text-white hover:bg-brand-700;
}
```

> **Nota**: la documentación de Tailwind v4 explica cómo `@theme` crea **theme variables** que influyen en qué utilidades existen (`bg-brand-600`, `text-brand-700`, etc.), y cómo `@utility` te permite añadir utilidades personalizadas de forma directa. [Tailwind CSS](https://tailwindcss.com/docs/theme?utm_source=chatgpt.com)

### El HTML base en WordPress (editor de bloques)

En el editor, compón la página con bloques:

- Un **Grupo** que hará de **héroe** (asignaremos una clase adicional `scope-hero`).
- Dentro, un **Encabezado** (`h1`) y un **Párrafo** para el subtítulo.
- Un bloque **Botones** con un **Botón**.
- Luego, otro **Grupo** para **pricing** (`scope-pricing`).
- Finalmente, otro **Grupo** para **FAQ** (`scope-faq`).

En la **barra lateral del editor**, en “Avanzado → Clase(s) CSS adicional(es)”, escribe:

- `scope-hero` en el grupo del héroe,
- `scope-pricing` en el grupo de pricing,
- `scope-faq` en el grupo del FAQ.

No necesitas inventar HTML; WordPress ya genera el marcado. Lo único importante es **asignar esa clase** para tener dónde anclar el scope.

### La versión «sin `@scope`»: todo global, todo pesa demasiado

Podríamos escribir:

```
/* No recomendado: reglas globales que afectan a todo */
.wp-block-group h1 { @apply heading; } /* esto tocaría todos los h1 en grupos */
.wp-block-button__link { @apply btn btn-primary; }
```

Funciona hasta que un plugin, o tú en otra plantilla, cambias un encabezado o un botón… y la “solución global” se transforma en problema. **No hay frontera**.

### La versión «con `@scope`»: cada zona decide por sí misma

Le daremos a cada sección su **ámbito**. Empezamos por el **héroe**:

```
@scope (.scope-hero) {
  h1 { @apply heading; }
  .wp-block-button__link { @apply btn btn-primary; }
  p { @apply text-lg; opacity: .85; }
}
```

De esta forma, el **encabezado** y el **botón** solo se “tunean” en el héroe. Si mañana tienes otro héroe con un look distinto, creas otro `@scope` con otra raíz. Lo mismo para **pricing**:

```
@scope (.scope-pricing) {
  .plan { @apply rounded-xl border border-neutral-200 p-6 shadow-sm; }
  .plan h3 { @apply text-2xl font-semibold; }
  .plan .price { @apply text-3xl font-semibold; }
  .wp-block-button__link { @apply btn btn-primary; }
}
```

Y **FAQ**:

```
@scope (.scope-faq) {
  details { @apply rounded-lg border p-4; }
  summary { @apply font-medium; }
  p      { @apply mt-2 text-neutral-700; }
}
```

Cada bloque de `@scope` te da **independencia**: el héroe puede evolucionar sin miedo a romper el FAQ, y el pricing puede incluir un **tema alternativo** sin que el resto del sitio se entere.

### ¿Y si una subzona no debe tocarse? «Donut scope»

Supón que en **pricing** tienes una **sidebar** con un widget que trae su CSS. Quieres que tu `@scope` toque **todo** pricing salvo esa **sidebar**. Usas el segundo selector:

```
@scope (.scope-pricing) to (.scope-pricing .sidebar) {
  .plan { @apply rounded-xl border p-6; }
  /* … */
}
```

El **límite inferior** excluye esa parte. Es un patrón clásico en `@scope`. [Chrome for Developers](https://developer.chrome.com/docs/css-ui/at-scope?utm_source=chatgpt.com)

### El fallback para Firefox sin drama

Como vimos, duplicas **lo imprescindible** con `:where()`:

```
:where(.scope-hero) h1 { @apply heading; }
:where(.scope-hero) .wp-block-button__link { @apply btn btn-primary; }

:where(.scope-pricing) .plan { @apply rounded-xl border p-6 shadow-sm; }
/* … */
```

Este fallback **no compite** con lo moderno gracias al “peso cero” de `:where()` (documentado por MDN). Si un día dejas de necesitar el fallback, lo puedes borrar con seguridad. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/%3Awhere?utm_source=chatgpt.com)

### ¿Y en el editor?

Para que el **editor** refleje tus estilos, añade en tu `functions.php`:

```
add_action( 'after_setup_theme', function () {
  add_theme_support( 'editor-styles' );
  add_editor_style( 'editor.css' ); // tu hoja para el editor
} );
```

En `editor.css` puedes **reutilizar** lo que tengas en `app.css` (dependiendo de tu build) o escribir **ajustes específicos del editor**. Recuerda que WordPress aplica un **scope automático** con `.editor-styles-wrapper` dentro del iframe del editor; por eso **puedes anidar** así:

```
/* editor.css */
@import "tailwindcss";

/* Ajuste específico del editor para el héroe */
@scope (.editor-styles-wrapper) {
  @scope (.scope-hero) {
    .wp-block-button__link { outline: 2px dashed color-mix(in oklch, var(--color-brand-600), white 35%); }
  }
}
```

La documentación de temas y de estilos del editor lo deja claro: `add_theme_support( 'editor-styles' )` + `add_editor_style()` son la vía, y los estilos quedan **scopeados** al lienzo con `.editor-styles-wrapper`.

## Profundizando sin dolor: conceptos que merece la pena conocer

### `:scope` (la pseudo‑clase) y su relación con `@scope`

La pseudo‑clase `:scope` representa un **punto de referencia**. Fuera de `@scope`, puede equivaler a `:root` (suele ser `html`). **Dentro** de un bloque `@scope`, `:scope` **apunta a la raíz del ámbito** que definiste. Esto sirve, por ejemplo, para estilar el **propio contenedor** del ámbito desde dentro del `@scope` sin tener que repetir el selector. MDN lo documenta así: dentro de `@scope`, `:scope` coincide con el root de ese scope. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/%3Ascope?utm_source=chatgpt.com)

```
@scope (.scope-pricing) {
  :scope { padding-block: 4rem; }
}
```

### Especificidad «ligera» con `:where()` y la diferencia con `:is()`

A menudo se confunden. `:where()` **siempre pesa 0**; `:is()` **hereda** la especificidad del selector más específico que contenga. Si tu estrategia de fallback consiste en **no añadir peso**, usa `:where()`. Si necesitas agrupar selectores **sin perder “fuerza”**, usa `:is()`. MDN subraya esa diferencia en sus páginas respectivas. [MDN Web Docs+1](https://developer.mozilla.org/en-US/docs/Web/CSS/%3Awhere?utm_source=chatgpt.com)

### Compatibilidad: cómo confirmarla con fuentes oficiales

- **Safari 17.4**: Apple anunció `@scope` en sus notas de versión, dentro de la sección CSS de “New Features”. [Apple Developer](https://developer.apple.com/documentation/safari-release-notes/safari-17_4-release-notes?utm_source=chatgpt.com)
- **Chrome/Edge**: la guía de Chrome Developers no solo explica el concepto, también discute detalles de implementación y ejemplos. [Chrome for Developers](https://developer.chrome.com/docs/css-ui/at-scope?utm_source=chatgpt.com)
- **Panorama general**: caniuse ofrece el mapa de soporte y la cifra de uso global aproximada. [Can I use](https://caniuse.com/css-cascade-scope?utm_source=chatgpt.com)

### Tailwind v4: por qué `@theme` y `@utility` te facilitan la vida

La **documentación** de Tailwind v4 explica que `@theme` define **theme variables** (CSS variables con significado para las utilidades) y que esas variables se exponen de forma nativa, de modo que puedes usarlas tanto desde las clases utilitarias como en tu propio CSS. Y, con `@utility`, puedes crear utilidades **semánticas** (como `btn`) sin pelearte con capas manuales. Esa convergencia hace que encajar `@scope` sea natural: roles globales con Tailwind, ajustes locales con `@scope`.

## Buenas prácticas «de taller»: cómo trabajar con `@scope` en un equipo real

**Empieza acotando donde el dolor es real.** Si tu home está bien pero la landing de un curso sufre cada vez que tocas un plugin, el primer `@scope` vive ahí. Declara `class="scope-landing"` en el contenedor de esa página (o `data-scope="landing"`, si prefieres atributos) y escribe **dentro** lo mínimo imprescindible. No necesitas “scopizar” todo el sitio.

**Pon nombres que expliquen la intención.** En vez de `scope-1`, usa `scope-hero`, `scope-pricing`, `scope-faq` o `data-scope="landing"`. Cuando alguien lea el CSS dentro del bloque `@scope`, sabrá inmediatamente **dónde** se aplica.

**Duplica poco en el fallback.** Evita copiar todo dentro de `:where()`. Incluye **solo** las reglas imprescindibles para que el diseño se mantenga coherente en Firefox. Cuando el soporte sea total, borrarás poco.

**Mide y revisa.** Abre DevTools y usa el panel de “estilos” para ver de dónde viene cada regla. La **columna izquierda** te muestra la fuente y la **barra de tachado** indica qué ha sido superado por otra regla. Esto te ayudará a confirmar que `@scope` hace lo que esperas. Si ves que otra hoja se carga después y gana siempre, quizá debas **ajustar el orden de encolado** en WordPress, no la especificidad.

**Editor ≠ Frontend.** Recuerda que el editor **envuelve** con `.editor-styles-wrapper`. Si algo no cuadra, inspeciona dentro del editor; verás ese prefijo y podrás anidar tu `@scope`. El Handbook detalla cómo se cargan esos estilos y por qué hace falta `add_theme_support( 'editor-styles' )` además de `add_editor_style()`.

## Problemas frecuentes y cómo resolverlos con calma

**«He creado el `@scope`, pero mis reglas no se aplican.**»
Comprueba **dos cosas**: (1) que el **selector raíz** del `@scope` **existe** en el HTML tal cual (clase, atributo…), y (2) que la **regla que “gana”** no provenga de **un archivo encolado después**. `@scope` **no mueve** tu hoja por encima de otras; si otro CSS se carga más tarde y compite con un selector igual de simple, ganará por **orden**.

**«Quiero estilar también el contenedor del `@scope` desde dentro.**»
Usa `:scope` dentro del bloque. Sirve para tocar el **root** del ámbito sin repetir su nombre. MDN lo documenta como “la referencia al root cuando estás dentro de `@scope`”. [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/CSS/%3Ascope?utm_source=chatgpt.com)

```
@scope (.scope-landing) {
  :scope { background: color-mix(in oklch, var(--color-brand-600), white 85%); }
}
```

**«En el editor se ve distinto al frontend.**»
El editor añade `.editor-styles-wrapper` para **scopear** tu CSS (así no afecta a la interfaz del editor). Puedes anidar tu `@scope` dentro de esa clase, o bien replicar el ámbito con un selector que parta de `.editor-styles-wrapper`. La documentación del editor explica este flujo y por qué primero hay que declarar soporte y luego encolar la hoja. [WordPress Developer Resources+1](https://developer.wordpress.org/block-editor/explanations/architecture/styles/?utm_source=chatgpt.com)

**«Tengo una sección con un bloque de terceros que no quiero tocar.**»
Define un **límite inferior** con “donut scope”: `@scope (.root) to (.root .no-tocar) { … }`. La guía de Chrome sobre `@scope` ilustra el concepto “upper” (raíz) y “lower bound” (tope). [Chrome for Developers](https://developer.chrome.com/docs/css-ui/at-scope?utm_source=chatgpt.com)

**«He puesto `:where()` y ahora parece que nada hace efecto.**»
Recuerda que `:where()` añade **especificidad 0**. Si de verdad quieres que una regla **gane** sobre otra, no es el selector adecuado; su propósito es lo contrario: **no competir**. Si necesitas «ganar», utiliza un selector ligeramente más específico (por ejemplo, añade una clase al mismo nivel), o bien asegúrate del **orden de carga**.

## Rendimiento, accesibilidad y pruebas

**Rendimiento.** `@scope` no es una “bala de plata” para hacer CSS más pequeño; te ayuda a **aislar** y a **no subir especificidad**. Si además añades fallback, estás **duplicando** una pequeña parte del CSS (en la práctica, apenas perceptible si seleccionas con cabeza). La clave está en que ese fallback sea **esencial y ligero**. En Tailwind v4, como defines utilidades/roles con `@utility`, evitarás repetir bloques largos: aplicarás clases reutilizables **dentro** del `@scope`.

**Accesibilidad.** Cuando afinamos localmente, es tentador “apagar” los contornos de foco o reducir contrastes. **No lo hagas**. Mantén `:focus` o `:focus-visible` **claros** en tus roles (por ejemplo, en `btn`) y no los mutes dentro del `@scope`. Puedes, eso sí, ajustar **ritmos** (espaciados) o construcción de layouts para mejorar legibilidad sin tocar accesibilidad. Si usas colorimetría moderna (OKLCH), los tokens de marca podrán ayudarte a mantener contraste a través de variantes; la web de Tailwind explica cómo los **theme variables** se convierten en variables CSS reales, por lo que puedes combinarlas con funciones como `color-mix()` nativo del navegador. [Tailwind CSS](https://tailwindcss.com/docs/theme?utm_source=chatgpt.com)

**Pruebas.** Verifica en **Chrome/Edge** y **Safari** (comportamiento moderno) y en **Firefox** (fallback). Una forma simple de confirmar que `@scope` se está aplicando es abrir DevTools y buscar el **bloque** de reglas que indica `@scope … { }`. En Firefox deberías ver, en cambio, las reglas `:where(.scope-*) …`. No olvides probar **navegación por teclado** para confirmar que los estados de foco no se han «tapado».

## Un segundo caso: «Página de producto» de WooCommerce, sin luchar con el CSS del plugin

WooCommerce trae su propio CSS. Si quieres **retocar** solo la página de producto (PDP), usar `@scope` es más **limpio** que subir especificidad global.

Supón que quieres darle a “Añadir al carrito” un look acorde con tus tokens:

```
@import "tailwindcss";

@theme {
  --color-brand-600: oklch(0.63 0.17 264);
  --color-brand-700: oklch(0.57 0.17 264);
}

@utility btn {
  @apply inline-flex items-center gap-2 px-4 py-2 rounded-md font-medium transition;
}

@utility btn-primary {
  @apply bg-brand-600 text-white hover:bg-brand-700;
}

/* PDP, acotado a la plantilla single product */
@scope (.single-product) {
  .wc-block-components-button { @apply btn btn-primary; }
}

/* Fallback */
:where(.single-product) .wc-block-components-button { @apply btn btn-primary; }
```

Con esto, la PDP queda ajustada **sin interferir** con el resto de la tienda. Si el plugin cambia su CSS, tu ámbito seguirá **encapsulando** tus decisiones. Y si más adelante creas una landing promocional con otro estilo de botón, será otro `@scope`, no una nueva “regla global” que compita con todo.

## ¿Cómo encaja esto en tu «sistema de diseño» basado en Tailwind?

Una duda legítima: “¿no estamos volviendo a escribir CSS en vez de usar utilidades?”. La respuesta es **no**, lo que hacemos es **componer utilidades** en **roles con nombre** (`@utility`) y **aplicarlas localmente** donde hace falta (`@scope`). Tailwind sigue haciendo el trabajo pesado: te da un lenguaje común de espaciados, tipografías, colores y comportamientos. Y como v4 expone los **theme variables** como **CSS variables**, puedes usarlos tanto en el HTML con clases utilitarias como en tus reglas dentro de `@scope`. La documentación de Tailwind v4 lo llama explícitamente “theme variables” y muestra cómo añadir colores y obtener sus utilidades hermanas. [Tailwind CSS](https://tailwindcss.com/docs/theme?utm_source=chatgpt.com)

La combinación **roles + scope** suele producir un HTML **más legible** y un CSS **más predecible**:

- Roles (`btn`, `heading`, `card`) definen **el qué**.
- `@scope` define **el dónde**.
- Los tokens (`@theme`) definen **con qué valores**.

## Integración con el Editor: el pequeño truco que evita sorpresas

Vuelve a WordPress. **Carga tus estilos del editor** con la pareja `add_theme_support( 'editor-styles' )` y `add_editor_style( 'editor.css' )`. En la práctica, el editor inyecta tu CSS en su **iframe** y **scopea** las reglas con `.editor-styles-wrapper`. Eso significa que si en tu site real (frontend) escribiste:

```
@scope (.scope-hero) { .wp-block-button__link { /* … */ } }
```

En el editor quizá te convenga **anidar** así:

```
@scope (.editor-styles-wrapper) {
  @scope (.scope-hero) {
    .wp-block-button__link { /* … */ }
  }
}
```

La **documentación oficial** recoge estos conceptos: la referencia de `add_editor_style()` y las guías de “Theme Support” y “Styles in the Editor” explican el flujo y cuándo necesitas la bandera de soporte. Si alguna vez ves divergencias, mira el inspector del editor y verás ese prefijo. [WordPress Developer Resources+1](https://developer.wordpress.org/reference/functions/add_editor_style/?utm_source=chatgpt.com)

## Migración suave: de «selectores grandes» a `@scope` (un plan en 3 tardes)

**Tarde 1 — Localiza dónde duele.** Haz una lista de **dos secciones** conflictivas (por ejemplo, el héroe del Home y el pricing de la landing). Añade clases o atributos al contenedor: `scope-hero`, `scope-pricing`. Crea un archivo CSS de **scopes** y mueve ahí los ajustes localizados.

**Tarde 2 — Crea roles básicos.** En tu hoja principal, define con `@utility` los **roles** que se repiten (botones, titulares, tarjetas). Reemplaza valores “a ojo” por **tokens** en `@theme` (marca, radio, tipografía). Empieza a **aplicar roles** dentro de los `@scope`.

**Tarde 3 — Fallback y pruebas.** Duplica en `:where()` **solo** lo imprescindible. Revisa editor y frontend, Chrome/Edge/Safari y Firefox. Escribe una **nota** en tu repositorio explicando **cuándo usar** `@scope`, cómo nombrar los ámbitos y cómo añadir la clase/atributo desde el editor. Deja, por último, un **blueprint mental**: cualquier ajuste local nuevo vive en `@scope`.

## Preguntas que quizá te estás haciendo ahora

**¿Puedo usar `@apply` y `@utility` dentro de un `@scope`?**
Sí. En **Tailwind v4**, estas directivas viven en el CSS; puedes **componer utilidades** y luego **aplicarlas localmente** dentro de `@scope` sin ningún problema. La guía de funciones y directivas de v4 repasa el uso de `@plugin`, `@theme`, etc., y el blog oficial de v4 y v4.1 profundiza en esta filosofía **CSS‑first**. [Tailwind CSS+2Tailwind CSS+2](https://tailwindcss.com/docs/functions-and-directives?utm_source=chatgpt.com)

**¿Cómo sé que mi proyecto puede actualizar a v4?**
Revisa la **guía de actualización**: v4 apunta a Safari 16.4+, Chrome 111+, Firefox 128+. Si tu público usa navegadores más antiguos, puedes mantener v3 y adoptar el patrón de `@scope` igualmente con el **fallback**. [Tailwind CSS](https://tailwindcss.com/docs/upgrade-guide?utm_source=chatgpt.com)

**¿`@scope` me libra de todos los conflictos?**
No de todos. Si un plugin encola su CSS **después** del tuyo y utiliza selectores **igual de simples** dentro de un `@scope` igual de cercano, podría ganar por **orden**. Asegúrate de **encolar bien** tus hojas y evita cargar “correcciones” en plugins de caché/minificado sin entender el orden resultante.

**¿Puedo “medir” el impacto?**
Sí. Aunque `@scope` no corta bytes, reduce **complejidad** y **superposición** de reglas. Lo notarás en PRs más pequeños, en menos `!important`, en menos “efectos colaterales” al tocar una sección. A nivel de **UX**, evitar colisiones puede mejorar **INP** (respuesta a la interacción) si no arrastras reglas innecesarias, pero no esperes milagros en métricas por sí solo: es más una mejora de **DX** (experiencia de desarrollo) y **estabilidad visual**.

## Recapitulación práctica (sin formato “chuleta”)

Hemos visto que **`@scope`** te da **fronteras claras** en el CSS: aplica “lo tuyo” **solo** en **tu zona**. Que **`:where()`** te permite un **plan B ligero** mientras **Firefox** completa el soporte estable. Que **Tailwind v4** facilita **tokens** (`@theme`) y **roles** (`@utility`) para que la base sea coherente y reusable. Y que, en **WordPress**, el **editor** y el **frontend** pueden convivir si usas **`add_theme_support('editor-styles')`** + **`add_editor_style()`** y recuerdas la **envoltura** `.editor-styles-wrapper`. [Tailwind CSS+1](https://tailwindcss.com/docs/theme?utm_source=chatgpt.com)

Si te quedas con una sola imagen mental, que sea esta: **roles** para decir *qué es* (un botón, un titular, una tarjeta), **`@scope`** para decir *dónde vale* (héroe, pricing, FAQ), **tokens** para decir *con qué valores* (marca, tipografía, radios). Con esas tres piezas, tu CSS deja de ser una batalla y se convierte en una conversación acotada.

## Apéndice con código consolidado (para tu primer commit)

> No es una “chuleta”; es el **resultado** de lo que construimos arriba, listo para pegar y probar. Úsalo como punto de partida, no como dogma.

**`app.css` (global + roles + ámbitos principales + fallback):**

```
@import "tailwindcss";

/* Tokens */
@theme {
  --font-sans: ui-sans-serif, system-ui, Inter, "Segoe UI", sans-serif;
  --color-brand-600: oklch(0.63 0.17 264);
  --color-brand-700: oklch(0.57 0.17 264);
  --radius-button: .75rem;
}

/* Roles */
@utility heading {
  @apply text-3xl md:text-5xl font-semibold tracking-tight;
}
@utility btn {
  @apply inline-flex items-center gap-2 px-5 py-3 rounded-[var(--radius-button)]
         font-medium transition-colors;
}
@utility btn-primary { @apply bg-brand-600 text-white hover:bg-brand-700; }

/* Héroe (moderno) */
@scope (.scope-hero) {
  h1 { @apply heading; }
  p  { @apply text-lg; opacity: .85; }
  .wp-block-button__link { @apply btn btn-primary; }
}
/* Héroe (fallback) */
:where(.scope-hero) h1 { @apply heading; }
:where(.scope-hero) p  { @apply text-lg; opacity: .85; }
:where(.scope-hero) .wp-block-button__link { @apply btn btn-primary; }

/* Pricing con donut scope: excluye la .sidebar */
@scope (.scope-pricing) to (.scope-pricing .sidebar) {
  :scope { padding-block: 4rem; }
  .plan { @apply rounded-xl border border-neutral-200 p-6 shadow-sm; }
  .plan h3 { @apply text-2xl font-semibold; }
  .plan .price { @apply text-3xl font-semibold; }
  .wp-block-button__link { @apply btn btn-primary; }
}
/* Fallback pricing */
:where(.scope-pricing) .plan { @apply rounded-xl border border-neutral-200 p-6 shadow-sm; }
:where(.scope-pricing) .plan h3 { @apply text-2xl font-semibold; }
:where(.scope-pricing) .plan .price { @apply text-3xl font-semibold; }
:where(.scope-pricing) .wp-block-button__link { @apply btn btn-primary; }

/* FAQ */
@scope (.scope-faq) {
  details { @apply rounded-lg border border-neutral-200 p-4; }
  summary { @apply font-medium; }
  p      { @apply mt-2 text-neutral-700; }
}
/* Fallback FAQ */
:where(.scope-faq) details { @apply rounded-lg border border-neutral-200 p-4; }
:where(.scope-faq) summary { @apply font-medium; }
:where(.scope-faq) p       { @apply mt-2 text-neutral-700; }
```

**`functions.php` (estilos del editor):**

```
add_action( 'after_setup_theme', function () {
  add_theme_support( 'editor-styles' );
  add_editor_style( 'editor.css' );
} );
```

**`editor.css` (ajustes específicos del editor, opcionales):**

```
@import "tailwindcss";

/* Aislar estilos del editor y refinar el héroe */
@scope (.editor-styles-wrapper) {
  @scope (.scope-hero) {
    .wp-block-button__link {
      outline: 2px solid color-mix(in oklch, var(--color-brand-600), white 35%);
      outline-offset: 2px;
    }
  }
}
```

Con esto tienes un “esqueleto” que **no pelea** globalmente: cada zona manda en su casa, el editor está alineado y el fallback cuida de quien aún no dispone de `@scope`.

## Próximo paso natural

`@scope` es una de esas piezas de CSS que, cuando la usas por primera vez **en un sitio real**, te preguntas cómo te las arreglabas antes. En **WordPress**, donde conviven tu tema, plugins y el editor, supone poder **ajustar con precisión** sin encadenar selectores ni rezar para que `!important` no rompa otra cosa. En **Tailwind v4**, donde tu **sistema** se expresa con **tokens** y **roles**, encaja como una frontera limpia entre lo **global** y lo **local**.

Mi propuesta para que avances hoy mismo: elige una página (una landing de producto, una clase del blog que te gustaría modernizar, una PDP de WooCommerce), añade una clase al contenedor, crea un bloque `@scope` y ajusta solo lo que haces falta, con **dos o tres reglas**. Añade el **fallback** con `:where()` para Firefox y revisa el resultado en los navegadores principales. Cuando veas lo predecible que se vuelve, seguramente quieras “scopizar” dos o tres secciones más y empezar a **documentar el patrón** en tu biblioteca interna.

---

Si quieres **acelerar** la adopción, pásate por la **Biblioteca de Flowtitude** y descarga el **pack de roles** (botones, titulares, tarjetas) junto a **plantillas de `@scope` + fallback** listas para WordPress + Tailwind v4. Con ese punto de partida, un héroe o una PDP “scopeados” te toman menos de una tarde.
