WordPress theme.json: Define Color Palette, Font Sizes, and Spacing Scale for Block Themes

WordPress 5.8 made theme.json the canonical way for block themes to declare their design system: the colour palette, font size scale, spacing scale, typography settings, and layout constraints. A single JSON file in the theme root replaces dozens of scattered add_theme_support() calls and eliminates the need for theme-specific CSS variables for these design tokens. Every value declared in theme.json is automatically exposed as a CSS custom property (--wp--preset--color--primary, --wp--preset--font-size--large, etc.) that can be used anywhere in the theme’s CSS. The block editor reads theme.json to populate the colour pickers, font size sliders, and spacing controls in the sidebar — creating a direct connection between the design system and the editor UI without any PHP. Classic themes (non-block themes) can also include a theme.json to get editor colour palettes and font size support without full FSE adoption.

Problem: A new block theme needs a consistent colour palette, a type scale with five sizes, and a spacing scale — all surfaced in the editor sidebar for content editors, and all available as CSS custom properties for use in block stylesheets, without writing separate PHP registration calls for each.

Solution: Define the full design system in theme.json under settings.color.palette, settings.typography.fontSizes, and settings.spacing.spacingSizes. WordPress generates the CSS custom properties and editor controls automatically.

// theme.json (place in theme root directory)
{
    "$schema": "https://schemas.wp.org/trunk/theme.json",
    "version": 2,
    "settings": {
        "color": {
            "palette": [
                { "slug": "primary",   "name": "Primary",   "color": "#1e40af" },
                { "slug": "secondary", "name": "Secondary", "color": "#0d7377" },
                { "slug": "accent",    "name": "Accent",    "color": "#f5c842" },
                { "slug": "light",     "name": "Light",     "color": "#f8fafc" },
                { "slug": "dark",      "name": "Dark",      "color": "#0f172a" }
            ],
            "gradients": [
                {
                    "slug":     "primary-to-secondary",
                    "name":     "Primary to Secondary",
                    "gradient": "linear-gradient(135deg, #1e40af 0%, #0d7377 100%)"
                }
            ],
            "custom": false,         // disallow custom color picker
            "customGradient": false  // disallow custom gradients
        },
        "typography": {
            "fontSizes": [
                { "slug": "small",  "name": "Small",      "size": "0.875rem" },
                { "slug": "normal", "name": "Normal",     "size": "1rem" },
                { "slug": "medium", "name": "Medium",     "size": "1.25rem" },
                { "slug": "large",  "name": "Large",      "size": "1.75rem" },
                { "slug": "x-large","name": "Extra Large","size": "2.5rem" }
            ],
            "fontFamilies": [
                {
                    "slug":       "sans",
                    "name":       "Sans Serif",
                    "fontFamily": "Inter, system-ui, sans-serif"
                },
                {
                    "slug":       "mono",
                    "name":       "Monospace",
                    "fontFamily": "'JetBrains Mono', monospace"
                }
            ]
        },
        "spacing": {
            "spacingSizes": [
                { "slug": "20",  "name": "XS",  "size": "0.5rem"  },
                { "slug": "30",  "name": "S",   "size": "1rem"    },
                { "slug": "40",  "name": "M",   "size": "1.5rem"  },
                { "slug": "50",  "name": "L",   "size": "2rem"    },
                { "slug": "60",  "name": "XL",  "size": "3rem"    },
                { "slug": "70",  "name": "2XL", "size": "4.5rem"  }
            ],
            "padding": true,
            "margin":  true
        },
        "layout": {
            "contentSize": "720px",   // max width for paragraph, heading blocks
            "wideSize":    "1280px"   // max width for wide-aligned blocks
        }
    },
    "styles": {
        "color": {
            "background": "var(--wp--preset--color--light)",
            "text":       "var(--wp--preset--color--dark)"
        },
        "typography": {
            "fontFamily": "var(--wp--preset--font-family--sans)",
            "fontSize":   "var(--wp--preset--font-size--normal)",
            "lineHeight": "1.6"
        }
    }
}

NOTE: WordPress generates CSS custom properties from the palette automatically — the primary colour slug becomes --wp--preset--color--primary and the class .has-primary-color / .has-primary-background-color. These classes and properties are used by blocks when a user selects a preset colour in the editor. Setting "custom": false hides the custom colour picker in the editor — users can only choose from the defined palette, which enforces brand colour usage. The styles section in theme.json applies CSS to the document (body) and to specific block types — for example, "styles.blocks.core/heading" sets defaults for all heading blocks. Classic themes (not full-site-editing) can use a minimal theme.json with just the settings section to get editor colour and font-size controls without adopting the rest of FSE.