Skip to content

Repository files navigation

wp-theme-modern-starter

Modern WordPress theme boilerplate, end-to-end.

License: Apache 2.0 Tailwind v4 esbuild GitHub Actions Consent Mode v2

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.

Why this starter

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:

  1. End-to-end production pipeline. Lint, build, deploy, cache flush. All wired.
  2. Modern stack 2026. Tailwind v4 (not v3), esbuild (not webpack), PostCSS modern, Block Editor first-class.
  3. Privacy-first preset. Cookie banner with Google Consent Mode v2, default-denied + granular update, GDPR text templates in EN and IT.
  4. 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.

60-second quickstart

# 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.

Stack

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.

Project structure

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

Customization

Renaming the theme

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.

Design tokens

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.

Fonts

The starter loads Inter from Google Fonts at runtime (see theme/header.php). To self-host:

  1. Drop the font file under theme/assets/fonts/.
  2. Add an @font-face block in tailwind/custom/fonts.css.
  3. Preload from functions.php so the file ships ahead of style.css.
  4. Remove the Google Fonts <link> from header.php.

Sample homepage sections

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.

Cookie consent

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.

GTM

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.

Deployment

One-time setup

  1. Create the five GitHub secrets documented in .github/secrets.example.yml:
    • DEPLOY_SERVER_IP
    • DEPLOY_SSH_USER
    • DEPLOY_SSH_PORT
    • DEPLOY_SSH_KEY
    • DEPLOY_PATH
  2. Add the public half of DEPLOY_SSH_KEY to the target server's ~/.ssh/authorized_keys for DEPLOY_SSH_USER.
  3. Make sure the target server has wp-cli on $PATH (the deploy workflow flushes WordPress caches via wp cache flush).
  4. Make sure DEPLOY_PATH points at a clean working tree of your repository on the server (the workflow runs git fetch && git reset --hard origin/main from there).

What happens on git push origin main

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.

Differentiation

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+

Roadmap

  • v0.2 (shipped 2026-05-01): Swap inline cookie consent wrapper for the consent-mode-v2-adapter npm 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.org directory submission.

Track open issues and milestones for current priorities.

Compatibility

  • WordPress 6.4 or higher
  • PHP 7.4 or higher (8.x recommended)
  • Node 20 or higher (LTS)
  • Composer 2

Contributing

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.

License

Apache License 2.0. See LICENSE and NOTICE.

Acknowledgements

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.

About

Modern WordPress theme boilerplate: Tailwind v4 + esbuild + PostCSS + WPCS + GitHub Actions deploy + Consent Mode v2

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages