Introducción a las @layer en CSS: Mejora la organización y control de estilos con WordPress
En proyectos WordPress, sobre todo cuando combinamos constructores visuales, hojas de estilo personalizadas y plugins, es fácil que los estilos se conviertan en un rompecabezas difícil de mantener. La buena noticia es que el CSS no tiene por qué ser un desorden creciente: podemos estructurarlo para que la cascada sea predecible, especialmente cuando usamos Tailwind v4.
Las @layer
te ayudan a documentar la intención: no solo escribes reglas, sino que decides en qué orden deben evaluarse. Esto reduce sobrescrituras, evita depender de !important
y mejora la colaboración en equipo, porque todos entienden dónde va cada cosa
Orden en medio del caos
En WordPress, los estilos llegan desde múltiples orígenes (core, plugins, maquetadores, tu tema…). Si no estableces una jerarquía explícita, la cascada decide por ti, y normalmente gana quien carga más tarde o quien mete CSS sin capa (o inline). El propósito de @layer
es poner orden y convertir esa mezcla en un sistema intencional y mantenible. Piensa en capas como “carriles” por los que circulan tus estilos: si cada cosa va por su carril, hay menos choques.
¿Qué es @layer
y por qué importa?
@layer
te permite definir la cascada de forma explícita creando capas de estilos. A igualdad de especificidad:
- A igualdad de origen, importancia y especificidad, gana la capa declarada más tarde.
- Los estilos sin capa (unlayered) se evalúan después de los estilos en capas; en igualdad de especificidad, pueden imponerse (por eso a veces “te gana” Gutenberg o un plugin).
- Dentro de una misma capa, manda el orden de aparición (lo último gana).
La ventaja clave es que @layer
convierte la cascada en configuración: fijas un orden base → componentes → utilidades → custom, y a partir de ahí todo lo que añadas respeta esa decisión. Pasas de “apagar fuegos” a diseñar un sistema. Cuando alguien nuevo entra al proyecto, entiende en minutos dónde debe ubicar un ajuste y por qué.
Arquitectura recomendada de capas (WordPress + Tailwind v4)
Define el orden al principio de tu hoja principal y mantén pocas capas con responsabilidades claras:
@layer base, components, utilities, custom;
- base → reset/preflight, tipografía global, normalizaciones.
- components → patrones reutilizables (.btn, .card, .input…).
- utilities → pequeños helpers cuando no exista la utilidad Tailwind.
- custom → último recurso para convivir con CSS de terceros (úsalo con moderación).
Menos es más. Cuantas menos capas y más claras estén sus responsabilidades, menos deuda técnica acumulas. Si todo se resuelve con custom
, a corto plazo “funciona”, pero a medio plazo el proyecto se vuelve opaco. Intenta que custom
sea la excepción bien documentada, no la norma.
Tailwind v4: integración por capas (elige una opción)
Opción A — Import único (rápida):
@import "tailwindcss";
/* Tras el import, declara el orden de capas */
@layer base, components, utilities, custom;
Opción B — Control por capas (fina):
/* Declara el orden primero */
@layer base, components, utilities, custom;
/* Importa Tailwind en las capas deseadas */
@import "tailwindcss/preflight" layer(base);
@import "tailwindcss/utilities" layer(utilities);
/* Tus componentes irán más abajo en layer(components) */
Tokens con @theme
(opcional, recomendado):
@theme {
--font-sans: ui-sans-serif, system-ui, "Helvetica Neue", Arial, sans-serif;
--color-primary: #1e40af;
--radius-md: .5rem;
--space-2: .5rem;
}
La Opción A es ideal para empezar rápido con Tailwind v4. La Opción B da control fino cuando quieres decidir exactamente qué entra en cada capa (por ejemplo, si tu base
ya establece tipografía y quieres evitar conflictos). Y @theme
te ofrece un lugar único para definir tokens (tipos, colores, radios, espaciados), lo que refuerza la coherencia visual en Bricks/Gutenberg y acelera el trabajo. Flowtit
Dónde pegar cada cosa en WordPress
Con WindPress
- WP Admin → WindPress → Files →
main.css
. - Arriba del todo pega, en este orden:
- (Opcional)
@theme
con tus tokens. - Opción A o B de Tailwind v4 (elige solo una).
- (Si elegiste A) la línea de orden de capas.
- (Opcional)
- Más abajo, añade tus capas y componentes:
@layer base {
html { font-family: var(--font-sans, ui-sans-serif); }
a { text-decoration: none; }
}
@layer components {
.btn { @apply inline-flex items-center justify-center px-4 py-2 rounded font-medium transition; }
.btn-primary { @apply bg-[var(--color-primary)] text-white hover:opacity-90; }
.input { @apply w-full px-3 py-2 border rounded outline-none; }
.input:focus { @apply ring-2 ring-[var(--color-primary)]; }
}
@layer utilities {
.text-balance { text-wrap: balance; }
}
/* Solo para casos puntuales frente a terceros */
@layer custom {
/* Ajustes mínimos y documentados */
}
Colocar custom
al final garantiza que tus excepciones controladas tengan prioridad, pero evita que esta capa se convierta en “cajón de sastre”. Antes de añadir algo a custom
, revisa si el problema se debe a orden de encolado o a estilos sin capa de WordPress/plugins. Arreglar la causa evita parches innecesarios.
Gutenberg: por qué a veces «te gana» y cómo evitarlo
Gutenberg a menudo inyecta CSS inline o carga estilos sin capa, que en igualdad de especificidad se imponen sobre tus capas.
Opciones prácticas (elige las que te apliquen):
Quitar Global Styles y filtros SVG si no los usas (en functions.php
o mu-plugin):
add_action('init', function () {
remove_action('wp_enqueue_scripts', 'wp_enqueue_global_styles');
remove_action('wp_body_open', 'wp_global_styles_render_svg_filters');
});
Cargar estilos de bloques por demanda (menos CSS global y menos choques):
add_filter('should_load_separate_core_block_assets', '__return_true');
Reducir/evitar CSS inline de WordPress para no competir con tus capas:
// 0 = desactiva inline y fuerza archivo; también puedes ajustar el umbral (bytes)
add_filter('styles_inline_size_limit', function () { return 0; });
Empieza por limitar el inline y por separar assets. Con menos CSS global en el aire, menos “sorpresas” tendrás en la cascada. Si aun así te pisan, valora encapsular el CSS externo en una capa controlada (ver sección nueva más abajo) antes de recurrir a custom
o !important
.
Encapsular CSS de WordPress y plugins en capas
WordPress no mete el CSS del core ni de los plugins en ninguna capa por defecto. Si quieres que lo externo respete tu jerarquía, puedes desencolar sus estilos y reencolarlos dentro de tu capa wordpress
usando @import … layer(wordpress)
.
¿Por qué aquí usamos una jerarquía extendida?
En otras partes solemos usar una versión reducida (@layer base, components, utilities, custom;
).
En esta sección, como tratamos la convivencia con core, plugins y builder, añadimos capas extra para aislar orígenes y evitar choques:
wordpress
→ agrupa core + plugins (todo lo externo que encapsulamos). Así queda debajo de lo nuestro y no pisa tus reglas.bricks
→ separa estilos base del builder (si usas Bricks). Permite ajustar el builder sin afectar al core ni a tus capas de proyecto. (Si no usas builder con CSS propio, puedes omitirla.)layouts
→ patrones estructurales con menos especificidad quecomponents
(secciones, containers, espaciados macro), para que los componentes puedan dominar sin!important
.components
,utilities
,custom
→ componentes con nombre, helpers puntuales y overrides excepcionales (documentados) respectivamente.
Orden de capas recomendado en este contexto (ejemplo):
@layer wordpress, bricks, base, layouts, components, utilities, custom;
Regla práctica: usa la reducida en proyectos simples; usa la extendida cuando haya builder y/o conflictos con core/plugins que quieras encapsular.
Implementación: encapsular estilos en @layer wordpress
Un solo snippet que:
- Desencola los handles que elijas.
- Genera una hoja
<style>
inline con tu orden de capas y un@import … layer(wordpress)
por cada URL detectada.
<?php
/**
* Inyecta CSS handles específicos en capas CSS personalizadas
* Versión completa y mejorada con configuración flexible por capas
*/
if (!defined('ABSPATH')) exit;
add_action('wp_enqueue_scripts', function () {
// Configuración: Handle => Capa de destino
$handles_config = [
// Capa WordPress (core y plugins básicos)
'wp-block-library' => 'wordpress',
'wp-block-library-theme' => 'wordpress',
'global-styles' => 'wordpress',
'wp-block-navigation' => 'wordpress',
'wp-block-button' => 'wordpress',
// Capa de componentes (plugins de UI)
'woocommerce-general' => 'wordpress',
'woocommerce-layout' => 'wordpress',
'woocommerce-smallscreen' => 'wordpress',
// Ejemplos adicionales
// 'elementor-frontend' => 'components',
// 'custom-plugin-styles' => 'utilities',
// 'gravity-forms' => 'components',
];
// Permitir que otros plugins/temas modifiquen la configuración
$handles_config = apply_filters('css_layers_handles_config', $handles_config);
// Opcional: Define orden de capas solo si no lo tienes en Tailwind
// Déjalo vacío '' si ya lo manejas en tu configuración de Tailwind
$layer_order = apply_filters('css_layers_order', '');
global $wp_styles;
if (!isset($wp_styles)) {
wp_styles();
}
$layers_css = [];
$processed_handles = [];
foreach ($handles_config as $handle => $target_layer) {
// Verificar si el handle existe y está registrado/encolado
if (!wp_style_is($handle, 'registered') && !wp_style_is($handle, 'enqueued')) {
continue;
}
$style_obj = $wp_styles->registered[$handle] ?? null;
if (!$style_obj || empty($style_obj->src)) {
continue;
}
// Construir URL absoluta
$src = $style_obj->src;
if (preg_match('#^https?://#i', $src)) {
$url = $src;
} elseif (str_starts_with($src, '//')) {
$url = (is_ssl() ? 'https:' : 'http:') . $src;
} elseif (str_starts_with($src, '/')) {
$url = home_url($src);
} else {
// Relativo al directorio base de WP
$url = home_url('/' . ltrim($src, '/'));
}
// Agregar versioning si existe
if (!empty($style_obj->ver) && $style_obj->ver !== false) {
$url = add_query_arg('ver', $style_obj->ver, $url);
}
// Agrupar por capa
if (!isset($layers_css[$target_layer])) {
$layers_css[$target_layer] = [];
}
$layers_css[$target_layer][] = $url;
// Desencolar y desregistrar
if (wp_style_is($handle, 'enqueued')) {
wp_dequeue_style($handle);
}
if (wp_style_is($handle, 'registered')) {
wp_deregister_style($handle);
}
$processed_handles[] = $handle;
}
// Si no hay handles procesados, salir
if (empty($layers_css)) {
return;
}
// Construir CSS con @layer e @import
$css_output = '';
// Agregar declaración de orden de capas solo si se especifica
if (!empty($layer_order)) {
$css_output .= "@layer {$layer_order};\n\n";
}
// Generar @import por cada capa
foreach ($layers_css as $layer => $urls) {
$css_output .= "/* === Capa: {$layer} === */\n";
foreach ($urls as $url) {
$css_output .= "@import url('" . esc_url_raw($url) . "') layer({$layer});\n";
}
$css_output .= "\n";
}
// Inyectar el CSS inline
$inline_handle = 'css-layers-injection';
wp_register_style($inline_handle, false, [], wp_get_theme()->get('Version'));
wp_enqueue_style($inline_handle);
wp_add_inline_style($inline_handle, $css_output);
// Debug: Log handles procesados (solo en desarrollo)
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('CSS Layers - Handles procesados: ' . implode(', ', $processed_handles));
error_log('CSS Layers - Capas creadas: ' . implode(', ', array_keys($layers_css)));
}
}, 15); // Prioridad temprana para capturar antes que otros plugins
/**
* Función helper para agregar handles dinámicamente desde otros plugins/temas
*
* @param string $handle Handle del CSS a mover
* @param string $layer Capa de destino
*
* Uso: add_css_to_layer('mi-plugin-css', 'components');
*/
function add_css_to_layer($handle, $layer = 'components') {
add_filter('css_layers_handles_config', function($config) use ($handle, $layer) {
$config[$handle] = $layer;
return $config;
});
}
/**
* Función helper para definir el orden de capas desde el tema
*
* @param string $order Orden de capas separadas por comas
*
* Uso: set_css_layers_order('wordpress, components, utilities, custom');
*/
function set_css_layers_order($order) {
add_filter('css_layers_order', function() use ($order) {
return $order;
});
}
/**
* Función para obtener información de debug sobre las capas procesadas
*
* @return array Array con información de las capas
*/
function get_css_layers_info() {
static $layers_info = null;
if ($layers_info === null) {
$layers_info = [
'processed_handles' => [],
'layers_created' => [],
'css_output' => ''
];
}
return $layers_info;
}
Notas rápidas para uso:
- Edita
$TARGETS
con los handles a “absorber” (p. ej.,wp-block-library
,woocommerce-general
…). - El snippet elimina los
<link>
originales y mete esas hojas dentro de@layer wordpress
. - Mantén
layouts
ycomponents
como capas propias por encima para evitar peleas. - Si un plugin añade inline styles a su handle, considera un proxy por handle (no cabe aquí, pero tu snippet global cubre la mayoría de casos).
Solución de conflictos: flujo de depuración rápido
- DevTools → Computed: identifica qué regla gana y de qué archivo/hook viene (¿inline? ¿wp-block-library? ¿un plugin?).
- ¿Está sin capa? Si la regla ganadora es unlayered/inline, decide si puedes reducir inline (B), separar assets (A) o reordenar encolado (D).
- ¿Tu regla está en
components
pero debería ser una excepción? Mueve solo ese ajuste acustom
y documenta el motivo. - ¿Nada de lo anterior es viable? Usa un opt-out puntual (pequeña hoja sin capas) para el override crítico, limita su alcance y evita convertirlo en norma.
- Repite prueba en páginas reales (home, blog, producto) y en el editor de bloques.
Piensa en este flujo como una lista de descarte: primero ataja la causa (orden/inline), luego valora encapsular en wordpress
, y solo al final recurre a custom
. Seguir esta secuencia reduce hotfixes y hace que tu CSS sea sostenible en el tiempo.
Buenas prácticas
- Una única estrategia de import: A o B, nunca ambas.
- Pocas capas, bien definidas: base → components → utilities → custom.
@theme
primero: tokens compartidos para coherencia visual.overrides
como excepción: usa selectores simples y ámbito mínimo.- Evita
!important
: suele ocultar problemas de orden/inline. - Rendimiento: preferir assets por demanda y menos inline.
- Documentación: anota por qué existe cada override (evita deuda técnica).
Documentar por qué existe cada override te ahorra horas cuando vuelves al proyecto meses después. También facilita que otros colaboradores se sumen sin romper nada: ven el histórico y entienden la intención detrás de cada ajuste.
Errores comunes (y cómo se arreglan)
- Mezclar Opción A y B → capas duplicadas/desorden. Arreglo: elige una.
- Encolar antes que Gutenberg/plugins → pierdes prioridad. Arreglo: encola al final o ajusta dependencias.
- Abusar de
custom
→ arquitectura opaca. Arreglo: usa custom solo tras ordenar encolado/inline. - Crear diez capas → mantenimiento imposible. Arreglo: mantén 3–4 capas.
- Ignorar inline → “fantasmas” que te pisan. Arreglo: limita inline con
styles_inline_size_limit
. - Usar
!important
de forma sistemática → te encierra. Arreglo: arregla orden, no la sintomatología.
Casi siempre que ves !important
multiplicándose, falta arquitectura. Antes de tocar reglas, revisa dónde y cómo se cargan. Encapsular WordPress/plugins en wordpress
y reservar custom
para excepciones puntuales suele eliminar la necesidad de !important
.
Checklist de verificación
- Declaraste
@layer base, components, utilities, custom;
al inicio. - Usas solo una estrategia de import v4 (A o B).
- Tu CSS se encola después del de plugins/Gutenberg.
- ¿Has activado assets por demanda de bloques si te conviene?
- ¿Has limitado el inline de WP si te pisa?
- ¿
overrides
existe pero está acotado y documentado? - ¿Tokens en
@theme
y estilos globales enbase
? - ¿Probado en plantillas reales y en el editor?
No hace falta cumplir todo desde el primer día. Empieza por ordenar el encolado, añade tu orden de capas, limita el inline, y deja custom
casi vacío. Con eso ya notarás un salto en estabilidad. El encapsulado en wordpress
es el plus cuando un tercero te pisa a menudo.
Preguntas frecuentes
¿Por qué mis utilidades de Tailwind no se aplican en páginas con bloques?
Porque Gutenberg puede inyectar CSS inline o sin capa que, a igualdad de especificidad, se impone sobre lo que está en @layer
. Ordena el encolado, separa assets, limita inline y, si es imprescindible, usa un opt-out puntual sin capas.
¿Puedo meter todo en custom
y listo?
No. Los overrides
son una excepción. Primero arregla el orden de encolado y el inline; solo después añade un ajuste mínimo y documentado.
¿Qué diferencia hay entre components
y utilities
?components
define patrones con nombre (p. ej., .btn
), mientras utilities
son pequeños helpers que no ameritan un componente completo. Mantener esa frontera clara evita duplicación y facilita mantenimiento.
¿!important
es mala práctica siempre?
No siempre, pero suele indicar un problema de fondo. Úsalo solo si has agotado orden de encolado, assets por demanda y un override mínimo.
Conclusión
@layer
te devuelve el control de la cascada. Con Tailwind v4 y una arquitectura clara (base → components → utilities → custom), puedes integrar estilos en WordPress sin “peleas” con Gutenberg y plugins. Ordena el encolado, reduce inline y usa overrides puntuales: obtendrás un CSS estable, escalable y fácil de mantener.
¿Quieres verlo aplicado a tu WordPress?
En la comunidad Flowtitude tienes plantillas, snippets y guías paso a paso para integrar Tailwind v4 + @layer en WordPress sin dolores. Únete y accede a los recursos.