FSE CSS Patterns¶
Patterns and gotchas for writing CSS in the WordPress Full Site Editing (FSE) child theme. Reference this when working on style.css, template parts, or block patterns.
WordPress Specificity¶
WordPress generates high-specificity selectors from theme.json and block markup:
- .has-link-color a overrides custom link colors
- .wp-element-button applies button defaults
- .is-layout-flow > * applies block gap via margins
Techniques:
- Use :where() for zero-specificity exclusions: a:where(:not(.wp-element-button)) targets links without matching buttons
- Use !important when WordPress inline styles or layout classes override custom CSS (e.g., block gap on columns)
- Check rendered HTML in DevTools before writing CSS — WordPress adds classes not visible in .html template source
<details> Element¶
The browser natively hides all children of <details> except <summary> unless the open attribute is present. CSS display: block !important on children cannot override this — it's browser-level content slot behavior, not a CSS display issue.
Solution: Use open attribute in HTML so content is visible by default. Control visibility per breakpoint:
- Desktop/tablet: hide <summary> via CSS, content stays visible because open is set
- Mobile: show <summary> as toggle, hide heading (summary replaces it)
Polylang Language Switcher¶
The wp:polylang/language-switcher block renders as:
<nav role="navigation" aria-label="Choose a language">
<ul class="wp-block-polylang-language-switcher">...</ul>
</nav>
CSS selectors targeting direct children of a container (e.g., <details> > .wp-block-polylang-language-switcher) will miss it. Target nav instead as the direct child.
Block Gap and .is-layout-flow¶
WordPress's .is-layout-flow applies blockGap via margin-block-start on child elements using generated container-specific selectors (.wp-container-core-BLOCK-is-layout-HASH > * + *). CSS gap has no effect on flow layout — it only uses margins.
Setting gap on a .wp-block-column container does nothing because the column uses flow layout, not flexbox.
Solution: Override flow layout with flexbox to make gap work:
.my-container .wp-block-column {
display: flex !important;
flex-direction: column;
gap: var(--wp--preset--spacing--10);
}
.my-container .wp-block-column > * {
margin-block-start: 0 !important;
margin-block-end: 0 !important;
}
This converts the column from flow to flex layout, then gap controls spacing while the margin reset prevents WordPress's generated margins from interfering.
Spacing Tokens¶
Always use var(--wp--preset--spacing--*) instead of hardcoded pixel values.
| Slug | Name | Size |
|---|---|---|
10 |
Micro | 8px |
20 |
Tiny | 10px |
30 |
X-Small | 20px |
40 |
Small | 30px |
50 |
Regular | clamp(30px, 5vw, 50px) |
60 |
Large | clamp(30px, 7vw, 70px) |
70 |
X-Large | clamp(50px, 7vw, 90px) |
80 |
XX-Large | clamp(70px, 10vw, 140px) |
Dev Workflow¶
- Hard reload (Cmd+Shift+R) after CSS changes — theme CSS is versioned by
wp_get_theme()->get('Version')which doesn't change during dev, so the browser serves cached files - Inspect rendered HTML before writing CSS — WordPress adds layout classes (
.is-layout-flow,.wp-container-*) and wraps blocks in extra elements not visible in.htmltemplates - Visual check on localhost before committing — FSE specificity issues are invisible in code review