Documentation · Previous: Style variations · Next: SCSS export
The examples below run inside an entrypoint callable with a ThemeJson parameter.
Embedded SCSS is compiled into the JSON styles.css field. To create an SCSS
partial for a separate Sass build, use SCSS reference export.
WordPress handles global and scoped custom CSS differently. The generator keeps
those two formats separate and validates scoped CSS before writing it to
theme.json.
Use styles()->css() and styles()->appendCss() for global CSS, or call the
same methods after blocks() or elements() for scoped CSS. The first argument
contains the CSS. The optional second argument declares its root selector,
which the generator removes and converts into WordPress & scoped syntax.
css(), appendCss(), scss(), and appendScss() accept an optional selector
as their second argument:
$styles->css($css, $selector);Without it, the generator expects CSS already written for its destination:
- global CSS uses complete rules;
- scoped CSS uses declarations and nested rules beginning with
&.
With it, the generator accepts regular CSS rooted at that selector and converts it to the scoped format required by WordPress:
$button = $themeJson->styles()->blocks('core/button');
$button->css(
'.wp-block-button { color: red; }'
. '.wp-block-button a { color: blue; }',
'.wp-block-button'
);This produces a scoped value equivalent to:
color: red;
& a {
color: blue;
}Every input selector must be the supplied selector or structurally belong to
it. Unrelated selectors cause an InvalidArgumentException.
Global CSS is stored in styles.css. WordPress adds it to the stylesheet
without changing it, so write complete CSS rules:
$themeJson
->styles()
->css('body { color: red; }');Use appendCss() to add more global rules without replacing existing CSS:
$styles = $themeJson->styles();
$styles->css('body { color: red; }');
$styles->appendCss('a { color: blue; }');The resulting theme.json contains:
{
"styles": {
"css": "body { color: red; }a { color: blue; }"
}
}Global CSS is the correct place for independent selectors, at-rules, and rules that are not owned by a block or element:
$themeJson->styles()->css(
'@media (min-width: 48rem) { .card_title { display: grid; } }'
);CSS configured through blocks() or elements() is scoped by WordPress:
$button = $themeJson
->styles()
->blocks('core/button');
$button->css('color: red;');
$button->appendCss('& a { color: blue; }');Scoped CSS uses the format expected by WP_Theme_JSON:
- declarations without braces apply to the current block or element;
- each nested rule starts with
&; - WordPress replaces
&with its resolved selector.
The stored value is:
color: red;& a { color: blue; }WordPress then generates selectors for the configured block. The exact root selector comes from WordPress block metadata.
Apply declarations to the current scope:
$button->css('color: red; padding: 1rem;');Target a descendant:
$button->appendCss('& .icon { color: blue; }');Target a state:
$button->appendCss('&:hover { color: blue; }');Target a child or sibling:
$button->appendCss('& > span { display: block; }');
$button->appendCss('& + .notice { margin-top: 1rem; }');A nested scoped rule without & is invalid:
// Wrong: WordPress would concatenate this with the root declarations.
$button->appendCss('a { color: blue; }');The generator throws instead of allowing WordPress to produce malformed CSS. Write this:
$button->appendCss('& a { color: blue; }');An independent selector does not belong inside scoped CSS:
$card = $themeJson->styles()->blocks('core/group');
// Wrong: .card_title is not inside the .card scope.
$card->css('.card { color: red; } .card_title { color: blue; }', '.card');Choose the intended relationship explicitly:
// Descendant: .card .card_title
$card->css('.card { color: red; } .card .card_title { color: blue; }', '.card');
// Independent selector: write it as global CSS.
$themeJson->styles()->css('.card_title { color: blue; }');The same rule applies to hyphenated names:
// Wrong: .card-title is a separate class, not a descendant of .card.
$card->css('.card-title { color: blue; }', '.card');Sharing a string prefix does not make selectors part of the same scope.
When using the selector argument, every selector in the input must be the scope itself or structurally belong to it:
/* Accepted */
.card
.card:hover
.card .title
.card > .title
.card + .notice
/* Rejected */
.other
.card-title
.card_titleRejected selectors cause an InvalidArgumentException. The message identifies
the selector and recommends either a descendant selector or global CSS. Nothing
is silently discarded.
css() writes the scoped CSS value. appendCss() preserves that value and adds
the new declarations or nested rules:
$button->css('color: red;');
$button->appendCss('&:hover { color: blue; }');Use the same syntax consistently:
- without the selector argument, provide WordPress scoped syntax yourself;
- with the selector argument, provide regular CSS rooted at that selector.
$button->css('.button { color: red; }', '.button');
$button->appendCss('.button:hover { color: blue; }', '.button');scss() and appendScss() compile SCSS first, then apply the same scope
validation as CSS:
$button->scss(
'.button {'
. ' color: red;'
. ' &:hover { color: blue; }'
. '}',
'.button'
);Compiled selectors must still belong to the requested scope. For example,
SCSS &__title compiles to the independent class .card__title; it is not a
descendant of .card and is rejected in .card-scoped CSS.
Use an actual descendant when that is the intended result:
.card {
.card__title {
color: blue;
}
}Use global scss() when the compiled stylesheet contains independent
selectors.
WordPress does not preserve @media or @supports correctly inside scoped
block custom CSS. The generator rejects scoped at-rules:
// Wrong: rejected in a block or element scope.
$button->css('@media (min-width: 48rem) { color: blue; }');Place the complete rule in global CSS instead:
$themeJson->styles()->css(
'@media (min-width: 48rem) { .wp-block-button { color: blue; } }'
);| Goal | API and syntax |
|---|---|
| Complete independent rules | Global styles()->css() |
| Add another independent rule | Global styles()->appendCss() |
| Style the current block or element | Scoped css('color: red;') |
| Style a descendant or state | Scoped appendCss('& ... { ... }') |
| Convert regular scoped CSS | Pass its root selector as the second argument |
| Add scoped at-rules | Not supported; use global CSS |
| Use independent selectors in scoped CSS | Not supported; use global CSS |