references/creating-new-block-theme.md
# Creating a new block theme
Use this file when you need to create a new block theme or convert a theme to block theme structure.
## Two practical starting points
1. **Export from a WP environment**
- Use the official “Create Block Theme” plugin to generate/export a theme from the Site Editor.
- This tends to produce a structure aligned with current WordPress behavior.
2. **Create the minimal filesystem structure**
- Create a theme folder with:
- `style.css` (theme header)
- `theme.json` (global settings/styles)
- `templates/index.html` (minimum viable template)
- `parts/header.html` and `parts/footer.html` (recommended)
## References
- Create Block Theme plugin:
- https://wordpress.org/plugins/create-block-theme/
- Block theme structure:
- https://developer.wordpress.org/themes/block-themes/theme-structure/
- Required templates:
- https://developer.wordpress.org/themes/block-themes/templates-and-template-parts/
## theme.json version choice (compatibility)
`theme.json` has schema versions. Pick the highest version that matches your minimum supported WordPress version.
References:
- Theme Handbook introduction:
- https://developer.wordpress.org/themes/global-settings-and-styles/introduction-to-theme-json/
- Theme.json version 3 dev note:
- https://make.wordpress.org/core/2024/06/19/theme-json-version-3/
references/debugging.md
# Debugging block theme issues
## Styles not applying
Fast checks:
1. Confirm you edited the active theme (Site Editor → theme).
2. Check if user customizations exist (they override theme defaults).
3. Validate `theme.json` structure (typos can prevent styles from applying).
4. If a `var(--wp--preset--*)` / `var(--wp--custom--*)` reference is silently using its fallback, the slug is likely un-normalised — WordPress hyphenates digit/letter boundaries and camelCase transitions before emitting the CSS var. See `theme-json.md` § "Slug normalisation gotcha".
Remember the hierarchy:
- core defaults → theme.json → child theme → user customizations
## Templates/parts not showing
- Ensure files are in the correct folders (`templates/`, `parts/`).
- Template parts must not be nested in subdirectories.
## Style variations not updating
- If a user already selected the variation, the selection is stored in the DB.
- Test with a fresh site/user or reset customizations when appropriate.
references/patterns.md
# Patterns (filesystem patterns)
Use this file when adding patterns that should be available in the inserter.
## Filesystem patterns
- Put patterns in `patterns/*.php`.
- Patterns are registered automatically by WordPress core based on file headers.
Upstream reference:
- https://developer.wordpress.org/themes/patterns/
## Practical guardrails
- Keep pattern markup stable; changing block names inside patterns can break older content in subtle ways.
- If a pattern should not be inserted directly by users, mark it as non-inserter / internal-only (per upstream header conventions).
references/style-variations.md
# Style variations (`styles/*.json`)
Use this file when adding or debugging style variations.
Key points:
- Style variations are JSON files in `styles/`.
- Users can pick a style variation in the UI.
- Once selected, the choice is stored in the DB (so “changing the JSON file” may not update what a user already selected).
Upstream reference:
- https://developer.wordpress.org/themes/global-settings-and-styles/style-variations/
references/templates-and-parts.md
# Templates and template parts
Use this file when creating or editing HTML templates/parts.
## Key folders
- `templates/` for templates.
- `parts/` for template parts.
Template parts must not be nested in subdirectories.
Upstream references:
- Templates + parts overview: https://developer.wordpress.org/themes/block-themes/theme-structure/
- Template parts details: https://developer.wordpress.org/themes/block-themes/template-parts/
references/theme-json.md
# `theme.json` guidance
Use this file when changing global settings/styles or per-block styling.
## High-level structure
Common top-level keys:
- `version`
- `settings` (what the UI exposes / allows)
- `styles` (default appearance)
- `customTemplates` and `templateParts` (optional, to describe templates and parts)
Upstream references:
- Theme Handbook: https://developer.wordpress.org/themes/global-settings-and-styles/
- Block Editor Handbook (often more current): https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-json/
- Theme JSON living reference: https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/
- Theme JSON version 3 (dev note): https://make.wordpress.org/core/2024/06/19/theme-json-version-3/
## Practical guardrails
- Prefer presets when you want editor-visible controls (colors, font sizes, spacing).
- Prefer `styles` when you want consistent defaults without requiring user choice.
- Be careful with specificity: user global styles override theme defaults.
## WordPress 6.9 additions
**Form element styling:**
- Style text inputs and selects via `styles.elements.textInput` and `styles.elements.select`.
- `textInput` targets `<textarea>` and `<input>` elements whose `type` is one of: email, number, password, search, text, tel, url.
- There is no `input`, `checkbox`, `radio`, or `label` element key.
- Supports border, color, outline, shadow, and spacing properties.
- Note: Focus state styling is not yet available in 6.9.
**Border radius presets:**
- Define presets in `settings.border.radiusSizes` for visual selection in the border radius control.
- Users can still enter custom values.
```json
{
"settings": {
"border": {
"radiusSizes": [
{ "name": "Small", "slug": "small", "size": "4px" },
{ "name": "Medium", "slug": "medium", "size": "8px" },
{ "name": "Large", "slug": "large", "size": "16px" }
]
}
}
}
```
**Button pseudo-classes:**
- Style Button block hover and focus states directly in theme.json.
- No longer requires custom CSS for simple button state styling.
References:
- Border radius presets: https://make.wordpress.org/core/2025/11/12/theme-json-border-radius-presets-support-in-wordpress-6-9/
- Form element styling: https://developer.wordpress.org/news/2025/11/how-wordpress-6-9-gives-forms-a-theme-json-makeover/
## Slug normalisation gotcha
> **Slug normaliser trap (silent failure).** WordPress inserts hyphens inside preset/custom slugs before emitting CSS vars: slug `3xl` becomes `--wp--preset--font-size--3-xl`; slug `cardShadow` becomes `--wp--custom--card-shadow`. A handwritten reference to the *un-normalised* form (e.g. `var(--wp--preset--font-size--3xl)`) resolves to nothing and silently falls back to the second `var()` argument.
Before assembling the variable name, `WP_Theme_JSON` passes each preset/custom slug through `_wp_to_kebab_case()`, which splits it into word tokens — at digit/letter boundaries, camelCase transitions, and non-alphanumeric characters — lowercases them, and joins with `-`. Reference the emitted form, not the slug you typed.
Grep pattern to catch un-normalised references in CSS/SCSS/PHP/JS:
```
var\(\s*--wp--(?:preset|custom)--[a-z-]+--\d+[a-z]
```
This matches a digit immediately followed by a letter inside the variable name (`3xl`, `2xs`, `4x-large`) — every emitted form keeps the hyphen (`3-xl`, `2-xs`, `4-x-large`) and is correctly not flagged.
scripts/detect_block_themes.mjs
import fs from "node:fs";
import path from "node:path";
const DEFAULT_IGNORES = new Set([
".git",
"node_modules",
"vendor",
"dist",
"build",
"coverage",
".next",
".turbo",
]);
function statSafe(p) {
try {
return fs.statSync(p);
} catch {
return null;
}
}
function existsDir(p) {
const st = statSafe(p);
return Boolean(st && st.isDirectory());
}
function readJsonSafe(p) {
try {
return JSON.parse(fs.readFileSync(p, "utf8"));
} catch {
return null;
}
}
function findFilesRecursive(repoRoot, predicate, { maxFiles = 6000, maxDepth = 10 } = {}) {
const results = [];
const queue = [{ dir: repoRoot, depth: 0 }];
let visited = 0;
while (queue.length > 0) {
const { dir, depth } = queue.shift();
if (depth > maxDepth) continue;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
continue;
}
for (const ent of entries) {
const fullPath = path.join(dir, ent.name);
if (ent.isDirectory()) {
if (DEFAULT_IGNORES.has(ent.name)) continue;
queue.push({ dir: fullPath, depth: depth + 1 });
continue;
}
if (!ent.isFile()) continue;
visited += 1;
if (visited > maxFiles) return { results, truncated: true };
if (predicate(fullPath)) results.push(fullPath);
}
}
return { results, truncated: false };
}
function summarizeTheme(repoRoot, themeJsonPath) {
const json = readJsonSafe(themeJsonPath);
const rel = path.relative(repoRoot, themeJsonPath);
const rootDir = path.dirname(rel);
const templatesDir = path.join(repoRoot, rootDir, "templates");
const partsDir = path.join(repoRoot, rootDir, "parts");
const patternsDir = path.join(repoRoot, rootDir, "patterns");
const stylesDir = path.join(repoRoot, rootDir, "styles");
const hasTemplates = existsDir(templatesDir);
const hasParts = existsDir(partsDir);
return {
themeRoot: rootDir,
themeJson: rel,
version: typeof json?.version === "number" ? json.version : null,
hasTemplates,
hasParts,
hasPatterns: existsDir(patternsDir),
hasStyles: existsDir(stylesDir),
isBlockTheme: hasTemplates || hasParts,
};
}
function main() {
const repoRoot = process.cwd();
const { results: themeJsonFiles, truncated } = findFilesRecursive(repoRoot, (p) => path.basename(p) === "theme.json", {
maxFiles: 8000,
maxDepth: 12,
});
const themes = themeJsonFiles.map((p) => summarizeTheme(repoRoot, p));
const report = {
tool: { name: "detect_block_themes", version: "0.1.0" },
repoRoot,
truncated,
count: themes.length,
themes,
};
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
}
main();
SKILL.md
---
name: wp-block-themes
description: "Use when developing WordPress block themes: theme.json (global settings/styles), templates and template parts, patterns, style variations, and Site Editor troubleshooting (style hierarchy, overrides, caching)."
compatibility: "Targets WordPress 7.0+ (PHP 7.4.0+). Filesystem-based agent with bash + node. Some workflows require WP-CLI."
---
# WP Block Themes
## When to use
Use this skill for block theme work such as:
- editing `theme.json` (presets, settings, styles, per-block styles)
- adding or changing templates (`templates/*.html`) and template parts (`parts/*.html`)
- adding patterns (`patterns/*.php`) and controlling what appears in the inserter
- adding style variations (`styles/*.json`)
- debugging “styles not applying” / “editor doesn’t reflect theme.json”
## Inputs required
- Repo root and which theme is targeted (theme directory if multiple exist).
- Target WordPress version range (theme.json version and features vary by core version).
- Where the issue manifests: Site Editor, post editor, frontend, or all.
## Procedure
### 0) Triage and locate block theme roots
1. Run triage:
- `node skills/wp-project-triage/scripts/detect_wp_project.mjs`
2. Detect theme roots + key folders:
- `node skills/wp-block-themes/scripts/detect_block_themes.mjs`
If multiple themes exist, pick one and scope all changes to that theme root.
### 1) Create a new block theme (if needed)
If you are creating a new block theme from scratch (or converting a classic theme):
- Prefer starting from a known-good scaffold (or exporting from a WP environment) rather than guessing file layout.
- Be explicit about the minimum supported WordPress version because `theme.json` schema versions differ.
Read:
- `references/creating-new-block-theme.md`
After creating the theme root, re-run `detect_block_themes` and continue below.
### 2) Confirm theme type and override expectations
- Block theme indicators:
- `theme.json` present
- `templates/` and/or `parts/` present
- Remember the style hierarchy:
- core defaults → theme.json → child theme → user customizations
- user customizations can make theme.json edits appear “ignored”
Read:
- `references/debugging.md` (style hierarchy + fastest checks)
### 3) Make `theme.json` changes safely
Decide whether you are changing:
- **settings** (what the UI allows): presets, typography scale, colors, layout, spacing
- **styles** (how it looks by default): CSS-like rules for elements/blocks
Read:
- `references/theme-json.md`
### 4) Templates and template parts
- Templates live under `templates/` and are HTML.
- Template parts live under `parts/` and must not be nested in subdirectories.
Read:
- `references/templates-and-parts.md`
### 5) Patterns
Prefer filesystem patterns under `patterns/` when you want theme-owned patterns.
Read:
- `references/patterns.md`
### 6) Style variations
Style variations are JSON files under `styles/`. Note: once a user picks a style variation, that selection is stored in the DB, so changing the file may not “update what the user sees” automatically.
Read:
- `references/style-variations.md`
## Verification
- Site Editor reflects changes where expected (Styles UI, templates, patterns).
- Frontend renders with expected styles.
- If styles aren’t changing, confirm whether user customizations override theme defaults.
- Run the repo’s build/lint scripts if assets are involved (fonts, custom JS/CSS build).
## Failure modes / debugging
Start with:
- `references/debugging.md`
Common issues:
- wrong theme root (editing an inactive theme)
- user customizations override your defaults
- invalid `theme.json` shape/typos prevent application
- templates/parts in wrong folders (or nested parts)
## Escalation
If upstream behavior is unclear, consult canonical docs:
- Theme Handbook and Block Editor Handbook for `theme.json`, templates, patterns, and style variations.