The styles section of theme.json is where your block theme’s visual identity takes shape. While settings defines what’s available, styles determines how everything looks by default — from typography and colors to spacing and element-level design.
This is Article 2 in our Block Theme Fundamentals series. If you haven’t read Article 1, start with our complete theme.json configuration guide for the foundations of settings, version control, and the overall file structure.
In this guide, you’ll learn how to configure global styles, apply element-level styling, set block-level overrides, and leverage CSS custom properties — all without writing a single line of traditional CSS.
Understanding the Styles Section in theme.json
The styles key in theme.json sits at the top level alongside version, settings, and templateParts. It controls the default visual output of your theme across three distinct layers:
- Top-level styles — Applied globally to the entire site (body-level defaults)
- Element styles — Target specific HTML elements like headings, links, buttons, and captions
- Block styles — Override defaults for specific blocks like paragraphs, groups, or columns
This layered approach mirrors CSS specificity but operates entirely within the JSON configuration. WordPress processes these declarations and generates the corresponding CSS during rendering.
Here’s the basic structure:
{
"version": 3,
"styles": {
"color": {},
"typography": {},
"spacing": {},
"elements": {},
"blocks": {}
}
}
According to the WordPress Theme Handbook, the styles section generates CSS custom properties and applies them to the front end. This means every style you declare in theme.json becomes a reusable CSS variable that both your theme and the editor can consume.
If you’re still evaluating whether to adopt block themes, our block themes vs classic themes migration guide covers the key differences and migration path.
Configuring Top-Level Global Styles
Top-level styles define site-wide defaults. These apply to the body element and cascade down to all content unless overridden by element or block-level styles.
Color
Set your background and text colors globally:
{
"version": 3,
"styles": {
"color": {
"background": "#ffffff",
"text": "#1a1a1a"
}
}
}
In practice, you’ll want to reference your preset palette slugs instead of hardcoded hex values. This keeps your theme maintainable:
{
"styles": {
"color": {
"background": "var(--wp--preset--color--base)",
"text": "var(--wp--preset--color--contrast)"
}
}
}
This approach means changing a single palette value in settings.color.palette automatically propagates everywhere the variable is referenced.
Typography
Global typography styles set the base font family, size, line height, and weight for all content:
{
"styles": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--system-sans)",
"fontSize": "var(--wp--preset--font-size--medium)",
"lineHeight": "1.6",
"fontWeight": "400"
}
}
}
Note that lineHeight accepts unitless values, pixel values, or relative units. Unitless values (like 1.6) are recommended because they scale proportionally with font size — a best practice recommended by web.dev for accessible typography.
Spacing
Control padding and margin at the global level:
{
"styles": {
"spacing": {
"padding": {
"top": "0",
"right": "var(--wp--preset--spacing--50)",
"bottom": "0",
"left": "var(--wp--preset--spacing--50)"
},
"blockGap": "1.5rem"
}
}
}
The blockGap property is particularly powerful — it controls the vertical space between blocks globally. This single value replaces dozens of margin declarations you’d otherwise need in a traditional CSS stylesheet.
Element-Level Styling: Targeting HTML Elements
The elements key lets you style specific HTML elements without touching CSS files. WordPress supports styling for headings, links, buttons, captions, and cite elements.
Styling Headings
You can style all headings uniformly or target individual heading levels (h1 through h6):
{
"styles": {
"elements": {
"heading": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--heading)",
"fontWeight": "700",
"lineHeight": "1.2"
},
"color": {
"text": "var(--wp--preset--color--contrast)"
}
},
"h1": {
"typography": {
"fontSize": "clamp(2.5rem, 5vw, 3.75rem)"
}
},
"h2": {
"typography": {
"fontSize": "clamp(2rem, 4vw, 2.75rem)"
}
},
"h3": {
"typography": {
"fontSize": "clamp(1.5rem, 3vw, 2rem)"
}
}
}
}
}
Using clamp() for font sizes creates fluid typography that adapts to viewport width without media queries. The heading key applies shared styles to all heading levels, while individual h1–h6 keys allow size differentiation. Properties set on individual heading levels override the shared heading properties.
Styling Links
Links support pseudo-class styling for hover and focus states:
{
"styles": {
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--primary)"
},
"typography": {
"textDecoration": "underline"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--secondary)"
},
"typography": {
"textDecoration": "none"
}
},
":focus": {
"outline": {
"color": "var(--wp--preset--color--primary)",
"style": "solid",
"width": "2px",
"offset": "2px"
}
}
}
}
}
}
Including :focus styles is essential for accessibility. The outline properties ensure keyboard users can see which element is focused — a requirement under WCAG 2.1 Success Criterion 2.4.7.
Styling Buttons
Button element styles affect all button-like elements across your theme, including the Button block, Search block submit button, and form submission buttons:
{
"styles": {
"elements": {
"button": {
"color": {
"background": "var(--wp--preset--color--primary)",
"text": "#ffffff"
},
"typography": {
"fontSize": "var(--wp--preset--font-size--small)",
"fontWeight": "600"
},
"border": {
"radius": "4px",
"width": "0"
},
"spacing": {
"padding": {
"top": "0.75rem",
"right": "1.5rem",
"bottom": "0.75rem",
"left": "1.5rem"
}
},
":hover": {
"color": {
"background": "var(--wp--preset--color--secondary)"
}
}
}
}
}
}
This single declaration creates consistent button styling across every block that renders a button element — no more hunting through CSS to update button styles in different contexts.
Block-Level Style Overrides
The blocks key inside styles lets you override global and element defaults for specific blocks. This is where you fine-tune the appearance of individual block types.
Each block is referenced by its namespaced identifier (e.g., core/paragraph, core/group, core/post-title):
{
"styles": {
"blocks": {
"core/paragraph": {
"typography": {
"lineHeight": "1.8"
}
},
"core/post-title": {
"typography": {
"fontSize": "clamp(2rem, 5vw, 3.5rem)",
"fontWeight": "800",
"lineHeight": "1.1"
},
"color": {
"text": "var(--wp--preset--color--contrast)"
}
},
"core/code": {
"color": {
"background": "var(--wp--preset--color--tertiary)",
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--monospace)",
"fontSize": "var(--wp--preset--font-size--small)"
},
"spacing": {
"padding": {
"top": "1.5rem",
"right": "2rem",
"bottom": "1.5rem",
"left": "2rem"
}
},
"border": {
"radius": "6px"
}
},
"core/quote": {
"border": {
"left": {
"color": "var(--wp--preset--color--primary)",
"style": "solid",
"width": "4px"
}
},
"spacing": {
"padding": {
"left": "1.5rem"
}
},
"typography": {
"fontStyle": "italic"
}
},
"core/navigation": {
"typography": {
"fontSize": "var(--wp--preset--font-size--small)",
"fontWeight": "500"
}
}
}
}
}
Block-level overrides are powerful for creating unique visual treatments without custom CSS. For example, the core/quote block above gets a primary-colored left border and italic text — styles that only apply to Quote blocks while the rest of the theme remains unaffected.
Block-Level Element Overrides
You can also override element styles within specific blocks. For example, making links inside the Navigation block a different color:
{
"styles": {
"blocks": {
"core/navigation": {
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"textDecoration": "none"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--primary)"
}
}
}
}
}
}
}
}
This three-tier specificity system — global, element, and block — gives you precise control over your theme’s appearance while maintaining a clean, declarative configuration.
CSS Custom Properties and the Inheritance Model
Every style declared in theme.json is converted into a CSS custom property by WordPress. Understanding this conversion is key to building maintainable themes. If you want to take your CSS further, see how modern CSS features like nesting and container queries complement the theme.json approach.
How WordPress Generates CSS Variables
Settings defined in theme.json generate variables following a predictable naming pattern:
--wp--preset--{category}--{slug}
For example:
- A color palette entry with slug
primarybecomes--wp--preset--color--primary - A font size preset with slug
largebecomes--wp--preset--font-size--large - A spacing preset with slug
50becomes--wp--preset--spacing--50
Custom properties defined under settings.custom use a different pattern:
--wp--custom--{key}--{nested-key}
For instance:
{
"settings": {
"custom": {
"lineHeight": {
"small": "1.3",
"medium": "1.6",
"large": "1.9"
},
"contentSize": "720px"
}
}
}
This generates:
--wp--custom--line-height--small: 1.3;
--wp--custom--line-height--medium: 1.6;
--wp--custom--line-height--large: 1.9;
--wp--custom--content-size: 720px;
Notice how camelCase keys are automatically converted to kebab-case in the generated CSS. This convention keeps your CSS variables consistent regardless of how they’re defined in JSON.
The Inheritance Chain
Styles in theme.json follow a clear inheritance chain:
- WordPress defaults — Core sets baseline styles
- Theme’s theme.json — Your top-level styles override WordPress defaults
- Element styles — Override top-level styles for specific elements
- Block styles — Override both top-level and element styles for specific blocks
- User customizations — Changes made in the Site Editor’s Global Styles panel override everything
This means a user can always override your theme defaults through the Global Styles interface in the Site Editor. Your theme.json styles serve as sensible defaults that users can adjust — a core principle of modern WordPress theme development.
Practical Example: Complete Global Styles Configuration
Here’s a real-world styles configuration that demonstrates all the concepts together. This example creates a cohesive, professional design system:
{
"version": 3,
"styles": {
"color": {
"background": "var(--wp--preset--color--base)",
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--body)",
"fontSize": "var(--wp--preset--font-size--medium)",
"lineHeight": "1.6"
},
"spacing": {
"blockGap": "1.5rem",
"padding": {
"right": "var(--wp--preset--spacing--50)",
"left": "var(--wp--preset--spacing--50)"
}
},
"elements": {
"heading": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--heading)",
"fontWeight": "700",
"lineHeight": "1.2"
},
"color": {
"text": "var(--wp--preset--color--contrast)"
}
},
"h1": {
"typography": {
"fontSize": "clamp(2.5rem, 5vw, 3.75rem)"
}
},
"h2": {
"typography": {
"fontSize": "clamp(1.75rem, 3.5vw, 2.5rem)"
}
},
"link": {
"color": {
"text": "var(--wp--preset--color--primary)"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--secondary)"
}
}
},
"button": {
"color": {
"background": "var(--wp--preset--color--primary)",
"text": "var(--wp--preset--color--base)"
},
"border": {
"radius": "4px"
},
":hover": {
"color": {
"background": "var(--wp--preset--color--contrast)"
}
}
}
},
"blocks": {
"core/post-title": {
"typography": {
"fontWeight": "800",
"lineHeight": "1.1"
}
},
"core/site-title": {
"typography": {
"fontSize": "var(--wp--preset--font-size--medium)",
"fontWeight": "700"
},
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"textDecoration": "none"
}
}
}
},
"core/navigation": {
"typography": {
"fontSize": "var(--wp--preset--font-size--small)"
},
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"textDecoration": "none"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--primary)"
}
}
}
}
},
"core/code": {
"color": {
"background": "var(--wp--preset--color--tertiary)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--monospace)",
"fontSize": "var(--wp--preset--font-size--small)"
},
"border": {
"radius": "6px"
}
}
}
}
}
This configuration establishes a complete visual foundation. The body gets base colors and typography. Headings share a common font family and weight but have individual fluid sizes. Links and buttons follow the brand palette. And specific blocks like Navigation and Code get tailored treatments.
Common Pitfalls and How to Avoid Them
Working with global styles introduces a few common mistakes that can be frustrating if you’re not aware of them.
1. Hardcoded Values Instead of Variables
Avoid writing "background": "#3b82f6" directly in styles. Always reference preset variables like var(--wp--preset--color--primary). Hardcoded values can’t be overridden by users in the Site Editor and create maintenance headaches when you update your palette.
2. Forgetting the Version Number
The version key must be set to 3 (as of WordPress 6.6+) for the latest features to work. Running an older version number limits available style properties and may produce unexpected output.
3. Ignoring the Editor Experience
Styles in theme.json apply to both the front end and the block editor. Test your styles in both contexts. A style that looks correct on the front end may render differently in the editor due to editor-specific wrapper elements.
4. Over-Relying on Block-Level Overrides
If you find yourself overriding the same property across many blocks, consider whether the top-level or element style should change instead. Block-level overrides should be exceptions, not the rule. A good theme.json configuration should have more top-level and element styles than block overrides.
5. Not Testing User Override Behavior
Remember that users can modify your styles through Global Styles in the Site Editor. Test what happens when a user changes the primary color or base font. If your theme breaks or looks inconsistent, your theme.json styles may be too tightly coupled.
Debugging Global Styles
When styles aren’t applying as expected, use these techniques:
- Inspect the generated CSS — Open browser DevTools and search for
--wp--presetin the Styles panel to see all generated custom properties - Check the global-styles-inline-css — WordPress outputs theme.json styles in a
<style>tag with IDglobal-styles-inline-css. Inspect this to see exactly what WordPress generated from your configuration - Validate your JSON — A misplaced comma or bracket can silently break entire sections. Use
wp-envor enableWP_DEBUGto catch JSON parsing errors - Check specificity conflicts — If a style isn’t applying, another declaration with higher specificity may be winning. Block-level styles override element styles, which override top-level styles
What’s Next in This Series
You now have a thorough understanding of the styles section in theme.json. You know how to set global defaults, target HTML elements, override styles per block, and leverage the CSS custom property system WordPress generates automatically.
In the next article in our Block Theme Fundamentals series, we’ll cover template and template part architecture — how to build reusable layouts using block markup and connect them through theme.json.
Start experimenting with your own theme.json styles configuration. The more you work with the inheritance model, the more intuitive it becomes. The official WordPress Global Styles reference is an excellent companion resource as you build.
