How to Add Custom Block Styles (theme.json vs CSS)
How to Add Custom Block Styles (theme.json vs CSS)
Custom block styles are one of the cleanest ways to give editors “approved design options”
without letting them freestyle layouts.
In modern WordPress, you can add styles in two main ways:
theme.json(Global Styles / block style variations)- Classic CSS (theme stylesheet + optional
register_block_style())
This article explains when each approach wins, how to implement both safely,
and how to keep output predictable across themes and environments.
What “Custom Block Styles” Means
A “block style” is a named variation that appears in the editor under:
Block sidebar → Styles
When selected, WordPress adds a class to the block:
is-style-your-style-name
Your theme then provides CSS for that class.
Choose the Right Approach
Use theme.json When
- You are building a block theme or heavily using Global Styles
- You want design tokens (colors, typography, spacing) centrally managed
- You want consistent editor + front-end styling alignment
- You want to leverage core style presets instead of custom CSS everywhere
Use CSS When
- You are using a classic theme
- You want full control with minimal Gutenberg coupling
- You only need a few simple variations
- You want styles to work even if theme.json evolves later
In practice, many pro builds are hybrid:
theme.jsonfor global design tokens and editor constraints- CSS for specific component-like styles
Option A: Add Block Styles via PHP + CSS (Classic Theme Friendly)
This is the most flexible approach and works in both classic and block themes.
You register a style name and provide CSS for the generated class.
Register a Custom Style for the Button Block
<?php
add_action( 'init', function () {
register_block_style(
'core/button',
array(
'name' => 'soft-pill',
'label' => __( 'Soft Pill', 'your-textdomain' ),
)
);
} );
This exposes a new “Soft Pill” style in the editor.
Now add CSS in your theme stylesheet:
/* Button: Soft Pill */
.wp-block-button.is-style-soft-pill .wp-block-button__link {
border-radius: 9999px;
padding: 0.75em 1.25em;
text-decoration: none;
}
Because the class is deterministic, this is easy to maintain and easy to review in Git.
Example: Style Variation for Quote Block
<?php
add_action( 'init', function () {
register_block_style(
'core/quote',
array(
'name' => 'callout',
'label' => __( 'Callout', 'your-textdomain' ),
)
);
} );
/* Quote: Callout */
.wp-block-quote.is-style-callout {
border-left-width: 0.4em;
padding-left: 1em;
}
Option B: Define Custom Block Styles in theme.json (Block-Theme Native)
In block themes, you can add styles directly in theme.json
under the block’s styles or variations-like structure.
A practical approach is to:
- Define the style variation name and label in
theme.json - Still provide custom CSS for the
is-style-*class if needed
Example (illustrative structure):
{
"version": 2,
"styles": {},
"settings": {},
"customTemplates": [],
"templateParts": [],
"blocks": {
"core/button": {
"styles": [
{
"name": "soft-pill",
"label": "Soft Pill"
}
]
}
}
}
Then use CSS the same way:
.wp-block-button.is-style-soft-pill .wp-block-button__link {
border-radius: 9999px;
}
The advantage is that the style becomes part of the theme’s design system.
The downside is that theme.json is more tightly coupled to Gutenberg and theme architecture.
Which Is More Maintainable?
CSS + register_block_style() Wins for Most Classic Themes
- Works everywhere
- Easy to diff and review
- Doesn’t require a block-theme structure
- Does not depend on theme.json syntax changes
theme.json Wins When You Want a Centralized Design System
- Design tokens are consistent
- You can limit editor features (colors/typography/spacing)
- Editor and front end stay aligned more naturally
Best Practice: Keep Style Names Stable
Once published, keep your style name stable:
soft-pillshould not becomepill-soft
Why? The class is stored in post content.
Renaming a style breaks existing posts unless you migrate content.
Best Practice: Avoid Too Many Styles
A style panel with 10 variations becomes a design system you must support forever.
Keep your list short and intentional:
- 1–3 styles per block is usually enough
- Prefer “semantic” styles (Callout, Warning, Note) over aesthetic styles (Blue, Rounded)
Best Practice: Match Editor and Front-End Styles
If the editor preview doesn’t match the front-end, editors will make bad decisions.
Ensure block styles load in both places:
- Front-end: normal enqueue
- Editor:
enqueue_block_editor_assetsor editor stylesheet support
Load Editor CSS for Block Styles
<?php
add_action( 'enqueue_block_editor_assets', function () {
wp_enqueue_style(
'wpct-editor-block-styles',
get_theme_file_uri( '/assets/css/editor-block-styles.css' ),
array(),
'1.0.0'
);
} );
If you already have a unified stylesheet, you can also reuse it carefully.
Example: Add a “Note” Style for Paragraph
This is a clean, semantic style that can be used across content.
<?php
add_action( 'init', function () {
register_block_style(
'core/paragraph',
array(
'name' => 'note',
'label' => __( 'Note', 'your-textdomain' ),
)
);
} );
p.is-style-note {
padding: 1em;
border-left-width: 0.35em;
}
Common Mistakes
- Creating style names based on colors (hard to maintain)
- Renaming style slugs after publishing
- Not loading CSS in the editor
- Using block styles as a substitute for a real component system
Summary
- Block styles add “approved variations” without giving editors full design freedom
register_block_style()+ CSS is the most portable approach (especially for classic themes)theme.jsonis best when you want a centralized design system for a block theme- Keep style slugs stable and the style list short
- Load the same styles in both editor and front-end for reliable previews
If you’re building WP Code Tips content, a practical angle is:
use PHP + CSS for most client themes, and highlight theme.json as the design-system layer
when moving into block themes.
🎨 Want to learn more? Visit our WordPress Customization Hub for tips and advanced techniques.