TutorialUpdated Feb 2026

    WordPress Starter Theme Guide: Build Custom Sites From Scratch

    Pre-built themes add 300–800KB of CSS you don't need, load 15+ scripts you never use, and lock you into someone else's design decisions. A custom theme built from a starter like Underscores (_s) or Sage gives you complete control, clean markup, exact performance, and pixel-perfect design. This guide covers everything: starter theme comparison, file structure, template hierarchy, functions.php, the Loop, Customizer API, block theme (FSE) support, and production optimization.

    Mallory Keegan
    Mallory Keegan

    Web hosting enthusiast who tests providers and breaks down features, pricing, and real world speed

    WordPress theme development concept showing a code editor with PHP template files and WordPress customizer interface
    Building custom WordPress themes from starter frameworks

    Why Build a Custom WordPress Theme?

    FactorPre-Built Theme (Astra, Divi)Custom Starter Theme
    Page weight (CSS + JS)300–800KB+ (unused styles included)30–80KB (only what you write)
    HTTP requests12–25+ scripts/styles3–6 optimized bundles
    Lighthouse Performance50–75 (typical)90–100 (achievable)
    Design flexibilityLimited to theme options/builderUnlimited; you control every pixel
    Code qualityGeneric, bloated, backward-compatibleClean, semantic, purpose-built
    SEO markupGeneric schema, often missingCustom structured data, semantic HTML5
    Update dependencyTheme updates can break customizationsYou control all updates
    Client handoffEasy (visual builder)Requires training or Customizer/Gutenberg setup
    Development timeHours (configure & customize)Days to weeks (build from scratch)
    Long-term maintenanceDependent on theme vendor updatesFull ownership, no vendor lock-in

    Best Starter Themes Compared

    #1

    Underscores (_s)

    Best Classic Starter9.4/10
    Price: Free
    Approach: Classic PHP templates
    Best for: Developers wanting a minimal, clean starting point with zero opinions on styling

    Pros

    Created by Automattic (WordPress.com team)
    Minimal, only essential markup, no design decisions
    Well-documented template hierarchy
    Sass-ready option available
    Custom generator at underscores.me
    Widely used, huge community knowledge base

    Cons

    No build tools included (add your own)
    Classic theme only (no FSE/block support)
    Requires strong CSS/PHP knowledge
    No built-in Customizer options
    #2

    Sage (Roots)

    Best Modern Workflow9.2/10
    Price: Free
    Approach: Laravel Blade templates + Webpack/Vite
    Best for: Agencies and developers wanting modern PHP with MVC-like architecture and build tools

    Pros

    Laravel Blade templating (clean separation of concerns)
    Bud.js build tool (Vite-based, HMR, Tailwind ready)
    Controller pattern for passing data to views
    Acorn framework for modern WordPress development
    Composer dependency management
    Tailwind CSS pre-configured

    Cons

    Steep learning curve (Blade, Acorn, Composer)
    Non-standard WP structure (harder to hand off to WP-only devs)
    Requires Node.js + Composer on hosting
    Overkill for simple sites
    #3

    Flavor Theme

    Best Lightweight8.6/10
    Price: Free
    Approach: Minimal PHP templates
    Best for: Developers wanting an even more stripped-down starting point than _s with modern CSS

    Pros

    Extremely lightweight (<20KB)
    Modern CSS (custom properties, Grid, Flexbox)
    No jQuery dependency
    Accessibility-ready from the start
    Clean, semantic HTML5 markup
    Easy to understand codebase

    Cons

    Smaller community than _s or Sage
    No build tool configuration
    Limited documentation
    Fewer starter generator options
    #4

    theme.json (Block Theme)

    Best for Gutenberg/FSE9.0/10
    Price: Free (Core)
    Approach: HTML templates + theme.json + block patterns
    Best for: Developers building Full Site Editing themes using WordPress core block editor

    Pros

    Future of WordPress theming (FSE)
    No PHP templates needed (HTML + blocks)
    theme.json controls all design tokens
    Global styles and block variations
    Site Editor for client-friendly editing
    Core WordPress, no dependencies

    Cons

    FSE still maturing (some limitations)
    Less flexibility than PHP templates for complex logic
    Learning curve for theme.json syntax
    Fewer examples and tutorials than classic themes
    #5

    Tailpress

    Best for Tailwind CSS8.5/10
    Price: Free
    Approach: Classic PHP + Tailwind CSS + esbuild
    Best for: Developers who love Tailwind CSS and want it integrated into WordPress theme development

    Pros

    Tailwind CSS pre-configured and purge-ready
    esbuild for fast compilation
    Live reload with BrowserSync
    Clean, minimal PHP templates
    npm scripts for development workflow
    PostCSS processing included

    Cons

    Tailwind utility classes can bloat HTML
    Not suitable for themes distributed to non-Tailwind devs
    Smaller community than _s or Sage
    No TypeScript support by default
    Feature_sSageBlock ThemeTailpressFlavor
    ApproachClassic PHPBlade + MVCHTML + theme.jsonPHP + TailwindMinimal PHP
    Build tools❌ BYO✅ Bud.js (Vite)❌ None needed✅ esbuild❌ BYO
    Tailwind CSS❌✅ Pre-configured❌✅ Core feature❌
    FSE / Block ready⚠️ Add manually⚠️ Add manually✅ Native⚠️ Add manually⚠️ Add manually
    Learning curveLowHighMediumLow: MediumLow
    WooCommerce ready⚠️ Add manually✅ (community)⚠️ Limited⚠️ Add manually⚠️ Add manually
    Theme size~50KB~200KB (with deps)~10KB~80KB~20KB
    Community size⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ (growing)⭐⭐⭐⭐⭐
    Best forMost developersAgenciesFuture-proof sitesTailwind fansUltra-minimal

    WordPress Theme File Structure

    your-theme/
    ├── style.css              ← Theme metadata + base styles (REQUIRED)
    ├── index.php              ← Ultimate fallback template (REQUIRED)
    ├── functions.php          ← Theme setup, enqueue, menus, sidebars
    ├── screenshot.png         ← Theme thumbnail (1200×900px recommended)
    │
    ├── header.php             ← <head> + opening <body> + site header
    ├── footer.php             ← Site footer + closing </body>
    ├── sidebar.php            ← Widget area template
    │
    ├── single.php             ← Individual blog post
    ├── page.php               ← Static page
    ├── archive.php            ← Post listings (category, tag, date, author)
    ├── search.php             ← Search results
    ├── 404.php                ← Not found page
    ├── front-page.php         ← Homepage (if set to static page)
    ├── home.php               ← Blog posts page
    │
    ├── template-parts/        ← Reusable template components
    │   ├── content.php        ← Post content in The Loop
    │   ├── content-single.php ← Single post content
    │   ├── content-page.php   ← Page content
    │   └── content-none.php   ← No results found
    │
    ├── inc/                   ← PHP includes (modular functions)
    │   ├── customizer.php     ← Theme Customizer settings
    │   ├── template-tags.php  ← Custom template functions
    │   └── template-functions.php
    │
    ├── assets/                ← Static assets
    │   ├── css/
    │   │   └── main.css       ← Compiled/custom styles
    │   ├── js/
    │   │   └── main.js        ← Theme JavaScript
    │   ├── images/
    │   └── fonts/
    │
    ├── languages/             ← Translation files
    │   └── theme-slug.pot
    │
    └── theme.json             ← Block editor settings (optional for classic)

    Step 1: Local Development Setup

    ToolTypeCostBest ForSetup Time
    LocalWPGUI app (Mac/Win/Linux)FreeMost developers, one-click WP install2 minutes
    DDEVDocker-based CLIFreeTeams, reproducible environments5 minutes
    wp-envDocker (official WP)FreeBlock theme / plugin development3 minutes
    LandoDocker-based CLIFreeComplex setups (Redis, ES, etc.)10 minutes
    MAMP / XAMPPTraditional AMP stackFree / Pro $39Beginners, familiar setup5 minutes

    Quick start with Underscores (_s)

    # 1. Install LocalWP and create a new WordPress site
    # 2. Navigate to your themes directory
    cd ~/Local Sites/my-site/app/public/wp-content/themes/
    
    # 3. Option A: Generate from underscores.me (download + unzip)
    # Visit https://underscores.me → enter theme name → download
    
    # 4. Option B: Clone from GitHub
    git clone https://github.com/Automattic/_s.git my-theme
    cd my-theme
    
    # 5. Search-replace '_s' with your theme slug
    # Replace '_s' with 'my_theme' in all files (functions, text domain, etc.)
    
    # 6. Activate in WP Admin → Appearance → Themes

    Step 2: Create Core Theme Files

    style.css, Theme header (required)

    /*
    Theme Name: My Custom Theme
    Theme URI: https://yourdomain.com/my-theme
    Author: Your Name
    Author URI: https://yourdomain.com
    Description: A lightweight, performance-focused custom WordPress theme.
    Version: 1.0.0
    Requires at least: 6.0
    Tested up to: 6.7
    Requires PHP: 8.0
    License: GNU General Public License v2 or later
    License URI: https://www.gnu.org/licenses/gpl-2.0.html
    Text Domain: my-theme
    Tags: custom-logo, custom-menu, featured-images, threaded-comments,
          translation-ready, block-styles, wide-blocks
    */

    header.php

    <!DOCTYPE html>
    <html <?php language_attributes(); ?>>
    <head>
        <meta charset="<?php bloginfo('charset'); ?>">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <?php wp_head(); ?>
    </head>
    <body <?php body_class(); ?>>
    <?php wp_body_open(); ?>
    
    <header id="site-header" class="site-header" role="banner">
        <div class="site-branding">
            <?php if (has_custom_logo()) : ?>
                <?php the_custom_logo(); ?>
            <?php else : ?>
                <a href="<?php echo esc_url(home_url('/')); ?>">
                    <?php bloginfo('name'); ?>
                </a>
            <?php endif; ?>
        </div>
    
        <nav id="site-navigation" class="main-navigation" role="navigation"
             aria-label="<?php esc_attr_e('Primary Menu', 'my-theme'); ?>">
            <?php wp_nav_menu(array(
                'theme_location' => 'primary',
                'menu_id'        => 'primary-menu',
                'container'      => false,
                'fallback_cb'    => false,
            )); ?>
        </nav>
    </header>

    footer.php

        <footer id="site-footer" class="site-footer" role="contentinfo">
            <div class="footer-widgets">
                <?php if (is_active_sidebar('footer-1')) : ?>
                    <?php dynamic_sidebar('footer-1'); ?>
                <?php endif; ?>
            </div>
            <div class="site-info">
                <p>&copy; <?php echo date('Y'); ?>
                   <?php bloginfo('name'); ?>.
                   <?php esc_html_e('All rights reserved.', 'my-theme'); ?>
                </p>
            </div>
        </footer>
    
        <?php wp_footer(); ?>
    </body>
    </html>

    Step 3: Template Hierarchy

    WordPress uses a cascading template system, it looks for the most specific template first, then falls back to more general ones. Understanding this is the foundation of theme development.

    Page TypeTemplate Priority (most → least specific)Example
    Single Postsingle-{post-type}-{slug}.php → single-{post-type}.php → single.php → singular.php → index.phpsingle-post-hello-world.php
    Static Pagepage-{slug}.php → page-{id}.php → page.php → singular.php → index.phppage-about.php
    Category Archivecategory-{slug}.php → category-{id}.php → category.php → archive.php → index.phpcategory-tutorials.php
    Custom Post Typearchive-{post-type}.php → archive.php → index.phparchive-portfolio.php
    Author Archiveauthor-{nicename}.php → author-{id}.php → author.php → archive.php → index.phpauthor-mallory.php
    Search Resultssearch.php → index.phpsearch.php
    404 Error404.php → index.php404.php
    Homepagefront-page.php → home.php → index.phpfront-page.php

    Step 4: functions.php Essentials

    functions.php, Complete starter configuration

    <?php
    /**
     * Theme functions and definitions.
     */
    
    // === THEME SETUP ===
    function mytheme_setup() {
        // Add title tag support
        add_theme_support('title-tag');
    
        // Featured images
        add_theme_support('post-thumbnails');
        set_post_thumbnail_size(1200, 630, true);
        add_image_size('hero', 1920, 800, true);
        add_image_size('card', 600, 400, true);
    
        // Custom logo
        add_theme_support('custom-logo', array(
            'height'      => 100,
            'width'       => 400,
            'flex-height' => true,
            'flex-width'  => true,
        ));
    
        // HTML5 markup
        add_theme_support('html5', array(
            'search-form', 'comment-form', 'comment-list',
            'gallery', 'caption', 'style', 'script',
        ));
    
        // Block editor support
        add_theme_support('wp-block-styles');
        add_theme_support('align-wide');
        add_theme_support('editor-styles');
        add_editor_style('assets/css/editor-style.css');
        add_theme_support('responsive-embeds');
    
        // Register navigation menus
        register_nav_menus(array(
            'primary' => __('Primary Menu', 'my-theme'),
            'footer'  => __('Footer Menu', 'my-theme'),
        ));
    }
    add_action('after_setup_theme', 'mytheme_setup');
    
    // === ENQUEUE STYLES & SCRIPTS ===
    function mytheme_scripts() {
        // Main stylesheet
        wp_enqueue_style(
            'mytheme-style',
            get_template_directory_uri() . '/assets/css/main.css',
            array(),
            filemtime(get_template_directory() . '/assets/css/main.css')
        );
    
        // Theme JavaScript (in footer)
        wp_enqueue_script(
            'mytheme-script',
            get_template_directory_uri() . '/assets/js/main.js',
            array(),
            filemtime(get_template_directory() . '/assets/js/main.js'),
            true  // Load in footer
        );
    
        // Comment reply script (only when needed)
        if (is_singular() && comments_open()) {
            wp_enqueue_script('comment-reply');
        }
    }
    add_action('wp_enqueue_scripts', 'mytheme_scripts');
    
    // === REGISTER SIDEBARS / WIDGET AREAS ===
    function mytheme_widgets_init() {
        register_sidebar(array(
            'name'          => __('Sidebar', 'my-theme'),
            'id'            => 'sidebar-1',
            'before_widget' => '<section class="widget %2$s">',
            'after_widget'  => '</section>',
            'before_title'  => '<h3 class="widget-title">',
            'after_title'   => '</h3>',
        ));
        register_sidebar(array(
            'name'          => __('Footer Widgets', 'my-theme'),
            'id'            => 'footer-1',
            'before_widget' => '<div class="footer-widget %2$s">',
            'after_widget'  => '</div>',
            'before_title'  => '<h4 class="widget-title">',
            'after_title'   => '</h4>',
        ));
    }
    add_action('widgets_init', 'mytheme_widgets_init');
    
    // === PERFORMANCE: Remove unnecessary features ===
    // Remove emoji scripts
    remove_action('wp_head', 'print_emoji_detection_script', 7);
    remove_action('wp_print_styles', 'print_emoji_styles');
    
    // Remove oEmbed discovery
    remove_action('wp_head', 'wp_oembed_add_discovery_links');
    
    // Disable XML-RPC (security + performance)
    add_filter('xmlrpc_enabled', '__return_false');

    Step 5: The WordPress Loop

    The Loop is WordPress's mechanism for displaying posts. Every template that shows content uses some form of The Loop.

    index.php, Basic Loop with template parts

    <?php get_header(); ?>
    
    <main id="primary" class="site-main" role="main">
        <?php if (have_posts()) : ?>
    
            <?php if (is_home() && !is_front_page()) : ?>
                <header class="page-header">
                    <h1 class="page-title"><?php single_post_title(); ?></h1>
                </header>
            <?php endif; ?>
    
            <div class="posts-grid">
                <?php while (have_posts()) : the_post(); ?>
                    <?php get_template_part('template-parts/content', get_post_type()); ?>
                <?php endwhile; ?>
            </div>
    
            <?php the_posts_pagination(array(
                'prev_text' => __('← Previous', 'my-theme'),
                'next_text' => __('Next →', 'my-theme'),
            )); ?>
    
        <?php else : ?>
            <?php get_template_part('template-parts/content', 'none'); ?>
        <?php endif; ?>
    </main>
    
    <?php get_sidebar(); ?>
    <?php get_footer(); ?>

    template-parts/content.php, Post card in The Loop

    <article id="post-<?php the_ID(); ?>" <?php post_class('post-card'); ?>>
        <?php if (has_post_thumbnail()) : ?>
            <a href="<?php the_permalink(); ?>" class="post-thumbnail">
                <?php the_post_thumbnail('card', array(
                    'loading' => 'lazy',
                    'alt'     => the_title_attribute(array('echo' => false)),
                )); ?>
            </a>
        <?php endif; ?>
    
        <div class="post-card-content">
            <header class="entry-header">
                <?php the_title(sprintf(
                    '<h2 class="entry-title"><a href="%s">',
                    esc_url(get_permalink())
                ), '</a></h2>'); ?>
                <div class="entry-meta">
                    <time datetime="<?php echo get_the_date('c'); ?>">
                        <?php echo get_the_date(); ?>
                    </time>
                    <span class="author">
                        <?php the_author_posts_link(); ?>
                    </span>
                </div>
            </header>
            <div class="entry-excerpt">
                <?php the_excerpt(); ?>
            </div>
        </div>
    </article>

    Step 6: Theme Customizer API

    The Customizer lets clients edit theme settings with live preview, without touching code.

    inc/customizer.php, Register custom settings

    <?php
    function mytheme_customize_register($wp_customize) {
    
        // === HERO SECTION ===
        $wp_customize->add_section('mytheme_hero', array(
            'title'    => __('Hero Section', 'my-theme'),
            'priority' => 30,
        ));
    
        // Hero heading
        $wp_customize->add_setting('hero_heading', array(
            'default'           => 'Welcome to Our Site',
            'sanitize_callback' => 'sanitize_text_field',
            'transport'         => 'postMessage', // Live preview
        ));
        $wp_customize->add_control('hero_heading', array(
            'label'   => __('Hero Heading', 'my-theme'),
            'section' => 'mytheme_hero',
            'type'    => 'text',
        ));
    
        // Hero CTA button text
        $wp_customize->add_setting('hero_cta_text', array(
            'default'           => 'Get Started',
            'sanitize_callback' => 'sanitize_text_field',
        ));
        $wp_customize->add_control('hero_cta_text', array(
            'label'   => __('CTA Button Text', 'my-theme'),
            'section' => 'mytheme_hero',
            'type'    => 'text',
        ));
    
        // Primary color
        $wp_customize->add_setting('primary_color', array(
            'default'           => '#2563eb',
            'sanitize_callback' => 'sanitize_hex_color',
            'transport'         => 'postMessage',
        ));
        $wp_customize->add_control(new WP_Customize_Color_Control(
            $wp_customize, 'primary_color', array(
                'label'   => __('Primary Color', 'my-theme'),
                'section' => 'colors',
            )
        ));
    }
    add_action('customize_register', 'mytheme_customize_register');
    
    // Output custom CSS from Customizer settings
    function mytheme_customizer_css() {
        $primary = get_theme_mod('primary_color', '#2563eb');
        ?>
        <style>
            :root { --color-primary: <?php echo esc_attr($primary); ?>; }
        </style>
        <?php
    }
    add_action('wp_head', 'mytheme_customizer_css');

    Step 7: Block Theme (FSE) Support

    Full Site Editing (FSE) is the future of WordPress theming. Even if you build a classic theme, add theme.json for block editor integration.

    theme.json, Design tokens and block settings

    {
      "$schema": "https://schemas.wp.org/wp/6.7/theme.json",
      "version": 3,
      "settings": {
        "color": {
          "palette": [
            { "slug": "primary", "color": "#2563eb", "name": "Primary" },
            { "slug": "secondary", "color": "#7c3aed", "name": "Secondary" },
            { "slug": "dark", "color": "#1e293b", "name": "Dark" },
            { "slug": "light", "color": "#f8fafc", "name": "Light" }
          ],
          "gradients": [
            {
              "slug": "primary-to-secondary",
              "gradient": "linear-gradient(135deg, #2563eb, #7c3aed)",
              "name": "Primary to Secondary"
            }
          ]
        },
        "typography": {
          "fontFamilies": [
            {
              "fontFamily": "'Inter', sans-serif",
              "slug": "body",
              "name": "Body"
            },
            {
              "fontFamily": "'Playfair Display', serif",
              "slug": "heading",
              "name": "Heading"
            }
          ],
          "fontSizes": [
            { "slug": "small", "size": "0.875rem", "name": "Small" },
            { "slug": "medium", "size": "1rem", "name": "Medium" },
            { "slug": "large", "size": "1.5rem", "name": "Large" },
            { "slug": "x-large", "size": "2.5rem", "name": "Extra Large" }
          ]
        },
        "spacing": {
          "units": ["px", "rem", "%", "vw"],
          "padding": true,
          "margin": true
        },
        "layout": {
          "contentSize": "768px",
          "wideSize": "1200px"
        }
      },
      "styles": {
        "color": {
          "background": "var(--wp--preset--color--light)",
          "text": "var(--wp--preset--color--dark)"
        },
        "typography": {
          "fontFamily": "var(--wp--preset--font-family--body)",
          "fontSize": "var(--wp--preset--font-size--medium)",
          "lineHeight": "1.6"
        },
        "elements": {
          "h1": {
            "typography": {
              "fontFamily": "var(--wp--preset--font-family--heading)",
              "fontSize": "var(--wp--preset--font-size--x-large)"
            }
          },
          "link": {
            "color": { "text": "var(--wp--preset--color--primary)" }
          }
        }
      }
    }

    Step 8: Performance Optimization

    OptimizationImplementationImpact
    Defer non-critical JSwp_script_add_data('handle', 'strategy', 'defer')Reduces render-blocking by 200–500ms
    Lazy load imagesloading="lazy" on <img> tags (WP 5.5+ auto)Saves 100–500KB on initial load
    Use srcset for responsive imageswp_get_attachment_image() generates srcset automatically30–70% image bandwidth savings on mobile
    Preload critical assetswp_preload_resources filter or <link rel='preload'>50–200ms faster LCP (fonts, hero image)
    Remove block library CSSwp_dequeue_style('wp-block-library') if not using blocksSaves ~40KB unused CSS
    Remove jQuery (if unused)wp_dequeue_script('jquery') + rewrite in vanilla JSSaves ~90KB + eliminates dependency chain
    Use transient cachingset_transient() for expensive queries (1–12hr cache)Reduces DB queries by 50–80% on complex pages
    Minify CSS/JS in productionBuild tool (esbuild, Vite) or Autoptimize plugin20–40% smaller file sizes
    Critical CSS inlineInline above-the-fold CSS in <head>, defer restEliminates render-blocking stylesheet
    Async load web fontsfont-display: swap in @font-face + preload key fontsPrevents FOIT (Flash of Invisible Text)

    Step 9: Security Hardening

    Security MeasureImplementationWhat It Prevents
    Escape all outputesc_html(), esc_attr(), esc_url(), wp_kses_post()XSS (Cross-Site Scripting) attacks
    Sanitize all inputsanitize_text_field(), absint(), sanitize_email()SQL injection, data corruption
    Use nonces for formswp_nonce_field() + wp_verify_nonce()CSRF (Cross-Site Request Forgery)
    Prefix everythingmytheme_ prefix on functions, classes, handlesNamespace collisions with plugins/core
    Validate capabilitiescurrent_user_can('edit_posts') before actionsPrivilege escalation
    Hide WordPress versionremove_action('wp_head', 'wp_generator')Version-targeted exploits
    Disable file editingdefine('DISALLOW_FILE_EDIT', true) in wp-config.phpBackdoor injection via Theme Editor
    Use prepared statements$wpdb->prepare() for all custom queriesSQL injection attacks

    Best Hosting for Theme Development

    HostStagingGit DeploySSH AccessWP-CLIPrice
    Kinsta✅ One-click✅ (SSH-based)✅✅From $35/mo
    Cloudways✅ One-click✅ (Git webhook)✅✅From $14/mo
    SiteGround✅ (GoGeek+)✅ (Git)✅✅From $2.99/mo
    WP Engine✅ Built-in✅ (Git push)✅ (SFTP/SSH)✅From $20/mo
    Pressidium✅ Built-in❌✅✅From $25/mo
    Flywheel✅ Built-in❌✅ (SFTP)❌From $15/mo

    Theme Development Checklist

    Before Launch ✅

    All template files created (index, single, page, archive, 404, search)
    functions.php: menus, sidebars, theme support registered
    Enqueue all styles/scripts properly (no hardcoded links)
    Responsive design tested on mobile, tablet, desktop
    Accessibility: semantic HTML, ARIA labels, keyboard navigation
    Translation-ready: all strings wrapped in __() or _e()
    Escape all output: esc_html(), esc_attr(), esc_url()
    Theme passes Theme Check plugin (if distributing)
    Screenshot.png included (1200×900px)
    theme.json configured for block editor support

    Performance & Security ✅

    Lighthouse score 90+ on Performance
    No render-blocking scripts (defer/async)
    Images lazy loaded + WebP format
    Critical CSS inlined, non-critical deferred
    Remove unused features (emoji, XML-RPC, oEmbed)
    All forms use nonces (wp_nonce_field)
    All DB queries use $wpdb->prepare()
    Custom queries use WP_Query (not query_posts)
    CSS/JS minified for production
    Child theme created if extending another theme

    Frequently Asked Questions

    Should I build a classic PHP theme or a block theme (FSE)?
    In 2026, the answer depends on your project: Build a classic PHP theme if: You need complex custom post types, advanced query logic, or WooCommerce customization. PHP templates give you full control. Start with Underscores (_s) or Sage. Build a block theme (FSE) if: You want clients to edit headers, footers, and templates visually in the Site Editor. Best for blogs, brochure sites, and content-heavy sites. Use the Create Block Theme plugin to scaffold. Hybrid approach: Many developers build a classic theme with block support, PHP templates handle layout logic while blocks power the content area. Add 'add_theme_support("editor-styles")' and 'add_theme_support("wp-block-styles")' to functions.php.
    What's the minimum files needed for a WordPress theme?
    Only 2 files are technically required: style.css (with the theme header comment containing Theme Name, Version, etc.) and index.php (the fallback template). However, a practical minimum theme includes: style.css, index.php, functions.php (enqueue styles/scripts, theme support), header.php, footer.php, sidebar.php, single.php (individual posts), page.php (static pages), archive.php (post listings), 404.php, and screenshot.png (theme thumbnail). For block themes: style.css, theme.json, and templates/index.html are the minimum.
    How do I make my custom theme fast?
    Performance checklist for WordPress themes: 1) Enqueue styles/scripts properly, never hardcode in header.php. Use wp_enqueue_script with 'in_footer' => true. 2) Minimize HTTP requests, combine CSS files, use a single stylesheet when possible. 3) Defer non-critical JS, add 'defer' or 'async' attributes. 4) Optimize images; use WebP, lazy loading (loading='lazy'), and srcset for responsive images. 5) Minimize database queries; avoid query_posts(), use WP_Query or pre_get_posts filter instead. 6) Use transients, cache expensive queries with set_transient(). 7) Remove unused features, dequeue block library CSS if not using Gutenberg, remove emoji scripts, disable XML-RPC. 8) Minify output; use build tools to minify CSS/JS for production. Target: <3s load time, <500KB total page weight, 90+ Lighthouse score.
    How do I add WooCommerce support to my custom theme?
    WooCommerce theme integration steps: 1) Declare support in functions.php: add_theme_support('woocommerce'); add_theme_support('wc-product-gallery-zoom'); add_theme_support('wc-product-gallery-lightbox'); add_theme_support('wc-product-gallery-slider'); 2) Create woocommerce.php (main WooCommerce template) or use the default wrapper. 3) Override WooCommerce templates by copying them from plugins/woocommerce/templates/ to your-theme/woocommerce/. 4) Style the shop, cart, and checkout pages to match your theme. 5) Test the full purchase flow: shop → cart → checkout → payment → order confirmation. Key rule: Never edit WooCommerce plugin files directly, always use template overrides or action/filter hooks.
    What's the WordPress template hierarchy and why does it matter?
    The template hierarchy is WordPress's system for choosing which PHP template file to use for each page. WordPress checks for the most specific template first, then falls back to more general ones. Example for a single blog post: 1) single-{post-type}-{slug}.php (most specific). 2) single-{post-type}.php. 3) single.php. 4) singular.php. 5) index.php (ultimate fallback). Example for a category archive: 1) category-{slug}.php. 2) category-{id}.php. 3) category.php. 4) archive.php. 5) index.php. Why it matters: You only create template files for pages that need unique layouts. A blog with a custom portfolio post type might only need: index.php, single.php, single-portfolio.php, archive-portfolio.php, and page.php, WordPress handles the rest with fallbacks.
    How do I set up a local WordPress development environment?
    Recommended local dev tools (ranked): 1) LocalWP (formerly Local by Flywheel), Free, one-click WordPress install, Nginx or Apache, PHP version switching, SSL, live links for client preview. Best for most developers. 2) DDEV, Docker-based, fast, supports WordPress + WooCommerce, Xdebug built-in, multi-site support. Best for teams. 3) wp-env (@wordpress/env), Official WordPress Docker environment. Run 'npx @wordpress/env start' in your theme directory. Best for block theme development. 4) Lando, Docker-based, highly configurable, supports custom services (Redis, Elasticsearch). Best for complex setups. 5) MAMP/XAMPP, Traditional Apache/MySQL/PHP stack. Reliable but dated. Still works fine. Workflow: Local dev → Git → Staging (on your host) → Production. Use WP-CLI for database exports/imports between environments.
    How do I make my theme translation-ready?
    Internationalization (i18n) steps: 1) Wrap all user-facing strings in translation functions: __('text', 'theme-slug') for strings, _e('text', 'theme-slug') for echoed strings, esc_html__() for escaped HTML output, printf(esc_html__('Posted by %s', 'theme-slug'), $author) for strings with variables. 2) Generate a .pot file using WP-CLI: wp i18n make-pot . languages/theme-slug.pot. 3) Load text domain in functions.php: load_theme_textdomain('theme-slug', get_template_directory() . '/languages'); 4) Create translations using Poedit or Loco Translate plugin, generates .po and .mo files. 5) For block themes, translations go in theme.json and are handled automatically by WordPress.
    Should I use a page builder instead of a custom theme?
    Custom theme vs page builder comparison: Build a custom theme when: Performance matters (page builders add 200–500KB+ of CSS/JS). You need unique, brand-specific design that no builder can replicate. You want full control over markup, accessibility, and SEO. Long-term project with ongoing development. Use a page builder when: Client needs to edit layouts without developer help. Quick turnaround is more important than performance. Template-based design is acceptable. Budget is limited (faster to build). Hybrid approach: Build a custom theme for the overall structure (header, footer, global styles) and let clients use Gutenberg blocks for content areas. This gives performance + flexibility. Avoid: Elementor/Divi for client sites where you'll maintain the code, the builder lock-in and performance overhead create long-term technical debt.

    Need WordPress Hosting for Theme Development?

    Compare managed WordPress hosts with staging environments, Git deployment, SSH access, and WP-CLI, everything a theme developer needs.

    Find Developer-Friendly Hosting