Color management is one of the most powerful features of WordPress block themes. With theme.json, you can define custom color palettes that appear throughout the block editor, ensuring every page, post, and template uses your exact brand colors, without relying on custom CSS or page builders.
This guide covers everything from basic palette setup to advanced techniques like duotone filters, gradient presets, dark mode variations, and accessibility testing for color contrast.
How Color Palettes Work in theme.json
When you define a color palette in theme.json, WordPress does three things automatically:
- Generates CSS custom properties (variables) for each color, accessible anywhere in your theme
- Populates the color picker in the block editor with your custom palette
- Overrides WordPress default colors so editors only see your brand colors (unless you explicitly keep the defaults)
This means your content creators can only choose from approved brand colors when styling blocks, eliminating off-brand color usage across the entire site.
Basic Color Palette Setup
Open your theme’s theme.json file (create one if it doesn’t exist) and add your palette under settings.color.palette:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"settings": {
"color": {
"palette": [
{
"slug": "primary",
"color": "#1a56db",
"name": "Primary"
},
{
"slug": "primary-dark",
"color": "#1e3a5f",
"name": "Primary Dark"
},
{
"slug": "secondary",
"color": "#7c3aed",
"name": "Secondary"
},
{
"slug": "accent",
"color": "#f59e0b",
"name": "Accent"
},
{
"slug": "surface",
"color": "#f8fafc",
"name": "Surface"
},
{
"slug": "text",
"color": "#1e293b",
"name": "Text"
},
{
"slug": "text-light",
"color": "#64748b",
"name": "Text Light"
},
{
"slug": "white",
"color": "#ffffff",
"name": "White"
}
]
}
}
}
Each color needs three properties:
slug, Machine-readable identifier used in CSS variable names and class namescolor, The hex, RGB, or HSL color valuename, Human-readable label shown in the editor
Generated CSS variables
WordPress automatically creates CSS custom properties from your palette:
--wp--preset--color--primary: #1a56db;
--wp--preset--color--primary-dark: #1e3a5f;
--wp--preset--color--secondary: #7c3aed;
--wp--preset--color--accent: #f59e0b;
Use these variables anywhere in your theme CSS:
.wp-block-button__link {
background-color: var(--wp--preset--color--primary);
color: var(--wp--preset--color--white);
}
.wp-block-button__link:hover {
background-color: var(--wp--preset--color--primary-dark);
}
Controlling Default vs Custom Palettes
By default, WordPress shows both its default palette and your custom palette in the editor. To show only your brand colors:
{
"settings": {
"color": {
"defaultPalette": false,
"palette": [ ... ]
}
}
}
Setting defaultPalette to false removes WordPress core colors (black, cyan, purple, etc.) from the picker. This is strongly recommended for brand consistency.
You can also disable custom colors entirely, forcing editors to use only your palette:
{
"settings": {
"color": {
"custom": false,
"defaultPalette": false,
"palette": [ ... ]
}
}
}
Gradient Presets
Define custom gradients alongside your color palette:
{
"settings": {
"color": {
"gradients": [
{
"slug": "primary-to-secondary",
"gradient": "linear-gradient(135deg, #1a56db 0%, #7c3aed 100%)",
"name": "Primary to Secondary"
},
{
"slug": "warm-sunset",
"gradient": "linear-gradient(180deg, #f59e0b 0%, #ef4444 100%)",
"name": "Warm Sunset"
},
{
"slug": "subtle-surface",
"gradient": "linear-gradient(180deg, #ffffff 0%, #f8fafc 100%)",
"name": "Subtle Surface"
}
]
}
}
}
Like palette colors, set defaultGradients: false to remove WordPress defaults and customGradient: false to prevent custom gradient creation.
Duotone Presets
Duotone filters apply a two-tone color effect to images and cover blocks. Define them in your palette:
{
"settings": {
"color": {
"duotone": [
{
"slug": "brand-duotone",
"colors": ["#1e3a5f", "#f59e0b"],
"name": "Brand Duotone"
},
{
"slug": "mono-blue",
"colors": ["#1a56db", "#bfdbfe"],
"name": "Mono Blue"
}
],
"defaultDuotone": false
}
}
}
Duotone arrays always take exactly two colors: the shadow (dark) color and the highlight (light) color. WordPress maps image tones to these two colors for a branded image effect.
Style Variations for Dark Mode
Block themes support style variations, alternative theme.json configurations that users can switch between in the Site Editor. This is how you implement dark mode:
Create a file at styles/dark.json in your theme:
{
"$schema": "https://schemas.wp.org/trunk/theme.json",
"version": 3,
"title": "Dark Mode",
"settings": {
"color": {
"palette": [
{
"slug": "primary",
"color": "#60a5fa",
"name": "Primary"
},
{
"slug": "surface",
"color": "#0f172a",
"name": "Surface"
},
{
"slug": "text",
"color": "#e2e8f0",
"name": "Text"
},
{
"slug": "text-light",
"color": "#94a3b8",
"name": "Text Light"
}
]
}
},
"styles": {
"color": {
"background": "var(--wp--preset--color--surface)",
"text": "var(--wp--preset--color--text)"
}
}
}
Keep the same slugs as your main palette but change the color values. Since CSS variables reference slugs, all your theme styles automatically adapt to the new colors when the variation is activated.
Color Palette Best Practices
- Use semantic slug names (primary, secondary, surface, text) instead of color names (blue, purple). This makes dark mode and rebranding possible without changing template code.
- Include 6-10 colors maximum. Too many choices lead to inconsistent designs. Include: primary, secondary, accent, surface/background, text (dark and light), and white/black.
- Test contrast ratios. Every text color + background color combination must meet WCAG 2.1 AA standards (4.5:1 for normal text, 3:1 for large text). Use WebAIM’s Contrast Checker.
- Disable default palettes in production themes. This prevents editors from using off-brand colors.
- Document your palette for content editors. Create a patterns page or documentation that shows approved color combinations.
Testing Color Accessibility
After defining your palette, test every expected text/background combination:
- Text on Surface: #1e293b on #f8fafc, ratio 14.3:1 (AAA pass)
- White on Primary: #ffffff on #1a56db, ratio 5.9:1 (AA pass)
- White on Secondary: #ffffff on #7c3aed, ratio 5.4:1 (AA pass)
- Text Light on Surface: #64748b on #f8fafc, ratio 4.7:1 (AA pass)
If any combination fails, adjust the color values until all combinations pass AA or higher. This is especially important for button text, link text, and body copy.
Frequently Asked Questions
Can I use RGB or HSL instead of hex in theme.json?
Yes. The color property accepts any valid CSS color value: hex (#1a56db), RGB (rgb(26, 86, 219)), HSL (hsl(221, 83%, 48%)), or named colors.
How do I add colors for specific blocks only?
Use block-level settings in theme.json. Under settings.blocks, you can define per-block palettes. For example, give the Button block a restricted set of colors while allowing more options for Headings.
Will my custom palette work with child themes?
Yes. Child themes can extend or override the parent’s palette by defining their own theme.json. WordPress merges theme.json files with child theme values taking priority.
