Creating a Starter Block Theme

Phase 1: Foundation & Setup

Tools You’ll Need:

  • Local Development: WordPress Studio (free, open-source) or LocalWP
  • Design: Figma for mockups and design system documentation
  • Development: VS Code with WordPress-related extensions
  • Essential Plugin: Create Block Theme — this is indispensable for saving Site Editor changes back to theme files

Starting Point Options:

  1. Twenty Twenty-Five (latest default theme) — recommended as your base
  2. Twenty Twenty-Four — Anders Norén’s preferred starting point
  3. Empty Theme — using Create Block Theme plugin to generate a blank slate

Phase 2: The Seven-Task Workflow (Adapted for Agency Use)

Task 1: Strip & Restructure

Remove everything you won’t need from your base theme:

  • Delete unused templates, template parts, patterns
  • Remove fonts and images you won’t use
  • Rename files to match your agency branding
  • Keep the folder structure: /parts/, /patterns/, /templates/, /styles/, /assets/

Recommended Folder Structure:

your-starter-theme/
├── assets/
│   ├── fonts/
│   ├── images/
│   └── css/          # Block-specific stylesheets
├── parts/
│   ├── header.html
│   ├── footer.html
│   └── sidebar.html
├── patterns/
│   ├── hidden-header.php
│   ├── hidden-footer.php
│   └── [reusable patterns]
├── styles/
│   └── [style variations].json
├── templates/
│   ├── index.html
│   ├── single.html
│   ├── page.html
│   ├── archive.html
│   ├── 404.html
│   └── search.html
├── functions.php
├── style.css
├── theme.json
└── readme.txt

Task 2: Configure theme.json

This is your design system definition. Set up:

Spacing Scale:

json

"spacing": {
    "spacingScale": { "steps": 0 },
    "spacingSizes": [
        { "name": "XS", "size": "8px", "slug": "10" },
        { "name": "S", "size": "16px", "slug": "20" },
        { "name": "M", "size": "24px", "slug": "30" },
        { "name": "L", "size": "clamp(32px, 4vw, 48px)", "slug": "40" },
        { "name": "XL", "size": "clamp(48px, 6vw, 64px)", "slug": "50" }
    ]
}

Color Palette (use semantic names for client flexibility):

json

"color": {
    "defaultPalette": false,
    "palette": [
        { "color": "#FFFFFF", "name": "Base", "slug": "base" },
        { "color": "#F5F5F5", "name": "Base Alt", "slug": "base-alt" },
        { "color": "#1A1A1A", "name": "Contrast", "slug": "contrast" },
        { "color": "#666666", "name": "Contrast Muted", "slug": "contrast-muted" },
        { "color": "#0066CC", "name": "Primary", "slug": "primary" },
        { "color": "#004499", "name": "Primary Dark", "slug": "primary-dark" }
    ]
}

Typography — include variable fonts for performance:

json

"typography": {
    "fontFamilies": [
        {
            "fontFace": [
                {
                    "fontFamily": "Inter",
                    "fontStyle": "normal",
                    "fontWeight": "100 900",
                    "src": ["file:./assets/fonts/inter-var.woff2"]
                }
            ],
            "fontFamily": "\"Inter\", sans-serif",
            "name": "Inter",
            "slug": "body"
        }
    ]
}
```

#### Task 3: Templates & Template Parts
Build your core layouts. Key principle: **templates call template parts, which call patterns**.

**Example structure:**
```
Template (index.html)
  └── Template Part (header.html)
        └── Pattern (hidden-header.php) ← Contains translatable strings
  └── Content blocks
  └── Template Part (footer.html)
        └── Pattern (hidden-footer.php)

Why patterns for headers/footers? They’re PHP files, so you can use esc_html_e() for translation-ready text.

Task 4: Handle Extended Functionality

For agency work, consider:

Block Bindings API for dynamic content:

  • Copyright year in footer
  • Reading time
  • Custom field display

Register in functions.php:

php

function starter_register_block_bindings() {
    register_block_bindings_source(
        'starter/copyright-year',
        array(
            'label' => __('Copyright Year', 'starter-theme'),
            'get_value_callback' => 'starter_copyright_year_callback'
        )
    );
}
add_action('init', 'starter_register_block_bindings');

function starter_copyright_year_callback() {
    return '© ' . date('Y');
}

Custom Block Styles:

php

register_block_style(
    'core/button',
    array(
        'name' => 'outline-primary',
        'label' => __('Outline Primary', 'starter-theme')
    )
);

Task 5: Create Reusable Patterns

Build patterns your agency will use across projects:

  • Hero sections (multiple variations)
  • CTA blocks
  • Testimonial layouts
  • Team member cards
  • Contact forms
  • FAQ accordions

Pattern header format:

php

<?php
/**
 * Title: Hero with CTA
 * Slug: starter/hero-cta
 * Categories: featured, starter-heroes
 * Keywords: hero, banner, cta
 * Viewport Width: 1400
 */
?>

Task 6: Style Variations

Create 2-3 style variations for quick client customization:

  • Default (light)
  • Dark mode
  • Alternative color scheme

Store as JSON files in /styles/:

json

{
    "title": "Dark Mode",
    "settings": {
        "color": {
            "palette": [
                { "color": "#1A1A1A", "name": "Base", "slug": "base" },
                { "color": "#FFFFFF", "name": "Contrast", "slug": "contrast" }
            ]
        }
    }
}

Task 7: Quality Assurance

  • Run Theme Check plugin
  • Test accessibility (keyboard navigation, screen readers)
  • Validate on multiple devices
  • Check the Stylebook view for missed block styles

GitHub Setup

Initial Repository Setup

bash

# Initialize git in your theme folder
cd wp-content/themes/your-starter-theme
git init

# Create .gitignore
cat > .gitignore << EOF
.DS_Store
Thumbs.db
*.log
node_modules/
.env
*.zip
EOF

# Initial commit
git add .
git commit -m "Initial commit: Agency starter theme foundation"

# Connect to GitHub
git remote add origin https://github.com/yourusername/starter-theme.git
git branch -M main
git push -u origin main

You can also run this locally before pushing by running npx wordpress-theme-check-action inside your theme folder.

GitHub Actions for Automated Testing

Create .github/workflows/theme-check.yml:

yaml

name: Deploy to WordPress.org
on:
  push:
    tags:
      - "*"

jobs:
  tag:
    name: New tag
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: |
          npm install
          npm run build
      - name: WordPress Theme Deploy
        uses: Codeinwp/action-wordpress-theme-deploy@primary
        env:
          SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
          SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
          SLUG: your-theme-slug

Deploying to WordPress.org Theme Repository

Once your theme is approved, you can automate future releases. Create .github/workflows/deploy.yml:

yaml

name: Deploy to WordPress.org
on:
  push:
    tags:
      - "*"

jobs:
  tag:
    name: New tag
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build
        run: |
          npm install
          npm run build
      - name: WordPress Theme Deploy
        uses: Codeinwp/action-wordpress-theme-deploy@primary
        env:
          SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
          SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
          SLUG: your-theme-slug

Branch Strategy for Agency Use

  • main — stable, production-ready
  • develop — active development
  • feature/* — new features
  • client/* — client-specific forks

WordPress.org Theme Repository Submission

Pre-Submission Checklist

Required Files:

  • style.css with proper header
  • readme.txt with changelog and description
  • screenshot.png (1200×900 px recommended)
  • Valid theme.json

style.css Header:

css

/*
Theme Name: Your Starter Theme
Theme URI: https://yoursite.com/theme
Author: Your Agency Name
Author URI: https://yoursite.com
Description: A flexible block theme starter kit for agencies.
Version: 1.0.0
Requires at least: 6.4
Tested up to: 6.8
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: your-starter-theme
Tags: block-patterns, block-styles, full-site-editing, wide-blocks
*/

Testing Tools

Testing Tools

1. Theme Check Plugin Install from WordPress.org admin and run comprehensive checks. This is what Anders Norén used before submitting Pulitzer.

2. WordPress Theme Review Action (Local)

bash

cd your-theme-folder
npx wordpress-theme-check-action

3. PHP CodeSniffer for WordPress Coding Standards

bash

composer require --dev wp-coding-standards/wpcs dealerdirect/phpcodesniffer-composer-installer
./vendor/bin/phpcs --standard=WordPress-Theme .

Common Issues to Avoid

  • No PHP errors or warnings
  • Escape all output: Use esc_html(), esc_attr(), esc_url()
  • Prefix everything: Functions, classes, handles with your theme slug
  • No hardcoded links: Use home_url(), get_template_directory_uri()
  • Include skip links for accessibility
  • License compatibility: All assets (fonts, images) must be GPL-compatible
  • No minified JS/CSS without source files

Submission Process

  1. Create account at wordpress.org/themes
  2. Go to Upload Your Theme
  3. Upload ZIP file (exclude .git, node_modules, etc.)
  4. Wait for review (typically 2-8 weeks)
  5. Respond to reviewer feedback promptly

Key Resources (Verified)

ResourceURL
Theme Handbookhttps://developer.wordpress.org/themes/
theme.json Referencehttps://developer.wordpress.org/themes/global-settings-and-styles/
Block Bindings APIhttps://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/
Create Block Theme Pluginhttps://wordpress.org/plugins/create-block-theme/
Theme Check Pluginhttps://wordpress.org/plugins/theme-check/
WordPress Theme Review Actionhttps://github.com/WordPress/theme-review-action
Theme Deploy Actionhttps://github.com/marketplace/actions/wordpress-theme-deploy
Pulitzer Theme (Reference)https://github.com/andersnoren/pulitzer
Submission Guidelineshttps://developer.wordpress.org/themes/releasing-your-theme/submitting-your-theme-to-wordpress-org/