@scope en CSS para WordPress + Tailwind: resuelve la especificidad sin romper el sitio
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
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
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
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
¿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
Hay tres ideas que conviene anclar:
- 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. - No reemplaza la herencia. Si tú defines un
color
en una caja, esecolor
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). - Te devuelve selectores cortos y legibles. En vez de
.landing .content .grid .card h2.title
puedes escribirh2
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
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
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
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
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
Eso nos da un camino claro:
- Definimos nuestros tokens (colores de marca, radios, ritmos, fuentes) con
@theme
. - Creamos roles con
@utility
(por ejemplo, unbtn
base y sus variantes). - 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
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
¿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
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
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
¿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
@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
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 - 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
- Panorama general: caniuse ofrece el mapa de soporte y la cifra de uso global aproximada. Can I use
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
@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
«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
«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
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
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
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
¿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
¿@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
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.