A practical guide to understanding and using theme.json in WordPress block themes.
What is theme.json?
Think of theme.json as your theme’s control center. It’s a single file where you define your design system—colors, fonts, spacing, and styles—and WordPress automatically makes everything work together across your entire site.
Basic File Structure
Every theme.json file starts with the same basic structure:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {},
"styles": {}
}
The $schema line is important—it enables autocomplete and IntelliSense in code editors like VS Code. As you type, your editor will suggest available properties and values, which makes working with theme.json much easier and helps catch typos before they cause problems.
The version number is critical—it tells WordPress which theme.json features are available to use:
- Version 1: Introduced in WordPress 5.8 (basic functionality)
- Version 2: Added in WordPress 5.9 (expanded features, better block styling)
- Version 3: Current standard in WordPress 6.6+ (fluid typography, improved layout controls)
Always use version 3 if you’re building a new theme for modern WordPress. The version number determines what properties and features will actually work in your theme.json file. Using an older version means you won’t have access to newer features, while using a version number that doesn’t exist yet will cause errors.
Part 1: Setting Up Your Design System
The settings section is where you define your design tokens—the building blocks of your site’s design.
Colors
Let’s start with a simple color palette:
{
"version": 3,
"settings": {
"color": {
"defaultPalette": false,
"defaultGradients": false,
"defaultDuotone": false,
"custom": true,
"palette": [
{
"slug": "base",
"color": "#ffffff",
"name": "Base"
},
{
"slug": "contrast",
"color": "#1f2937",
"name": "Contrast"
},
{
"slug": "accent-1",
"color": "#0066cc",
"name": "Primary Blue"
},
{
"slug": "accent-2",
"color": "#ff6b35",
"name": "Secondary Orange"
},
{
"slug": "accent-3",
"color": "#7c3aed",
"name": "Purple"
}
]
}
}
}
What these properties mean:
slug: The internal name you’ll use in code (no spaces, use hyphens)color: The actual hex code, RGB, or HSL valuename: The friendly name users see in the editor
Important naming convention: Notice that the slug uses generic names like base, contrast, and accent-1, while the name is more descriptive like “Primary Blue”. This is WordPress best practice and follows the same pattern used in default WordPress themes.
Why use generic slugs?
- Makes it easier to change your color scheme later without breaking your theme
- Users can understand the color’s purpose (accent, base) without being tied to a specific color
- Your theme’s styles remain semantic:
var(--wp--preset--color--accent-1)makes sense even if you change from blue to green - Consistency with WordPress core themes makes your theme more familiar to users
Common slug patterns:
base– Main background colorcontrast– Main text/foreground coloraccent-1,accent-2,accent-3– Brand/accent colorsaccent-4,accent-5– Additional accents if needed
WordPress automatically creates CSS variables for each color: var(--wp--preset--color--accent-1)
Color Settings: Defaults and Custom Colors
The boolean settings at the top of the color object control what color options appear in the editor:
defaultPalette:
true– Shows WordPress’s default theme colors alongside your custom palettefalse– Hides WordPress defaults, showing only your defined colors (recommended for branded themes)
defaultGradients:
true– Shows WordPress’s default gradientsfalse– Hides default gradients, showing only your custom gradients
defaultDuotone:
true– Shows WordPress’s default duotone filters for imagesfalse– Hides default duotones, showing only your custom duotones
custom:
true– Users can pick any color using a color picker (beyond your defined palette)false– Users can only choose from your defined colors (stricter brand control)
Best practice: Set all defaults to false and custom to true (or false if you need strict brand compliance). This gives you a clean, professional color picker with just your brand colors.
Adding Gradients
Gradients work similarly to colors:
{
"settings": {
"color": {
"gradients": [
{
"slug": "primary-gradient",
"gradient": "linear-gradient(135deg, var(--wp--preset--color--accent-1) 0%, var(--wp--preset--color--accent-2) 100%)",
"name": "Primary to Secondary"
},
{
"slug": "subtle-gradient",
"gradient": "linear-gradient(180deg, var(--wp--preset--color--base) 0%, var(--wp--preset--color--contrast) 100%)",
"name": "Subtle Background"
},
{
"slug": "radial-accent",
"gradient": "radial-gradient(circle at top right, var(--wp--preset--color--accent-1) 0%, transparent 70%)",
"name": "Radial Accent"
}
]
}
}
}
Gradient tips:
- You can reference your color variables inside gradient definitions
- Use
linear-gradient,radial-gradient, orconic-gradient - These create variables like:
var(--wp--preset--gradient--primary-gradient)
Adding Duotones
Duotones are color filters that can be applied to images, creating two-tone effects:
{
"settings": {
"color": {
"duotone": [
{
"slug": "primary-duotone",
"colors": [
"#000000",
"#0066cc"
],
"name": "Dark and Accent"
},
{
"slug": "warm-duotone",
"colors": [
"#8B4513",
"#FFD700"
],
"name": "Warm Sepia"
},
{
"slug": "cool-duotone",
"colors": [
"#1a1a2e",
"#16213e"
],
"name": "Cool Dark"
}
]
}
}
}
Duotone notes:
- Duotones always use exactly two colors
- First color is for shadows, second is for highlights
- These only affect images when applied in the block editor
- You can use hex codes directly or reference your color variables
- Creates variables like:
var(--wp--preset--duotone--primary-duotone)
Complete Color Settings Example
Here’s everything together:
{
"settings": {
"color": {
"defaultPalette": false,
"defaultGradients": false,
"defaultDuotone": false,
"custom": true,
"customGradient": true,
"customDuotone": true,
"palette": [
{
"slug": "base",
"color": "#ffffff",
"name": "Base"
},
{
"slug": "contrast",
"color": "#1f2937",
"name": "Contrast"
},
{
"slug": "accent-1",
"color": "#0066cc",
"name": "Primary Blue"
},
{
"slug": "accent-2",
"color": "#ff6b35",
"name": "Secondary Orange"
}
],
"gradients": [
{
"slug": "primary-gradient",
"gradient": "linear-gradient(135deg, var(--wp--preset--color--accent-1) 0%, var(--wp--preset--color--accent-2) 100%)",
"name": "Primary to Secondary"
}
],
"duotone": [
{
"slug": "primary-duotone",
"colors": ["#000000", "#0066cc"],
"name": "Dark and Accent"
}
]
}
}
}
Additional color controls:
customGradient: Set totrueto let users create custom gradients,falseto restrict to your defined gradients onlycustomDuotone: Set totrueto let users create custom duotones,falseto restrict to your defined duotones only
Typography
Now let’s add font families and sizes:
{
"settings": {
"typography": {
"fontFamilies": [
{
"slug": "body",
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
"name": "Body"
},
{
"slug": "heading",
"fontFamily": "Georgia, 'Times New Roman', serif",
"name": "Heading"
}
],
"fontSizes": [
{
"slug": "small",
"size": "0.875rem",
"name": "Small"
},
{
"slug": "medium",
"size": "1rem",
"name": "Medium"
},
{
"slug": "large",
"size": "1.5rem",
"name": "Large"
},
{
"slug": "x-large",
"size": "2rem",
"name": "Extra Large"
}
]
}
}
}
These create variables like:
var(--wp--preset--font-family--body)var(--wp--preset--font-size--large)
Important: Use rem instead of px
Notice all the font sizes use rem units instead of px. This is best practice for several reasons:
- Accessibility: Users who adjust their browser’s base font size will see your text scale proportionally
- Consistency:
1remequals the root font size (usually 16px), making calculations predictable - Responsive design: rem values scale better across different devices and user preferences
- Maintainability: Change the root font size once, and everything scales accordingly
This applies to all sizing in theme.json—spacing, borders, and even layout widths benefit from relative units over fixed pixels. The exception is when you specifically need a fixed size that shouldn’t scale (like a 1px border).
Using Custom Font Files
If you want to use custom fonts (web fonts you host yourself), you need to define them in theme.json with @font-face information:
{
"settings": {
"typography": {
"fontFamilies": [
{
"slug": "custom-font",
"name": "Custom Font",
"fontFamily": "'Custom Font', sans-serif",
"fontFace": [
{
"fontFamily": "Custom Font",
"fontWeight": "400",
"fontStyle": "normal",
"fontDisplay": "swap",
"src": [
"file:./assets/fonts/custom-font-regular.woff2"
]
},
{
"fontFamily": "Custom Font",
"fontWeight": "700",
"fontStyle": "normal",
"fontDisplay": "swap",
"src": [
"file:./assets/fonts/custom-font-bold.woff2"
]
}
]
}
]
}
}
}
Important notes about font files:
- File location: Place your font files in your theme directory (commonly in
assets/fonts/) - File path: Use
file:./path/to/font.woff2– thefile:prefix tells WordPress this is a theme file - Font formats: Use
.woff2format (best compression and modern browser support) - Font weights: Define each weight/style combination separately
- fontDisplay: Use
"swap"to prevent invisible text while fonts load
Folder structure example:
your-theme/
├── theme.json
└── assets/
└── fonts/
├── custom-font-regular.woff2
├── custom-font-bold.woff2
└── custom-font-italic.woff2
Using Google Fonts
For Google Fonts, you don’t need to include font files. Just reference the font family name and load the font in your theme’s functions.php:
In theme.json:
{
"settings": {
"typography": {
"fontFamilies": [
{
"slug": "inter",
"name": "Inter",
"fontFamily": "'Inter', sans-serif"
},
{
"slug": "merriweather",
"name": "Merriweather",
"fontFamily": "'Merriweather', serif"
}
]
}
}
}
In functions.php:
<?php
function my_theme_enqueue_fonts() {
wp_enqueue_style(
'google-fonts',
'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Merriweather:wght@400;700&display=swap',
array(),
null
);
}
add_action( 'wp_enqueue_scripts', 'my_theme_enqueue_fonts' );
Google Fonts tips:
- Only load the weights you actually use to keep your site fast
- Use
&display=swapin the URL to prevent invisible text - Consider privacy implications (Google Fonts loads from Google’s servers)
- For better privacy and performance, you can download Google Fonts and host them as custom font files instead
System Font Stacks (No Files Needed)
The fastest option is using system fonts that are already installed on users’ devices:
{
"settings": {
"typography": {
"fontFamilies": [
{
"slug": "system-sans",
"name": "System Sans",
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"
},
{
"slug": "system-serif",
"name": "System Serif",
"fontFamily": "Georgia, 'Times New Roman', Times, serif"
},
{
"slug": "system-mono",
"name": "System Mono",
"fontFamily": "'Courier New', Courier, monospace"
}
]
}
}
}
System fonts load instantly, use no bandwidth, and look native to each operating system.
Spacing
Spacing controls padding, margins, and gaps throughout your site:
{
"settings": {
"spacing": {
"spacingScale": {
"steps": 0
},
"spacingSizes": [
{
"slug": "xs",
"size": "0.5rem",
"name": "Extra Small"
},
{
"slug": "sm",
"size": "1rem",
"name": "Small"
},
{
"slug": "md",
"size": "1.5rem",
"name": "Medium"
},
{
"slug": "lg",
"size": "2rem",
"name": "Large"
},
{
"slug": "xl",
"size": "3rem",
"name": "Extra Large"
}
],
"padding": true,
"margin": true,
"blockGap": true
}
}
}
Setting padding, margin, and blockGap to true enables these controls in the block editor.
These create: var(--wp--preset--spacing--md)
Understanding spacingScale
The spacingScale setting controls whether WordPress generates additional spacing sizes automatically:
"steps": 0– WordPress won’t generate any spacing sizes automatically. You have complete control and must define every spacing size you want inspacingSizes. This is what most theme developers prefer."steps": 10(or any number greater than 0) – WordPress will automatically generate a fluid spacing scale with that many steps between your minimum and maximum spacing values. This creates responsive spacing that scales based on viewport size.
When to use which:
Use "steps": 0 when:
- You want explicit control over every spacing option
- You’re defining a specific design system
- You want consistent spacing across all screen sizes
Use "steps": 10 (or higher) when:
- You want spacing to scale fluidly with screen size
- You’re comfortable with WordPress generating intermediate values
- You want fewer manual spacing definitions
Most beginners should start with "steps": 0 and define their own spacing sizes as shown above. It’s more predictable and easier to understand.
Shadows
Shadows add depth to your design. You can define a set of shadow presets that users can apply to blocks:
{
"settings": {
"shadow": {
"defaultPresets": false,
"presets": [
{
"slug": "sm",
"shadow": "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
"name": "Small"
},
{
"slug": "md",
"shadow": "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)",
"name": "Medium"
},
{
"slug": "lg",
"shadow": "0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
"name": "Large"
},
{
"slug": "xl",
"shadow": "0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)",
"name": "Extra Large"
},
{
"slug": "inner",
"shadow": "inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)",
"name": "Inner Shadow"
}
]
}
}
}
Shadow notes:
defaultPresets: falsehides WordPress’s default shadows (recommended)- Shadow values use standard CSS box-shadow syntax
- You can use multiple shadows separated by commas
- Creates variables like:
var(--wp--preset--shadow--md) - Shadows can be applied to Group blocks, Columns, and other container blocks
Layout Settings
Layout settings control how content is sized and aligned on your site. These are crucial for creating a well-structured theme:
{
"settings": {
"layout": {
"contentSize": "940px",
"wideSize": "1440px"
},
"useRootPaddingAwareAlignments": true
}
}
Understanding contentSize and wideSize
These two settings define the width constraints for your content:
contentSize: The default maximum width for regular content
- Use this for optimal reading width (typically 600-800px)
- Text blocks and standard content default to this width
- Think of this as your “comfortable reading width”
wideSize: The maximum width for wide-aligned content
- Use this for your overall site container width (typically 1200-1400px)
- Blocks set to “wide” alignment will use this width
- Gives you flexibility for images, galleries, or full-width sections that need more space
Example in practice:
Full Width Container 100%
contentSize 940px
wideSize 1440px
Recommended values:
- contentSize:
640pxto940pxfor text-heavy sites - wideSize:
1200pxto1440pxfor most modern sites
Understanding useRootPaddingAwareAlignments
This setting changes how full-width and wide alignments behave with your site’s padding:
useRootPaddingAwareAlignments: true (Recommended)
- Full-width blocks respect your site’s root padding
- Creates a consistent gutter/margin on the edges
- Full-width blocks won’t stick to the very edge of the screen
- Better for modern, polished designs
useRootPaddingAwareAlignments: false (Legacy behavior)
- Full-width blocks truly go edge-to-edge
- No padding or margin on full-width content
- Can create cramped layouts on mobile
- Use only if you need truly full-bleed designs
Best practice: Set this to true for better mobile responsiveness and visual breathing room.
Complete Layout Example
{
"settings": {
"layout": {
"contentSize": "720px",
"wideSize": "1280px"
},
"useRootPaddingAwareAlignments": true,
"spacing": {
"padding": true
}
},
"styles": {
"spacing": {
"padding": {
"top": "var(--wp--preset--spacing--md)",
"right": "var(--wp--preset--spacing--md)",
"bottom": "var(--wp--preset--spacing--md)",
"left": "var(--wp--preset--spacing--md)"
}
}
}
}
This setup gives you:
- Regular content constrained to 720px for comfortable reading
- Wide-aligned content up to 1280px
- Consistent padding around the edges
- Proper alignment behavior with padding
Template Parts and Custom Templates
WordPress allows you to define template parts (reusable sections like headers and footers) and custom templates (page layouts) in your theme.json. This helps organize your theme and makes these components available in the Site Editor.
Template Parts
Template parts are reusable sections that appear across multiple pages. Common examples include headers, footers, and sidebars:
{
"settings": {
"templateParts": [
{
"name": "header",
"title": "Header",
"area": "header"
},
{
"name": "footer",
"title": "Footer",
"area": "footer"
},
{
"name": "sidebar",
"title": "Sidebar",
"area": "uncategorized"
}
]
}
}
Template part properties:
name: The filename (without .html extension) from your/parts/directorytitle: The user-friendly name shown in the Site Editorarea: Defines where this part is used. Options:header– For site headersfooter– For site footersuncategorized– For any other reusable section
Your file structure would look like:
your-theme/
├── theme.json
├── parts/
│ ├── header.html
│ ├── footer.html
│ └── sidebar.html
└── templates/
└── index.html
Custom Templates
Custom templates define different page layouts users can choose from. These appear in the template picker when creating pages:
{
"settings": {
"customTemplates": [
{
"name": "page-no-title",
"title": "Page (No Title)",
"postTypes": ["page"]
},
{
"name": "page-sidebar",
"title": "Page with Sidebar",
"postTypes": ["page"]
},
{
"name": "single-portfolio",
"title": "Portfolio Item",
"postTypes": ["portfolio"]
},
{
"name": "archive-wide",
"title": "Wide Archive",
"postTypes": ["post"]
}
]
}
}
Custom template properties:
name: The filename (without .html extension) from your/templates/directorytitle: The user-friendly name shown in the template pickerpostTypes: Array of post types this template can be used with. Common values:["page"]– Available for pages["post"]– Available for blog posts["page", "post"]– Available for both["your-custom-post-type"]– For custom post types
Your templates folder would include:
your-theme/
├── theme.json
└── templates/
├── index.html (required)
├── page-no-title.html
├── page-sidebar.html
├── single-portfolio.html
└── archive-wide.html
Complete Example with Template Parts and Templates
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"templateParts": [
{
"name": "header",
"title": "Header",
"area": "header"
},
{
"name": "header-minimal",
"title": "Minimal Header",
"area": "header"
},
{
"name": "footer",
"title": "Footer",
"area": "footer"
}
],
"customTemplates": [
{
"name": "page-full-width",
"title": "Full Width Page",
"postTypes": ["page"]
},
{
"name": "page-landing",
"title": "Landing Page",
"postTypes": ["page"]
},
{
"name": "single-team",
"title": "Team Member",
"postTypes": ["team"]
}
]
}
}
Why define these in theme.json?
- Makes your template parts and custom templates discoverable in the Site Editor
- Provides user-friendly names instead of showing filenames
- Organizes template parts by area (header, footer, etc.)
- Controls which post types can use which templates
- Creates a better user experience for non-technical users
Note: Even if you don’t define templateParts or customTemplates in theme.json, WordPress will automatically discover template files in your /templates/ and /parts/ directories. However, defining them explicitly gives you:
- Control over the display names
- Better organization in the Site Editor
- Specific post type targeting for custom templates
- Professional presentation of your theme’s capabilities
Part 2: Using Your Settings (The styles Section)
Now that you’ve defined your design tokens, the styles section is where you actually apply them. This is where beginners often get confused, so let’s break it down clearly.
Elements vs Blocks: What’s the Difference?
Elements are HTML elements that appear across your entire site—things like links, headings, and buttons that exist everywhere.
Blocks are specific WordPress blocks—like the Paragraph block, Image block, or Group block.
Styling Elements
Elements live under styles.elements. Here’s how to style common HTML elements using your defined settings:
{
"styles": {
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--accent-1)"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--accent-2)"
}
}
},
"h1": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--heading)",
"fontSize": "var(--wp--preset--font-size--x-large)",
"fontWeight": "700",
"lineHeight": "1.2"
},
"spacing": {
"margin": {
"top": "var(--wp--preset--spacing--lg)",
"bottom": "var(--wp--preset--spacing--md)"
}
}
},
"h2": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--heading)",
"fontSize": "var(--wp--preset--font-size--large)"
}
},
"button": {
"color": {
"background": "var(--wp--preset--color--accent-1)",
"text": "var(--wp--preset--color--base)"
},
"spacing": {
"padding": {
"top": "var(--wp--preset--spacing--sm)",
"bottom": "var(--wp--preset--spacing--sm)",
"left": "var(--wp--preset--spacing--md)",
"right": "var(--wp--preset--spacing--md)"
}
},
"border": {
"radius": "4px"
}
}
}
}
}
Available elements you can style:
linkheading(applies to all h1-h6 at once)h1,h2,h3,h4,h5,h6(individual heading levels)buttoncaption(for images and galleries)cite
The Special ‘heading’ Element
The heading element is particularly useful because it lets you apply styles to ALL heading levels (h1-h6) at once, which you can then override for specific levels:
{
"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": "var(--wp--preset--font-size--x-large)"
}
},
"h2": {
"typography": {
"fontSize": "var(--wp--preset--font-size--large)"
}
}
}
}
}
In this example:
- ALL headings get the heading font family, bold weight, and tight line-height from the
headingelement - Then h1 and h2 get their specific font sizes
- This prevents repetition and keeps your code DRY (Don’t Repeat Yourself)
When to use heading vs individual heading elements:
- Use
headingfor properties shared by ALL headings (font family, color, weight) - Use individual
h1,h2, etc. for properties unique to each level (font size, spacing) - Individual heading styles override the
headingelement styles
Styling Blocks
Blocks live under styles.blocks and use WordPress’s block name format. Here’s how to style specific blocks:
{
"styles": {
"blocks": {
"core/paragraph": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--body)",
"fontSize": "var(--wp--preset--font-size--medium)",
"lineHeight": "1.6"
},
"spacing": {
"margin": {
"bottom": "var(--wp--preset--spacing--sm)"
}
}
},
"core/group": {
"spacing": {
"padding": {
"top": "var(--wp--preset--spacing--md)",
"bottom": "var(--wp--preset--spacing--md)"
},
"blockGap": "var(--wp--preset--spacing--sm)"
}
},
"core/columns": {
"spacing": {
"blockGap": "var(--wp--preset--spacing--md)"
}
},
"core/quote": {
"color": {
"background": "var(--wp--preset--color--base)",
"text": "var(--wp--preset--color--contrast)"
},
"spacing": {
"padding": "var(--wp--preset--spacing--md)"
},
"border": {
"left": {
"color": "var(--wp--preset--color--accent-1)",
"width": "4px",
"style": "solid"
}
},
"typography": {
"fontStyle": "italic"
}
}
}
}
}
Styling Elements Within Specific Blocks
You can also style elements (like links, headings, buttons) differently when they appear inside specific blocks. This is powerful for creating contextual designs:
{
"styles": {
"blocks": {
"core/group": {
"color": {
"background": "var(--wp--preset--color--accent-1)",
"text": "var(--wp--preset--color--base)"
},
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--base)"
},
":hover": {
"color": {
"text": "var(--wp--preset--color--accent-2)"
}
}
},
"heading": {
"color": {
"text": "var(--wp--preset--color--base)"
}
}
}
},
"core/cover": {
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--base)"
}
},
"h2": {
"typography": {
"fontSize": "var(--wp--preset--font-size--x-large)"
}
}
}
}
}
}
}
How block-level element styling works:
- Global elements (in
styles.elements) apply everywhere - Block-level elements (in
styles.blocks.{block-name}.elements) override global styles for that specific block - This lets you create special styling for links/headings/buttons that only applies in certain contexts
Common use cases:
- Light-colored links inside dark Group blocks
- Larger headings inside Cover blocks
- Different button styles inside specific container blocks
- Special link treatments in navigation or footer areas
The cascade order:
Global element styles
↓ (overridden by)
Block-level element styles
↓ (overridden by)
User selections in the editor
Global Styles
You can also set site-wide defaults at the root level:
{
"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": "var(--wp--preset--spacing--md)"
}
}
}
These apply everywhere unless overridden by more specific styles.
How to Reference Your Settings as Variables
This is the key to making theme.json powerful. Any setting you define in the settings section automatically becomes a CSS custom property (variable) that you can use in the styles section.
The Variable Pattern
WordPress creates variables following this pattern:
var(--wp--preset--{type}--{slug})
Where:
{type}is the category:color,font-family,font-size,spacing{slug}is the slug you defined in settings
Examples:
From your color palette:
"slug": "accent-1"
Becomes: var(--wp--preset--color--accent-1)
From your font families:
"slug": "heading"
Becomes: var(--wp--preset--font-family--heading)
From your spacing:
"slug": "md"
Becomes: var(--wp--preset--spacing--md)
Why Use Variables?
Instead of writing:
"color": {
"text": "#0066cc"
}
Write:
"color": {
"text": "var(--wp--preset--color--accent-1)"
}
Benefits:
- Change the color once in settings, it updates everywhere
- Users can override your colors in the Site Editor
- Your theme is more maintainable
- You create a consistent design system
Complete Working Example
Here’s a simple but complete theme.json putting it all together:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"color": {
"palette": [
{
"slug": "base",
"color": "#ffffff",
"name": "Base"
},
{
"slug": "contrast",
"color": "#1f2937",
"name": "Contrast"
},
{
"slug": "accent-1",
"color": "#2563eb",
"name": "Primary Blue"
},
{
"slug": "accent-2",
"color": "#7c3aed",
"name": "Secondary Purple"
}
]
},
"typography": {
"fontFamilies": [
{
"slug": "primary",
"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
"name": "Primary"
}
],
"fontSizes": [
{
"slug": "small",
"size": "0.875rem",
"name": "Small"
},
{
"slug": "medium",
"size": "1rem",
"name": "Medium"
},
{
"slug": "large",
"size": "1.25rem",
"name": "Large"
}
]
},
"spacing": {
"spacingSizes": [
{
"slug": "sm",
"size": "1rem",
"name": "Small"
},
{
"slug": "md",
"size": "2rem",
"name": "Medium"
},
{
"slug": "lg",
"size": "3rem",
"name": "Large"
}
],
"padding": true,
"margin": true,
"blockGap": true
}
},
"styles": {
"color": {
"background": "var(--wp--preset--color--base)",
"text": "var(--wp--preset--color--contrast)"
},
"typography": {
"fontFamily": "var(--wp--preset--font-family--primary)",
"fontSize": "var(--wp--preset--font-size--medium)",
"lineHeight": "1.6"
},
"elements": {
"link": {
"color": {
"text": "var(--wp--preset--color--accent-1)"
}
},
"h1": {
"typography": {
"fontSize": "var(--wp--preset--font-size--large)",
"fontWeight": "700"
},
"spacing": {
"margin": {
"bottom": "var(--wp--preset--spacing--md)"
}
}
},
"button": {
"color": {
"background": "var(--wp--preset--color--accent-1)",
"text": "var(--wp--preset--color--base)"
},
"spacing": {
"padding": {
"top": "var(--wp--preset--spacing--sm)",
"bottom": "var(--wp--preset--spacing--sm)",
"left": "var(--wp--preset--spacing--sm)",
"right": "var(--wp--preset--spacing--sm)"
}
}
}
},
"blocks": {
"core/paragraph": {
"spacing": {
"margin": {
"bottom": "var(--wp--preset--spacing--sm)"
}
}
},
"core/group": {
"spacing": {
"padding": "var(--wp--preset--spacing--md)",
"blockGap": "var(--wp--preset--spacing--sm)"
}
}
}
}
}
Quick Reference: Common Properties
Color Properties
"color": {
"text": "var(--wp--preset--color--contrast)",
"background": "var(--wp--preset--color--base)",
"gradient": "var(--wp--preset--gradient--primary-gradient)"
}
Typography Properties
"typography": {
"fontFamily": "var(--wp--preset--font-family--primary)",
"fontSize": "var(--wp--preset--font-size--medium)",
"fontWeight": "400",
"lineHeight": "1.6",
"textTransform": "uppercase",
"letterSpacing": "0.05em"
}
Spacing Properties
"spacing": {
"padding": "var(--wp--preset--spacing--md)",
"margin": "var(--wp--preset--spacing--md)",
"blockGap": "var(--wp--preset--spacing--sm)"
}
You can also use individual sides:
"spacing": {
"padding": {
"top": "var(--wp--preset--spacing--md)",
"right": "var(--wp--preset--spacing--sm)",
"bottom": "var(--wp--preset--spacing--md)",
"left": "var(--wp--preset--spacing--sm)"
}
}
Border Properties
"border": {
"color": "var(--wp--preset--color--accent-1)",
"width": "1px",
"style": "solid",
"radius": "4px"
}
Individual sides:
"border": {
"top": {
"color": "var(--wp--preset--color--accent-1)",
"width": "2px",
"style": "solid"
}
}
Tips for Beginners
- Start simple: Begin with just colors and typography, then expand as you get comfortable
- Use variables consistently: Always reference your settings variables rather than hard-coding values
- Test in the editor: Open the Site Editor (Appearance → Editor) to see your changes immediately
- Validate your JSON: Use a JSON validator to catch syntax errors
- Keep slugs simple: Use lowercase letters, numbers, and hyphens only
- Comment your work: While JSON doesn’t support comments, keep a separate notes file to document your design decisions
Common Mistakes to Avoid
- Forgetting the comma between items (JSON is strict about syntax)
- Using quotes around numbers (write
"size": "1rem"not"size": 1) - Misspelling variable names (WordPress won’t warn you)
- Trying to style elements that don’t exist (check the WordPress documentation for available elements)
- Not using the
var()function when referencing presets
Next Steps
Once you’re comfortable with the basics:
- Explore custom spacing scales
- Add custom gradients
- Create layout defaults for templates
- Use appearance tools to fine-tune what controls users see
- Add custom CSS when theme.json can’t do what you need
Resources:
- Official WordPress theme.json documentation
- Use your browser’s developer tools to inspect the CSS variables WordPress generates
- Join the WordPress community forums to ask questions
Happy theme building!
