A production-ready WordPress base theme built for modern development workflows. Ships with a Vite build pipeline, TypeScript, Tailwind CSS, GSAP, React islands (Shadcn UI), full Block Editor support, ACF page builder, WooCommerce templates, Schema.org structured data, and a one-command setup script.
Developed by Working Model Inc
- WordPress 6.0+
- PHP 8.0+
- Node.js 18+
npm run setupThis single command will:
- Prompt for a project name and rename the theme slug + text domain throughout
- Copy
.env.example→.env - Install all Node dependencies
- Run the first production build
Then copy the theme folder to wp-content/themes/ and activate it in WordPress.
| Command | Description |
|---|---|
npm run setup |
First-time project setup (rename, install, build) |
npm run dev |
Start Vite dev server with HMR |
npm run build |
Production build to dist/ |
npm run test |
Run Vitest unit tests |
npm run test:watch |
Run tests in watch mode |
npm run test:coverage |
Run tests with coverage report |
npm run lint |
Lint TypeScript |
npm run lint:fix |
Lint and auto-fix |
npm run format |
Format with Prettier |
npm run type-check |
TypeScript type check |
├── bin/
│ └── setup.sh # One-command setup script
├── patterns/ # Block Editor patterns (auto-registered)
│ ├── hero.php
│ ├── cards.php
│ ├── cta-banner.php
│ ├── testimonial.php
│ ├── text-image.php
│ └── section-divider.php
├── woocommerce/ # WooCommerce template overrides
│ ├── archive-product.php
│ ├── single-product.php
│ ├── cart/cart.php
│ └── checkout/form-checkout.php
├── src/
│ ├── acf-json/ # ACF field group JSON (version-controlled)
│ ├── assets/
│ │ ├── scripts/
│ │ │ ├── main.ts # Main JS entry point
│ │ │ ├── admin.ts # Admin JS entry point
│ │ │ ├── islands.ts # React island hydration entry
│ │ │ ├── animations/ # GSAP scroll animations
│ │ │ ├── components/ # Interactive components (forms, nav, dark mode)
│ │ │ └── gsap/ # GSAP setup and utilities
│ │ └── styles/
│ │ ├── main.scss # Main stylesheet entry
│ │ ├── config/ # Variables and mixins
│ │ ├── base/ # Reset, typography, utilities
│ │ ├── components/ # Component styles
│ │ ├── layout/ # Header, footer, content
│ │ └── wordpress/ # Block editor styles
│ ├── components/
│ │ └── ui/ # Pre-installed Shadcn components
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── badge.tsx
│ │ ├── input.tsx
│ │ └── dialog.tsx
│ ├── inc/
│ │ ├── theme-functions.php # Helper functions
│ │ ├── acf-setup.php # ACF options page + JSON sync
│ │ ├── block-setup.php # Block categories and styles
│ │ ├── cpt-setup.php # Custom post type helpers
│ │ ├── woocommerce-setup.php # WooCommerce support
│ │ ├── schema.php # Schema.org / JSON-LD output
│ │ ├── react-islands.php # React island PHP helper
│ │ ├── performance.php # Core Web Vitals defaults
│ │ ├── breadcrumbs.php # Native PHP breadcrumbs
│ │ └── customizer.php # WordPress Customizer options
│ ├── lib/
│ │ └── utils.ts # cn(), debounce(), formatDate()
│ ├── templates/
│ │ ├── acf-layouts/ # ACF flexible content layouts
│ │ └── content-*.php # Template parts
│ └── types/ # TypeScript definitions
├── tests/ # Vitest unit tests
├── theme.json # Block Editor design tokens
├── functions.php
├── style.css
├── page.php # Page template with ACF page builder loop
├── CHANGELOG.md # Version history
├── .wp-env.json # Docker-based local WordPress config
└── .env.example # Environment variable reference
theme.json exposes WM design tokens directly in the editor — color palette, font families, font sizes, and spacing scale. Six block patterns are auto-registered from the patterns/ directory:
- Hero — full-width heading + copy + dual CTA buttons
- Three Column Cards — bordered feature grid
- CTA Banner — dark background with yellow accent button
- Testimonial — centered quote with rule framing
- Text + Image — 50/50 two-column with CTA
- Section Divider — ornamental dot separator
Three block styles are registered: core/button outline, core/separator thick, core/quote plain.
page.php checks for a page_builder flexible content field and renders layouts from src/templates/acf-layouts/. Falls back to the_content() when ACF is inactive.
Field group JSON lives in src/acf-json/ and syncs automatically via the ACF sync screen.
Included layouts: hero, text_columns, image_text, cta_banner
src/inc/cpt-setup.php provides two helpers:
// Register a CPT
wm_register_cpt( 'project', 'Project', 'Projects' );
// Register a taxonomy
wm_register_taxonomy( 'project_type', 'Type', 'Types', ['project'] );Both generate a full label set and sensible defaults. Pass an optional $args array to override anything. Add registrations to wm_register_post_types() in the same file.
Add WooCommerce and the theme is ready. Template overrides in woocommerce/ cover the shop archive, single product, cart, and checkout — all with WC hooks intact. Styles are disabled by default so the theme's CSS takes full control.
The header includes a fully-accessible mobile drawer built with GSAP:
- Hamburger button (
nav-toggle) with animated → × transition via CSS - Drawer slides in from the right (
x: '100%' → '0%', 0.35spower3.out) - Focus trap cycles through all focusable elements within the open drawer
- Closes on Escape, overlay click, or any nav link click; returns focus to the trigger
aria-expanded/aria-hidden/aria-controlswired throughout
A dark mode toggle is included in the header. It reads prefers-color-scheme on first visit, then persists to localStorage. All CSS custom properties support both modes via the .dark class on <html>.
Toggle additional buttons anywhere by adding data-dark-toggle to any element.
Theme options live under Appearance → Customize → Theme Options:
| Section | Settings |
|---|---|
| Brand | Accent colour (live preview via postMessage) |
| Contact | Phone, email, address |
| Social Links | Instagram, LinkedIn, X/Twitter, Facebook, YouTube |
| Footer | Copyright text |
Helper functions for templates:
wm_get_contact( 'phone' ); // → escaped phone string
wm_get_social( 'instagram' ); // → escaped URL
wm_get_footer_text(); // → wp_kses_post'd copyright textRender a React component from PHP:
wm_render_react_component( 'MyComponent', [ 'label' => 'Hello' ] );Register the component in src/assets/scripts/islands.ts, call wm_enqueue_islands() in your template, and the island hydrates client-side with the provided props.
Pre-installed Shadcn components in src/components/ui/:
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Dialog, DialogTrigger, DialogContent, DialogTitle } from '@/components/ui/dialog';Structured data is injected automatically in wp_head:
| Context | Schema types |
|---|---|
| Every page | WebSite |
| Front page | Organization (with logo if set) |
| Singular / archive / search | BreadcrumbList |
| Single posts | Article (headline, dates, author, image) |
Call wm_breadcrumbs() anywhere in a template. Outputs an accessible <nav> with Schema.org itemListElement microdata. Handles posts (with category ancestors), pages (with parent pages), CPT archives, taxonomy/tag/author/date archives, search, and 404.
wm_breadcrumbs(); // defaults
wm_breadcrumbs(['sep' => '/', 'class' => 'my-breadcrumbs']); // customloading="lazy"anddecoding="async"added automatically to archive thumbnails andthe_content()images- Hero / LCP image gets
fetchpriority="high"instead wm_preconnect_originsfilter lets you add<link rel="preconnect">hints for third-party domains
ScrollTrigger is registered once in src/assets/scripts/gsap/init.ts. All animations respect prefers-reduced-motion.
import { animateOnScroll } from '@/assets/scripts/animations';
animateOnScroll('.my-element', { opacity: 1, y: 0, duration: 0.8 });The theme ships with a .wp-env.json for zero-dependency local WordPress via Docker:
npm run env:start # start WordPress at http://localhost:8888
npm run env:stop # stop containers
npm run env:clean # clean environment (resets DB)Requires Docker Desktop and @wordpress/env (npm i -g @wordpress/env or use npx). WordPress runs PHP 8.2 with WP_DEBUG enabled and the theme pre-mapped to wp-content/themes/wm-base-theme.
Copy .env.example to .env:
VITE_PORT=3000
VITE_HMR_HOST=localhost # Set to your local domain if using Valet/DDEV/LandoThe theme defaults to Stack Sans Text / Stack Sans Headline (WM's internal typeface) with system-ui as fallback. Replace with your own fonts in src/assets/styles/config/_variables.scss:
Option A — Self-hosted (add to src/assets/styles/base/_typography.scss):
@font-face {
font-family: 'My Font';
src: url('../fonts/my-font.woff2') format('woff2');
font-weight: 100 900;
font-display: swap;
}Option B — Google Fonts (enqueue in functions.php):
wp_enqueue_style('google-fonts', 'https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap', [], null);Then update $font-stack-text and $font-stack-headline in _variables.scss, and the fontFamilies array in theme.json.
Edit tokens in two places — they're kept in sync:
- SCSS variables:
src/assets/styles/config/_variables.scss - Block Editor:
theme.json(palette, spacing, font sizes)
Create a PHP file in patterns/ with the standard header:
<?php
/**
* Title: My Pattern
* Slug: wm-base-theme/my-pattern
* Categories: wm-blocks
*/
?>
<!-- wp:paragraph -->
<p>Pattern content here.</p>
<!-- /wp:paragraph -->WordPress 6.0+ registers it automatically.
- Add a layout to
src/acf-json/group_wm_page_builder.json(or use the ACF UI and sync) - Create
src/templates/acf-layouts/your_layout.php - Use
wm_get_field()to fetch field values inside the template
add_filter( 'wm_preconnect_origins', function ( array $origins ): array {
$origins[] = 'https://fonts.googleapis.com';
$origins[] = 'https://fonts.gstatic.com';
return $origins;
} );npm run testVitest runs 18 unit tests covering cn(), debounce(), form validation behaviour, dark mode toggle, and the wmTheme nonce guard. Tests use jsdom — no browser required.
Modern browsers (last 2 versions). IE 11 polyfills provided via @vitejs/plugin-legacy — remove from vite.config.ts if not needed.
MIT — see LICENSE for details.
Working Model Inc · workingmodel.co