Skip to content

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 .html templates
  • Visual check on localhost before committing — FSE specificity issues are invisible in code review