Modern WordPress theme boilerplate, end-to-end.
A WordPress theme boilerplate that ships the full pipeline, not just the theme code: Tailwind v4 + esbuild + PostCSS + WordPress Coding Standards + GitHub Actions deploy + Consent Mode v2 cookie banner pre-integrated. Extracted from a setup that powers two production client sites.
Status: v0.2.0 (May 2026). The starter is functional and battle-tested in production. APIs may still shift before v1.0; see CHANGELOG.md.
Most WordPress starters stop at "theme code only". This one assumes you also want CI, a deploy, cache busting, privacy compliance, and a modern frontend pipeline that does not require webpack archaeology. The four moats:
- End-to-end production pipeline. Lint, build, deploy, cache flush. All wired.
- Modern stack 2026. Tailwind v4 (not v3), esbuild (not webpack), PostCSS modern, Block Editor first-class.
- Privacy-first preset. Cookie banner with Google Consent Mode v2, default-denied + granular update, GDPR text templates in EN and IT.
- Production-grade real. Derived from production traffic, not a toy.
What it deliberately does not include:
- No page builders (Elementor, WPBakery, Bricks). Scope is developer theme, not DIY end-user.
- No premium admin plugins (ACF Pro, Yoast SEO premium). README links optional addons but the boilerplate is minimal.
- Not Hostinger-locked. The deploy workflow uses generic
DEPLOY_*SSH secrets and runs against any SSH-accessible host.
# 1. Use this repo as a GitHub template, OR clone:
git clone https://github.com/iambilotta/wp-theme-modern-starter.git
cd wp-theme-modern-starter
# 2. Move (or symlink) the project under your local WordPress install:
ln -s "$(pwd)" /path/to/wp-content/themes/wp-theme-modern-starter
# 3. Install dependencies:
npm install
composer install
# 4. Build CSS + JS:
npm run dev
# 5. Activate the theme in /wp-admin > Appearance > Themes.That's it. The site renders the sample homepage (hero, three-column features, FAQ accordion). The cookie banner pops on first load with Italian copy by default. The deploy workflow waits for you to set the five DEPLOY_* secrets in your repo settings.
| Concern | Choice | Why |
|---|---|---|
| Frontend CSS | Tailwind v4 + PostCSS | @theme design tokens map to theme.json palette so block editor swatches and front-end utilities stay in sync. |
| JS bundling | esbuild | Sub-second builds, tree shaking, source maps in dev, minification in prod. No webpack. |
| PHP standards | PHPCS with WordPress-Extra + WordPress-Docs | composer run php:lint / php:lint:autofix. PHP 7.4+ compatibility checked via PHPCompatibilityWP. |
| JS/CSS lint | ESLint + Prettier with prettier-plugin-tailwindcss |
Consistent formatting; tailwind class ordering enforced. |
| Cookie consent | vanilla-cookieconsent v3 + consent-mode-v2-adapter v0.1+ (both npm) | Default-denied gtag pushed inline in header.php; granular update on user choice via the adapter's wireVanillaCookieConsent bridge. The adapter is a sister package by the same author, banner-agnostic, ~250 LOC. |
| CI | GitHub Actions (lint + build) | Runs on PRs and feature branches. |
| Deploy | GitHub Actions (SSH + SCP + cache flush) | Single workflow against any SSH host. |
wp-theme-modern-starter/
├── theme/ ← what gets uploaded to wp-content/themes/
│ ├── functions.php ← cache busting, GTM, enqueues
│ ├── style.css ← regenerated by PostCSS, do not edit by hand
│ ├── theme.json ← block editor palette + design tokens
│ ├── front-page.php
│ ├── header.php / footer.php
│ ├── single.php / page.php / archive.php / index.php / search.php / 404.php
│ ├── page-privacy-policy.php ← EN GDPR template
│ ├── page-privacy-policy-it.php ← IT variant
│ ├── page-cookie-policy.php ← EN, Consent Mode v2 documented
│ ├── page-cookie-policy-it.php ← IT variant
│ ├── template-parts/
│ │ ├── layout/ ← header-content.php, footer-content.php
│ │ ├── content/ ← content.php, content-page.php, content-excerpt.php, content-single.php, content-none.php
│ │ └── sections/ ← section-hero.php, section-features.php, section-faq.php
│ ├── inc/ ← template-tags.php, template-functions.php
│ ├── assets/ ← drop images / fonts here
│ └── js/ ← built bundles (gitignored)
├── tailwind.css ← Tailwind entry, imports the rest
├── tailwind/
│ ├── tailwind-theme.css ← @theme design tokens
│ ├── tailwind-typography.css ← prose color set
│ ├── tailwind-editor.css ← block editor overrides
│ └── custom/
│ ├── base.css ← responsive font scale + body
│ ├── fonts.css ← @font-face hooks
│ ├── utilities.css ← custom @utility blocks
│ └── components/components.css ← navbar, FAQ, blog cards, cookie consent overrides
├── javascript/
│ ├── script.js ← navbar + FAQ + cookie consent boot
│ └── block-editor.js ← block style registration
├── bin/optimize-images.sh ← ImageMagick WebP re-encoding helper
├── node_scripts/zip.js ← bundles `theme/` into <slug>.zip
├── .github/workflows/deploy.yml ← deploy to any SSH host
├── .github/workflows/ci.yml ← lint + build on every PR
├── .github/secrets.example.yml ← deploy secrets documentation
├── package.json
├── composer.json
├── phpcs.xml.dist
├── eslint.config.js
├── prettier.config.js
├── postcss.config.js
├── LICENSE ← Apache 2.0
├── NOTICE ← third-party attributions
├── CHANGELOG.md
└── CONTRIBUTING.md
The starter uses three identifiers you will want to fork:
| Identifier | Where | Replace with |
|---|---|---|
wp-theme-modern-starter (slug + text domain) |
theme/style.css header, package.json, composer.json, phpcs.xml.dist, theme/languages/, every __('...', 'wp-theme-modern-starter') call |
your-theme-slug |
wpms (PHP function and CSS class prefix) |
theme/inc/*.php, theme/functions.php, theme/template-parts/*.php, tailwind/custom/components/components.css, javascript/script.js |
your-prefix |
WP_THEME_MODERN_STARTER_* (PHP constants) |
theme/functions.php, theme/inc/template-tags.php, node_scripts/zip.js regex |
YOURTHEME* |
A safe sequence:
# Slug + text domain
grep -rl 'wp-theme-modern-starter' --include='*.php' --include='*.json' --include='*.dist' --include='*.css' --include='*.js' --include='*.md' . \
| xargs sed -i 's/wp-theme-modern-starter/your-theme-slug/g'
# PHP/CSS short prefix (be precise: `wpms` may collide with substrings)
grep -rl '\bwpms\b' --include='*.php' --include='*.css' --include='*.js' . \
| xargs sed -i 's/\bwpms\b/your_prefix/g'
# PHP constants
grep -rl 'WP_THEME_MODERN_STARTER' --include='*.php' --include='*.js' . \
| xargs sed -i 's/WP_THEME_MODERN_STARTER/YOUR_THEME/g'Then update phpcs.xml.dist's prefixes list and text_domain, regenerate composer install and npm ci, run npm run lint-fix && composer run php:lint:autofix, and commit.
Edit theme/theme.json to change colors. The @theme block in tailwind/tailwind-theme.css reads from the WordPress-generated CSS custom properties (--wp--preset--color--*), so block editor swatches and Tailwind utilities update from one source.
// theme/theme.json
{
"settings": {
"color": {
"palette": [
{ "slug": "primary", "color": "#3b82f6", "name": "Primary" }
]
}
}
}Use the resulting class as bg-primary, text-primary, etc.
The starter loads Inter from Google Fonts at runtime (see theme/header.php). To self-host:
- Drop the font file under
theme/assets/fonts/. - Add an
@font-faceblock intailwind/custom/fonts.css. - Preload from
functions.phpso the file ships ahead ofstyle.css. - Remove the Google Fonts
<link>fromheader.php.
theme/template-parts/sections/ ships three example sections (hero, features, FAQ). Add or replace them and update the get_template_part() calls in theme/front-page.php. Each section is a self-contained PHP file so removing one cleanly removes a homepage block.
The cookie banner runtime is vanilla-cookieconsent v3, configured directly in javascript/script.js. The Consent Mode v2 wiring is inline: the default-denied state is pushed from theme/header.php before any tag fires, and the updateGtagConsent callback in script.js issues gtag('consent', 'update', ...) with the granular state per category as soon as the user makes a choice. Switch the language by changing language.default; bundled translation packs cover Italian and English. Brand the modal by editing the CSS custom properties under :root { --cc-* } in tailwind/custom/components/components.css.
// javascript/script.js
import * as CookieConsent from 'vanilla-cookieconsent';
CookieConsent.run({
language: { default: 'it' }, // or 'en'
guiOptions: {
consentModal: { layout: 'box', position: 'middle center' },
},
disablePageInteraction: true,
onConsent: updateGtagConsent,
onChange: updateGtagConsent,
categories: {
necessary: { enabled: true, readOnly: true },
analytics: { autoClear: { cookies: [{ name: /^_ga/ }] } },
marketing: { autoClear: { cookies: [{ name: /^_gcl/ }] } },
},
});To opt out of Consent Mode v2 entirely (e.g. you do not use Google products), remove the inline gtag('consent', 'default', ...) script in theme/header.php and the updateGtagConsent callback in javascript/script.js.
The sister package consent-mode-v2-adapter is the maintained source of the wiring (banner-agnostic, ~250 LOC, on npm). The starter depends on it from v0.2 onwards; the inline wrapper that lived in the v0.1 bundle is gone.
To opt out of Consent Mode v2 entirely (e.g. you do not use Google products), pass consentMode: { enabled: false } and remove the inline gtag('consent', 'default', ...) script in theme/header.php.
Set WP_THEME_MODERN_STARTER_GTM_CONTAINER_ID in theme/functions.php to a non-empty string (e.g. 'GTM-XXXX'). The container loads on first user interaction or after a 7-second timeout, so it stays out of the LCP critical path. Leave the constant empty to opt out.
- Create the five GitHub secrets documented in
.github/secrets.example.yml:DEPLOY_SERVER_IPDEPLOY_SSH_USERDEPLOY_SSH_PORTDEPLOY_SSH_KEYDEPLOY_PATH
- Add the public half of
DEPLOY_SSH_KEYto the target server's~/.ssh/authorized_keysforDEPLOY_SSH_USER. - Make sure the target server has
wp-clion$PATH(the deploy workflow flushes WordPress caches viawp cache flush). - Make sure
DEPLOY_PATHpoints at a clean working tree of your repository on the server (the workflow runsgit fetch && git reset --hard origin/mainfrom there).
checkout
-> npm ci
-> npm run production # rebuilds theme/style.css + theme/js/*.min.js
-> ssh: git fetch + reset # syncs source on host
-> scp: upload built bundles # built files are gitignored, so SCP carries them
-> ssh: wp cache flush # cleans WP object + LiteSpeed caches
The flow assumes your server keeps a checkout of the repo at DEPLOY_PATH. If you prefer to ship the entire tree from CI, swap step 3 for an rsync step.
| wp-theme-modern-starter | _s / Underscores | Sage (Roots) | _tw | |
|---|---|---|---|---|
| Tailwind v4 | yes | no | yes (configurable) | yes |
| esbuild | yes | no (no bundler) | no (Vite) | yes |
| PostCSS modern (advanced-variables, nesting) | yes | no | yes | yes |
| WP Coding Standards preconfigured | yes | no | yes | yes |
| GitHub Actions deploy | yes | no | no | no |
| GitHub Actions CI (lint + build) | yes | no | no | no |
| Cookie consent (Consent Mode v2) | yes | no | no | no |
| GDPR templates (EN + IT) | yes | no | no | no |
| Block editor first-class | yes | yes | yes | yes |
| Page builder bundled | no | no | no | no |
| License | Apache-2.0 | GPL-2.0+ | MIT | GPL-2.0+ |
- v0.2 (shipped 2026-05-01): Swap inline cookie consent wrapper for the
consent-mode-v2-adapternpm package. - v0.2: WP-CLI scaffold command for slug rename automation.
- v0.2: Translation files (
.pot+ IT translation) shipped in repo. - v0.3: Optional alternative deploy targets documented (rsync to bare-metal, Cloudways, Pantheon).
- v0.4: WP unit tests harness (PHPUnit + WP_UnitTestCase) bootstrapped.
- v1.0: API stability commitment, semantic versioning enforced, optional
wp.orgdirectory submission.
Track open issues and milestones for current priorities.
- WordPress 6.4 or higher
- PHP 7.4 or higher (8.x recommended)
- Node 20 or higher (LTS)
- Composer 2
See CONTRIBUTING.md. Issues and PRs welcome. The boilerplate is intentionally small: feature requests beyond the core scope (page builder integrations, premium plugin presets) will likely be declined.
Apache License 2.0. See LICENSE and NOTICE.
The PHP template structure follows WordPress theme conventions documented by WordPress core, Underscores (_s) and _tw. Implementation is independent and shipped under Apache 2.0.
Cookie consent runtime is vanilla-cookieconsent by Orest Bida, plus consent-mode-v2-adapter by the same author of this starter for the Consent Mode v2 wiring. Both are MIT and Apache 2.0 respectively; both are tracked as runtime dependencies in package.json.