Skip to content

Repository files navigation

Claude Design → WordPress

A repeatable pipeline for turning a Claude design (HTML/Tailwind, or a Claude Design React/JSX bundle) into a pixel-perfect, production WordPress / WooCommerce theme — fast, consistent across many client sites, and built to pass the official Theme Check / theme-review bar.

The target architecture is Keel, a lean hybrid-classic base theme: the PHP template hierarchy + a named hook system render the front end, theme.json holds every design token, and interactive sections are server-rendered React blocks (Interactivity API). There is no Tailwind in the shipped theme and no Full-Site-Editing .html templates.

The idea: stop rebuilding designs by hand in wp-admin. Instead, treat WordPress-native output as a target format. The Keel base theme is cloned per client (tbt → the client slug); the design's real computed styles are folded into theme.json tokens + scoped CSS, and each section is ported to a server-rendered block (editable attributes) or a pattern, composed into one full-page pattern per page (home, services, about, …). The client creates a Page and inserts the pattern; content stays editable, layout stays intact. Fidelity is then proven by automated pass/fail gates (static lint + a seeded headless WordPress render with computed-style diffing and DOM assertions), not by eyeballing.

Built for an agency that designs with Claude but has clients who specifically want WordPress. Editability is agent-native — server-rendered blocks + patterns, no page builders (Elementor/Bricks), and no wp:html for content.

Disclaimer. This is an independent, community project. It is not affiliated with, endorsed by, or sponsored by Anthropic. "Claude" and "Anthropic" are trademarks of Anthropic, PBC, used here only to describe interoperability. "WordPress" and "WooCommerce" are trademarks of their respective owners.

License. This repository is licensed GPL-2.0-or-later (see LICENSE), matching the themes it generates. No third-party fonts/images/JS are bundled.


Table of contents


How it works

Three moving parts: a reusable base theme, a scaffolder that clones it per client, and a packager that produces an upload-ready zip.

flowchart LR
    A[Claude design<br/>HTML/Tailwind or JSX] --> C
    B[base-theme/<br/>Keel starter] -->|new-site.sh<br/>clone + rewrite + build| C[sites/&lt;client&gt;/]
    C -->|Claude converts:<br/>theme.json tokens + blocks + patterns| D[Themed site]
    D -->|npm run build<br/>wp-scripts| E[build/blocks/*]
    E -->|lint + seed + assert + style-diff + validate-blocks| Q{QA gates green?}
    Q -->|no — fix| C
    Q -->|yes| F[dist/&lt;client&gt;.zip]
    F -->|wp-admin upload| G[Live WordPress site]
Loading

The end-to-end run is wrapped by the /generate-theme skill (asks Theme Name → Theme URL → WooCommerce, then scaffolds, converts, gates, and packages). By hand:

  1. Clone the base theme: scripts/new-site.sh acme "Acme Co" — copies base-theme/ to sites/acme/, rewrites every identifier from the base slug tbt to acme (text domain, tbt_/Tbt_/TBT_ prefixes, block namespace tbt/, CSS --tbt-), and runs npm install && npm run build. Author defaults to Nextbit Technologies / nextbit.io.
  2. Convert the design (see Converting a design): fold the design's computed styles into theme.json tokens + scoped CSS, port each section to a Keel block or pattern, and compose a full-page pattern per page.
  3. Build the blocks: npm run build (wp-scripts) → build/blocks/*.
  4. Gate (see Quality gates): lint → seed a headless WordPress → assert DOM invariants → style-diff (computed styles) → validate-blocks → text-parity. All green.
  5. Package: scripts/package-site.sh acme → a clean dist/acme.zip (re-runs the lint).
  6. Upload via wp-admin (Appearance → Themes → Add New → Upload Theme).

Why Keel (hybrid-classic)

The theme is a hybrid-classic theme, not a block theme and not headless:

  • PHP template hierarchy + hooks render the front end (header.php, page.php, …, with a named tbt_hook() system for chrome). No .html FSE templates.
  • theme.json v3 is the single token source — colours, fluid type, spacing, widths. Every template and block consumes --wp--preset--*; there is no Tailwind and no tailwind.config.js to keep in sync.
  • Server-rendered React blocks (block.json apiVersion 3 + edit.js + render.php) carry the editable, repeating, or dynamic sections; front-end interactivity uses the Interactivity API. Because save() is null, there is no block-validity ("Attempt recovery") class of bug.
  • Scoped component CSS (assets/css/*.css + per-block style.scss) handles the last few percent — always referencing tokens, never raw values a token covers.

Fidelity comes from the design's real computed styles, extracted headlessly and re-expressed as tokens + scoped CSS (see Converting a design) — not from utility classes.


Repository layout

ClaudeToWP/
├── README.md                  This file.
├── CLAUDE.md                  The Keel pipeline playbook (clone → convert → gate → package).
├── base-theme/                The Keel base theme — cloned per client, never edited per-client.
│   ├── style.css              Theme header (name, author, license, tags, text domain).
│   ├── theme.json             The single token source: palette, fluid type, spacing, widths.
│   ├── functions.php          Thin loader: constants + require inc/ modules.
│   ├── header.php footer.php index.php front-page.php page.php single.php
│   │   archive.php search.php 404.php searchform.php comments.php   PHP template hierarchy.
│   ├── inc/                   setup, enqueue, hooks, template-tags, blocks, woocommerce.
│   ├── template-parts/        site-branding, navigation, content, content-none, entry-meta.
│   ├── blocks/                Server-rendered React block SOURCE (product-showcase); built to /build.
│   ├── patterns/              Section + full-page patterns composing the blocks.
│   ├── woocommerce/           content-product.php + Woo overrides (only where needed).
│   ├── assets/                css/ (base, theme, woocommerce), js/, fonts/ (local), images/.
│   ├── build/                 wp-scripts output (generated; shipped in the zip).
│   ├── bin/                   The theme's own verify/package/phpcs harness (wp-env).
│   ├── examples/tbt-child/    A child-theme conversion demo.
│   ├── SPEC.md RENAME.md CLAUDE.md DECISIONS.md   Theme-internals authority (read these).
│   ├── package.json           wp-scripts build (npm run build / start).
│   ├── composer.json phpcs.xml.dist   PHPCS: Extra + Docs + WPThemeReview + PHPCompatibility.
│   ├── readme.txt  screenshot.png  languages/tbt.pot
├── scripts/
│   ├── new-site.sh            Clone base-theme → sites/<slug>, rewrite tbt→slug, build blocks.
│   ├── package-site.sh        Build (wp-scripts) + lint-gate + runtime-only zip for upload.
│   └── visual-check/          Headless-WordPress QA gates (Puppeteer):
│       ├── lint-theme.mjs     Static: specificity risk, wp:html, hotlinks, leftover Tailwind, raw-value/token.
│       ├── seed.mjs           Seed Title/Tagline + Pages + nav menu + front page + permalinks.
│       ├── assert.mjs         Hard DOM gate: overflow, image load, responsive visibility, mobile nav.
│       ├── style-diff.mjs     Computed-style diff (design vs theme); `--extract` emits the porting manifest.
│       ├── validate-blocks.mjs  block.json/render.php validity + php -l + zero render-time PHP errors.
│       ├── text-parity.mjs    Every design phrase present in the theme.
│       ├── ledger.mjs         Runs every gate in order → prints RESULT: DONE.
│       ├── run.mjs            Design-vs-theme side-by-side render → dist report.
│       └── example.json       Per-site config template: designDir, brand, routes, styleProbes, assert.
├── dist/                      Packaged <slug>.zip + visual reports (git-ignored).
└── sites/                     Per-client themes (git-ignored; each ships independently).

Prerequisites

Tool Why Required?
Node.js + npm Build the blocks with @wordpress/scripts (build/blocks/*) Yes
PHP 8.1+ Theme runtime; php -l in the gates Yes (runtime)
Composer Run PHPCS (Extra + Docs + WPThemeReview + PHPCompatibility) Optional (recommended)
@wp-now/wp-now (via npx) Boot a real SQLite WordPress for the QA gates Yes (for gates)
Google Chrome Headless rendering for seed/assert/style-diff (Puppeteer) Yes (for gates)
@wordpress/env (Docker) WooCommerce + WP_DEBUG QA (npm run env in the theme) For stores / deep QA
WooCommerce Shop templates Only for stores

Target platform: WordPress 7.0 (floor 6.6), PHP 8.1+. Hybrid-classic theme — not FSE, not headless.


Quick start

# 1. Scaffold a client theme (clones base-theme, rewrites tbt→slug, builds blocks)
scripts/new-site.sh acme "Acme Co"

# 2. Extract the design's computed styles into a porting manifest (needs a <slug>.json)
node scripts/visual-check/style-diff.mjs --slug acme --extract   # -> dist/acme-style-manifest.json

# 3. Convert the design: fold the manifest into theme.json tokens + scoped CSS,
#    port each section to a Keel block/pattern (see CLAUDE.md), then rebuild:
(cd sites/acme && npm run build)

# 4. Coding-standards check
(cd sites/acme && composer install && composer lint)

# 5. Run the full DONE ledger — must print RESULT: DONE (see "Quality gates")
(cd sites/acme && npx -y @wp-now/wp-now start --port=8891 --reset --php=8.2 &)
node scripts/visual-check/ledger.mjs --slug acme --theme-url http://localhost:8891
node scripts/visual-check/run.mjs    --slug acme --theme-url http://localhost:8891   # then read the side-by-sides

# 6. Replace screenshot.png with the client homepage (≤1200×900, 4:3)

# 7. Package for upload (re-runs the lint gate)
scripts/package-site.sh acme        # -> dist/acme.zip

# 8. Upload dist/acme.zip in wp-admin > Appearance > Themes > Add New > Upload Theme

The base theme

base-theme/ is the Keel base theme and the single source of truth. It is never edited for a specific client — instead it is cloned. It ships:

  • The full PHP template hierarchy + template parts (branding, navigation, content), and a named hook system (tbt_hook()) for chrome.
  • theme.json v3 as the only token source, with a local variable font.
  • A reference server-rendered block (product-showcase) and reference patterns (hero-cta, product-grid) demonstrating the per-section recipe.
  • Its own verify/package tooling (bin/), a WooCommerce module, and a child-theme demo (examples/tbt-child/).
  • Everything needed to pass theme review (see below).

Its internals are documented in base-theme/CLAUDE.md, base-theme/SPEC.md, and base-theme/RENAME.md — read those when working on the theme itself. Because new-site.sh clones it and rewrites tbt→slug, every client site starts identical and consistent.


Converting a design

Fidelity comes from the design's real computed styles, not from utilities. Full detail in CLAUDE.md; the shape:

  1. Extract: node scripts/visual-check/style-diff.mjs --slug <slug> --extract renders the design headlessly and writes dist/<slug>-style-manifest.json — computed styles + box metrics for each annotated element.
  2. Tokenise → theme.json: fold the manifest's repeated values into presets (colours, fluid type, spacing, widths); snap near-duplicates to one token, else mint a named token. No raw value survives into component CSS.
  3. Emit scoped CSS in assets/css/theme.css (or a block's style.scss), referencing --wp--preset--*, scoped to the section/block class; prefix custom classes (<slug>-*) and qualify any display rule on a core block with the block class so core's later-loaded sheet can't override it.
  4. Port each section to a Keel server-rendered block (editable/repeating/dynamic) or a pattern (static); compose a full-page pattern per page.
  5. Verify: style-diff.mjs in diff mode confirms the render matches the design computed-for-computed — the extractor and the gate share one code path.

Client-editable content

Each page ships as a full-page pattern. The client creates a Page (Pages → Add New), inserts the pattern, and the whole design becomes editable blocks in that Page — no plugin, no wp:html. page.php renders the_content() (title-less — designs carry their own hero); there is no baked front-page.html (the client sets a static front page the normal way). Forms / live booking / real maps are left as clean native placeholders for the relevant plugin block.

Editable fields are block attributes on server-rendered blocks (exposed via RichText / media controls in edit.js; render.php outputs the escaped, tokenised HTML). This is the agent-native surface — an agent edits attributes or theme.json, never raw markup. For a section reused across pages with centrally-controlled design, register it as a synced pattern and lock structure with "lock": { "move": true, "remove": true }.


Header, footer & menus

Header and footer are PHP (header.php, footer.php) with the shared parts in template-parts/, rendered on every request via the hook system — edit once, applies site-wide. Navigation is a classic menu (wp_nav_menu) output by template-parts/navigation.php; the client edits links under Appearance → Menus and assigns them to the primary / footer locations. On mobile the menu collapses to a toggle (.menu-toggle.primary-menu-container.is-open) driven by the Interactivity API — the nav is never display:none'd.


WooCommerce

WooCommerce support is declared in the theme's Woo module (loaded only when Woo is active). Styling is via woocommerce_* hooks + tokenised CSS enqueued only on shop pages; PHP template overrides live in woocommerce/ and are added only where structure must change (content-product.php, and single-product.php / archive-product.php when needed). The mini-cart uses Woo's own widget/hooks — there is no .html part.


Theme-review compliance

Generated themes are built to pass the Theme Check plugin and PHPCS (WordPress-Extra

  • Docs + WPThemeReview + PHPCompatibility):
  • Text domain == theme slug — rewritten per client by the scaffolder across all PHP/JSON and the PHPCS config.
  • All globals prefixed with a unique, slug-derived prefix (incl. pattern-file PHP vars).
  • Output escaped late (esc_html, esc_attr, esc_url, wp_kses_post); input sanitized; strings internationalized with translators: comments.
  • Styles/scripts enqueued (versioned, conditional; no hardcoded <link>/<script>); parent assets via get_template_* (never get_stylesheet_*).
  • Required files ship: style.css (full header), index.php, theme.json, readme.txt, screenshot.png (≤1200×900, 4:3, the real homepage).
  • GPL-2.0-or-later. Images bundled in assets/images/ and referenced via get_theme_file_uri() (never hot-linked); local GPL/OFL fonts declared in readme.txt.

Self-check: composer install && composer lint and Theme Check on a local WP (the theme's npm run env). The packager ships the runtime tree only — block source, node_modules, vendor, Composer/PHPCS/package/wp-env config, bin/, examples/, and the base theme's docs are all excluded — and warns if readme.txt/screenshot.png are missing.


Scripts reference

scripts/new-site.sh <slug> ["Display Name"]

Clones base-theme/ to sites/<slug>/ and rebrands it:

  • Sets the theme name, description, and text domain in style.css.
  • Rewrites the base token to the client slug everywhere: text domain, version constant, enqueue/style handles, @package, pattern namespaces (agency-base/hero<slug>/hero), and the PHPCS config.
  • Rewrites readme.txt theme name; author/contributor default to Nextbit Technologies / nextbit.io (set in the base theme — override per-site only if asked).
  • Slug must be lowercase, start with a letter, and contain only letters/numbers/hyphens.

scripts/package-site.sh <slug> [--skip-build]

Builds the blocks with wp-scripts (unless --skip-build), runs the static lint as a blocking gate (SKIP_LINT=1 bypasses), and produces dist/<slug>.zip containing the runtime tree only, wrapped in a single top-level <slug>/ folder (what WordPress expects). Ships the PHP hierarchy, inc/, template-parts/, patterns/, woocommerce/, assets/, and the compiled build/; excludes block source, node_modules/, vendor/, composer.*, phpcs.xml.dist, package*.json, .wp-env.json, bin/, examples/, and the base theme's docs. Warns if review assets are missing.

scripts/visual-check/*.mjs

The QA gates — see Quality gates. lint-theme.mjs and style-diff.mjs --extract are browser-free; seed.mjs, assert.mjs, style-diff.mjs (diff mode), validate-blocks.mjs, text-parity.mjs, and run.mjs drive a headless Chrome against a running WordPress and are configured by scripts/visual-check/<slug>.json (see example.json). ledger.mjs runs them all in order and prints RESULT: DONE.


Quality gates

The hard part isn't building the design — it's proving it's right. php -l and PHPCS pass clean on themes that still have a mobile bar leaking onto desktop, a 0-width hero, or 27px of horizontal scroll on phones. So fidelity is enforced by pass/fail gates that must be green before delivery. They live in scripts/visual-check/ and read scripts/visual-check/<slug>.json (see example.json).

# The whole ledger at once (runs every gate in order → RESULT: DONE):
cd sites/<slug> && (npx -y @wp-now/wp-now start --port=8891 --reset --php=8.2 &) ; cd -
node scripts/visual-check/ledger.mjs --slug <slug> --theme-url http://localhost:8891

# …or individual gates while iterating (same --slug --theme-url):
node scripts/visual-check/lint-theme.mjs      --slug <slug>          # browser-free
node scripts/visual-check/seed.mjs            --slug <slug> --theme-url http://localhost:8891
node scripts/visual-check/assert.mjs          --slug <slug> --theme-url http://localhost:8891
node scripts/visual-check/style-diff.mjs      --slug <slug> --theme-url http://localhost:8891
node scripts/visual-check/validate-blocks.mjs --slug <slug> --theme-url http://localhost:8891
node scripts/visual-check/text-parity.mjs     --slug <slug> --theme-url http://localhost:8891
node scripts/visual-check/run.mjs             --slug <slug> --theme-url http://localhost:8891
  • lint — statically flags: a custom class forcing a non-flex display while riding on a core flex block (the "mobile CTA on desktop" bug), raw wp:html, hot-linked images, leftover Tailwind utilities, and raw hex/px where a theme.json token exists. Re-run as a blocking step inside package-site.sh.
  • seed — sets the real Site Title/Tagline, creates the Pages + static front page + a nav menu assigned to the theme's locations + pretty permalinks, so the render reflects what ships (Keel's classic nav renders nothing without an assigned menu).
  • assert — hard DOM invariants per page × width (1440/768/390): no horizontal overflow, every image loaded, responsive visibility, key sections sized, no char-wrapped buttons, and the mobile menu opens with visible links. Non-zero exit blocks delivery.
  • style-diff — the decisive gate: diffs getComputedStyle of each design element vs its theme counterpart; 0 diffs or it fails. --extract reuses the same collector to emit the porting manifest.
  • validate-blocks — every built block has valid block.json + render.php, php -l is clean on every render/pattern, and fetching each seeded page emits zero PHP errors (SSR blocks have no save(), so there is no isValid check).
  • text-parity — every design phrase is present in the theme.
  • run — renders design vs. theme side-by-side per section → dist/<slug>-visual-report/. The mismatch % lies; read the side-by-side images at full resolution.

When a gate fails, decide whether it's a real bug or a gate false-positive and fix the right one — never loosen a gate to make a real bug pass.


Verification

The gates above are the bar. Round them out with a quick manual pass on a local WordPress before delivery:

  1. Editability — create a Page, insert the full-page pattern, edit a block attribute in the normal editor; the change saves and shows (server-rendered blocks, no wp:html).
  2. Editor parity — the wp-admin editor preview matches the front end (edit.js uses ServerSideRender + add_editor_style).
  3. WooCommerce (if enabled) — product, single + archive, add to cart, checkout (via the theme's npm run env wp-env with Woo active).
  4. Compliancecomposer lint passes; the Theme Check plugin reports no REQUIRED/WARNING.

FAQ

Why not headless WordPress + Next.js? Clients here specifically want to edit in wp-admin. Keel keeps the native WP editing experience — the design lands as server-rendered blocks + patterns editable in the block editor.

Why hybrid-classic instead of a block theme (FSE) or Tailwind? theme.json alone can't express every design, and FSE's save()-validated block markup is brittle to port into. Keel renders with PHP + hooks (full control), keeps theme.json as the only token source, and puts editable/dynamic sections in server-rendered blocks — whose save() is null, so the "Attempt recovery" class of bug can't happen. No Tailwind ships.

Can the base theme itself be submitted to WordPress.org? It's a starter/template; the per-client clone is what ships. Each clone is made self-consistent (slug == text domain, prefixed globals) so it can pass theme review.

Where are the per-client themes? Under sites/, which is git-ignored — each client theme is deployed/versioned independently.


Conversion conventions for contributors (and for Claude) live in CLAUDE.md.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages