wp-theme-json
v1.1.0
Published
JS-first theme.json compiler for WordPress block themes
Downloads
192
Maintainers
Readme
wp-theme-json
wp-theme-json lets you author WordPress block theme theme.json files in JavaScript and compile them into a real theme.json.
It is built for themes that want:
- structured settings and styles in JS instead of hand-written JSON
- Sass escape hatches for selectors that
theme.jsoncannot express cleanly - validation against the official WordPress schema and bundled core block support data
theme.json stays generated output. Your source of truth lives in theme-config/.
Install
Install the package in your theme root:
npm install --save-dev wp-theme-jsonRecommended scripts:
{
"scripts": {
"theme:init": "wp-theme-json init",
"build:theme": "wp-theme-json build",
"start:theme": "wp-theme-json start",
"validate:theme": "wp-theme-json validate",
"inspect:theme": "wp-theme-json inspect"
}
}Quick Start
For a working first setup, start with the example scaffold:
npx wp-theme-json init --exampleThen build:
npx wp-theme-json buildThat writes theme.json in the current project root.
Useful commands during development:
npx wp-theme-json start
npx wp-theme-json validate
npx wp-theme-json inspectMore guides:
What init Creates
wp-theme-json init creates a minimal starter:
theme-json.config.js
theme-config/
index.js
settings.js
styles.js
scss/
blocks/
elements/wp-theme-json init --example creates the same structure plus:
- starter color, spacing, and font-size presets
- one fluid font-size example
- a working block style example using
css('./scss/blocks/core-navigation.scss') - style variations in
theme-config/variations/
Use --example if you want something you can build immediately. Use plain init if you want a clean starting point.
If scaffolded files already exist:
npx wp-theme-json init --forceFor npm scripts, both of these work:
npm run theme:init -- --force
npm run theme:init --forcePractical Example
theme-json.config.js
import { defineConfig } from 'wp-theme-json';
export default defineConfig({
validation: {
level: 'error',
},
});theme-config/index.js
import { defineTheme } from 'wp-theme-json';
import settings from './settings.js';
import styles from './styles.js';
export default defineTheme({
settings,
styles,
});theme-config/settings.js
import { defineSettings } from 'wp-theme-json';
export default defineSettings({
color: {
palette: [
{ color: '#f6f1e8', name: 'Base', slug: 'base' },
{ color: '#1f1a17', name: 'Contrast', slug: 'contrast' },
{ color: '#a2512b', name: 'Accent', slug: 'accent' },
],
},
spacing: {
spacingSizes: [
{ name: 'Small', size: '0.875rem', slug: 'sm' },
{ name: 'Medium', size: '1.25rem', slug: 'md' },
],
},
typography: {
fontSizes: [
{ name: 'Body', size: '1rem', slug: 'body' },
{
fluid: {
min: '2.25rem',
max: '4rem',
},
name: 'Display',
size: '2.75rem',
slug: 'display',
},
],
},
});theme-config/styles.js
import { css, defineStyles } from 'wp-theme-json';
export default defineStyles({
color: {
background: 'var:preset|color|base',
text: 'var:preset|color|contrast',
},
elements: {
link: {
color: {
text: 'var:preset|color|accent',
},
},
},
blocks: {
'core/paragraph': {
color: {
text: 'var:preset|color|contrast',
},
typography: {
fontSize: 'var:preset|font-size|body',
},
},
'core/navigation': {
css: css('./scss/blocks/core-navigation.scss'),
},
},
});theme-config/scss/blocks/core-navigation.scss
& .wp-block-navigation-item__content:hover {
text-decoration: underline;
}Build it:
npx wp-theme-json buildThe generated theme.json automatically includes:
$schemaversion- sorted output for stable rebuilds
- minified CSS strings for every
css()field
Commands
wp-theme-json initwp-theme-json buildwp-theme-json startwp-theme-json validatewp-theme-json inspect
What they do:
buildcompiles and writestheme.jsonstartperforms an initial build and then rebuilds on file changesvalidatecompiles and validates without writing outputinspectprints the compiledtheme.jsonto stdout
Configuration
theme-json.config.js exports defineConfig({...}).
import { defineConfig } from 'wp-theme-json';
export default defineConfig({
entry: 'theme-config/index.js',
output: 'theme.json',
schema: 'https://schemas.wp.org/trunk/theme.json',
schemaVersion: 3,
sass: {
loadPaths: ['theme-config/scss'],
},
watch: {
ignore: ['dist/**'],
poll: false, // false | true | number
},
validation: {
level: 'error', // error | warn | off
},
});Notes:
entryis the main source moduleoutputis where compiledtheme.jsonis writtenschemaVersion: 3is the currently supported versionschemacontrols the emitted$schemavaluewatch.ignoreadds extra paths or globs for watch mode to skipwatch.pollhelps on Docker mounts, network drives, and other unreliable file-watch environmentscss()output is always minified- top-level
templatescompile tocustomTemplates - top-level
variationscompile tostyles.variations
Deprecated config keys such as source, theme, watch.ignored, and validation.strict still load, but they emit deprecation warnings.
Validation
Validation runs during build, start, and validate unless you set validation.level: 'off'.
Levels:
error: validation findings are errors and invalid builds do not writetheme.jsonwarn: validation findings are warnings and builds still writetheme.jsonoff: validation is skipped
Validation covers:
- unsupported or misplaced
theme.jsonkeys - wrong value types and invalid nesting
- malformed
var:preset|...references - invalid
css()placement outsidestyles.*.css - Sass compile failures with source file and line context
- unsupported core block feature usage based on bundled WordPress metadata
Import failures, config load failures, and Sass runtime errors still fail normally even if validation is set to warn or off.
When to Use css()
Keep styles in structured JS whenever theme.json already supports the property.
Use css() for:
- descendant selectors
- pseudo selectors that do not map cleanly to
theme.json - media queries
- advanced structural selectors
Top-level & is supported inside css() SCSS files. In this package, it means the current WordPress block or element selector.
Example:
& .wp-block-navigation-item__content:hover {
text-decoration: underline;
}Nested Sass parent selectors still work as usual inside that root block.
Advanced Path Targeting
Most users do not need a path flag. If the package is installed in the theme root, just run the commands there.
If the package is installed in an ancestor project instead of directly in the theme folder, use --theme-path or --config.
Examples:
wp-theme-json build --theme-path ./wp-content/themes/your-theme
wp-theme-json start --theme-path ./wp-content/themes/your-theme
wp-theme-json validate --config ./wp-content/themes/your-theme/theme-json.config.jsNotes:
--theme-pathtells the CLI which theme folder to treat as the project root--configpoints at a specifictheme-json.config.jsfile- legacy
--cwdis still supported as a backward-compatible alias - watch mode follows the resolved theme/config paths, not just the shell working directory
Public Helpers
Main authoring helpers:
defineConfig()defineTheme()defineSettings()defineStyles()css(filePath, options?)
Advanced exports:
defineTokens()compileProjectloadConfig
