An opinionated WordPress starter for building modern, testable sites on the Roots stack.
- π§± Bedrock foundation β 12-factor WordPress with Composer-managed core, clean config, and a
public/web root. - πΏ Acorn + Sage theme β Laravel-style service providers, Blade templates, Vite, Tailwind v4, and Alpine.js.
- π WooCommerce-ready β seeded products, German store settings, Blade template overrides, and an end-to-end checkout test.
- π§ͺ Pest 4 included β Unit, Feature, and Browser suites with a reusable test database baseline and FrankenPHP browser driver.
Inspired by Roots Radicle; the original Roots attribution is preserved in LICENSE.md.
A working theme out of the box, with seeded content so every feature is visible after composer setup.
Built from the standard wp-admin β Appearance β Menus editor β no page builder, no extra plugin. Each menu item gains a Mega menu type fieldset (Link / Image / HTML); a top-level item with at least one Image or HTML child renders as a multi-column mega panel, otherwise as a plain dropdown.
On screens narrower than lg, the nav collapses into a hamburger drawer with the panels rendering inline β same Blade partials, no JS branching.
Single posts, the blog index, search results, and the not-found page all ship with consistent Tailwind styling. The comment form is restyled to match the rest of the theme β <button>-based submit, theme-consistent inputs, required-field markers β via a custom comment_form() args array.
![]() |
![]() |
![]() |
![]() |
A complete WooCommerce vertical wired into the theme: 8 seeded products in 2 categories, German store configuration (EUR / 19 % VAT inclusive / flat-rate shipping), and BACS payment labelled Rechnung (Vorkasse). The shop archive and single product pages render through Blade templates resolved via the wc_get_template_part and wc_get_template filters, with Tailwind styling on the cards, sort toolbar, and category chips. A shopping-bag icon in the header shows a live cart count that updates without a page reload via WooCommerce's add_to_cart_fragments AJAX hook.
![]() |
![]() |
A Pest browser test (tests/Browser/ShopPurchaseTest.php) drives a guest through the full flow β shop β product β cart β block checkout β BACS β thank-you β and asserts on the seeded IBAN on the order-received page.
- Bedrock layout β WordPress core vendored at
public/wp/, content atpublic/content/, config via.env. Nowp-config.phpto edit. - Acorn (Laravel-on-WordPress) β Blade templates, Eloquent models that map onto
wp_posts/wp_postmeta, Acorn mail, service providers inapp/Providers/. - Sage (theme conventions) β Vite + Tailwind v4 + Alpine.js; no
tailwind.config.js, CSS-first@themeconfiguration. - Custom Gutenberg blocks β PHP class + Blade render template + JSX editor file, scaffolded with
wp acorn make:block. - REST API scaffold β controllers in
app/Http/Controllers/Api/. Neveradmin-ajax.php. - WooCommerce integration β
App\Providers\WooCommerceServiceProviderresolves WC templates to Blade, wraps content in the theme layout, and feeds AJAX cart fragments. Seeders bootstrap a German storefront ready to purchase from. - Translations β
__()everywhere, full.pot/.po/.mo/.jsonpipeline viacomposer translate. - Pest 4 tests β three suites (Unit / Feature / Browser) sharing a dedicated
starter_testDB with a generated baseline dump. Browser tests spawn a real FrankenPHP subprocess (multithreaded, so WC Blocks Store API checkout works end-to-end). - Automated screenshots β
composer screenshotsregenerates the images in this README from the running site at desktop and mobile viewports. Seetests/Browser/ScreenshotsTest.php.
- Laravel Herd
- PHP β₯ 8.4
- Composer
- Node.js β₯ 22.12 (npm)
- WP-CLI
# 1. Scaffold the project (also installs Composer + npm deps + Playwright browsers)
composer create-project bambamboole/wordpress-starter starter
cd starter
# 2. Configure environment
# Edit .env: fill DB_NAME, DB_USER, DB_PASSWORD; generate fresh salts at https://roots.io/salts.html
# 3. Create the database, install WordPress, run migrations, seed baseline content, build assets
composer setup
# 4. Visit the site
herd open https://starter.test/
# 5. Start the dev server (Vite HMR + Laravel scheduler, side by side)
composer devHerd auto-derives the .test hostname from the project directory name. To change the hostname, pass a different name to create-project (e.g., composer create-project bambamboole/wordpress-starter myproject β myproject.test).
composer create-project strips the .git directory by default β run git init afterwards to start your own history.
To wipe local data and reseed from scratch, run composer reset β drops the DB and re-runs composer setup.
| Path | Description |
|---|---|
app/Blocks/ |
Block render callbacks (Blade templates for blocks) |
app/Http/Controllers/Api/ |
REST API controllers |
app/Models/ |
Eloquent models for WordPress posts/CPTs |
app/Providers/ |
Acorn service providers |
app/View/Composers/ |
Blade view composers |
config/post-types.php |
Custom post types config |
config/theme.php |
Theme menus, sidebars, supports |
database/seeders/ |
Eloquent seeders (e.g. WordPressBaselineSeeder) |
public/content/ |
wp-content directory (themes, plugins, mu-plugins) |
resources/views/ |
Blade templates (theme + components) |
resources/js/editor/ |
Block editor JSX |
routes/web.php |
Acorn web routes |
storage/logs/laravel.log |
Application log |
theme.json |
WordPress block-editor theme config |
tests/Feature/ |
Pest PHP feature tests (REST, models) |
tests/Browser/ |
Pest browser tests via pest-plugin-browser |
# Run all tests (unit + feature + browser, in that order)
composer test
# Run only unit tests (no framework boot, fastest)
composer test:unit
# Run only feature tests (boots WP+Acorn+Eloquent)
composer test:feature
# Run only browser tests (spawns FrankenPHP subprocess on :8080)
composer test:browser
# Regenerate the README screenshots in docs/screenshots/
composer screenshots
# Dev mode: Vite + Laravel scheduler daemon, side by side
composer dev
# Front-end dev server only (HMR, no scheduler)
npm run dev
# Front-end production build
npm run build
# Production build (compiles translations + Vite production bundle)
composer build
# Translation workflow
composer translate # regenerate .pot + sync .po files
composer translate:compile # compile .po -> .mo + .json (also runs as part of `composer build`)
# Format / lint
composer format # check
composer format:fix # apply
composer lint
npm run format
npm run lint
# Scaffold a new block (creates PHP class, Blade view, JSX, registers it)
wp acorn make:block MyBlockPHP tests use Pest 4 and bootstrap WordPress + Acorn in-process via tests/FeatureTestCase.php. Browser tests use pest-plugin-browser with a custom FrankenPhpDriver that spawns FrankenPHP (Caddy + libphp, multithreaded) as a subprocess. The binary is pinned to v1.11.2 (PHP 8.4.18) and auto-downloads into the project root on the first browser-test run if missing (gitignored, CI-cached). wp acorn frankenphp:install [--force] is available as a manual pre-warm / re-download.
Both targets share a dedicated database, starter_test, configured via .env.testing. The first feature or browser test run on a machine bootstraps the test database automatically: drop+create starter_test, wp core install, activate all plugins, run Acorn migrations, run WordPressBaselineSeeder, then export to database/dumps/testing.sql. Subsequent test runs reuse the dump.
Every test re-imports the dump in setUp, giving each test a fresh baseline DB.
To force a rebuild (after adding a plugin, changing migrations, or changing the baseline seeder), delete the dump:
rm database/dumps/testing.sqlThe next test run rebuilds it.
Detailed testing notes (custom driver, Pest setup, debugging tips) live in the pest-testing skill at .ai/skills/pest-testing/SKILL.md.
The primary nav is a classic WordPress menu assigned to the primary_navigation location. The WordPressBaselineSeeder ships a default named Primary with Home, Blog, Products (with a mega panel), and Company (with a plain dropdown) so the feature is visible out of the box.
Edit it via Appearance β Menus in wp-admin. Each menu item has an extra Mega menu type fieldset (Link / Image / HTML):
- Link (default) β renders as a normal
<a>or, with children, as a column header with sub-links underneath it. - Image β pick an image from the media library; the menu item's URL is reused as the click target.
- HTML β write rich HTML (TinyMCE editor); the content is
wp_kses_post-sanitized on save.
A top-level item with at least one Image or HTML child renders as a multi-column mega panel; otherwise its children render as a plain dropdown. Depth-2 link items beneath a depth-1 link become sub-links under that column header. Click outside, Escape, or the trigger again to close. On screens narrower than lg, the whole nav collapses into a hamburger drawer that expands the panels inline.
The frontend lives in resources/views/partials/nav/ (one Blade partial per concern), the runtime tree-builder in app/Support/MegaMenu/MenuBuilder.php, and the admin/save plumbing in App\Providers\MegaMenuServiceProvider. The footer nav is also a classic menu, on location footer_navigation.
Domain knowledge and conventions live in two AI-discoverable locations so Claude (via Laravel Boost) gets the right context automatically:
- Always-loaded style rules in
.ai/guidelines/β concatenated intoCLAUDE.mdon everywp acorn boost:update. Includes PHP conventions (style/php.md), frontend conventions (style/frontend.md), Bedrock (wordpress/bedrock.md), Acorn (acorn/core.md), project structure, and the canonical command reference. - On-demand domain skills in
.claude/skills/β Claude activates these by description match when you work in a given area. Skills cover REST API (wordpress-rest-api), Gutenberg blocks (wordpress-blocks), Eloquent models (wordpress-eloquent-models), custom post types (wordpress-post-types), Blade components (blade-components), Tailwind (tailwindcss-development), translations (wordpress-i18n), and Pest testing (pest-testing). Service providers, theme configuration, and Acorn mail are covered by the always-loadedacorn/coreguideline.
CLAUDE.md itself is auto-generated by wp acorn boost:update (which runs after every composer update) and is gitignored.
MIT β see LICENSE.md. Originally derived from Roots Radicle; the original Roots copyright notice is preserved.









