How to Build a WordPress Theme from Scratch (2025 Guide)

5/5 - (1 vote)

Developer creating a custom WordPress theme from scratch on dual monitors

You want WordPress theme development from scratch without the fluff and mixed messages, right? Good. We’ll build a clean, modern, block-first theme you actually understand, and you’ll see where classic PHP templates still fit. By the end, you’ll have a working starter you can extend for clients or side projects, and you’ll know why each file exists rather than blindly copying boilerplate. Sounds fair? Let’s ship a theme you’re proud of.

What you’ll build

You’ll create a simple yet professional block theme called starterpress. It will use Global Styles via theme.json for colours, typography, and spacing, plus block templates for pages, posts, archives, and a homepage. We’ll also add a tiny functions.php for safe enqueues and feature support, and a couple of template parts to keep things tidy. You’ll finish with a theme that’s fast, editable in the Site Editor, and easy to maintain.

What you need

No fancy gear. You only need local WordPress, a code editor, and a web browser. If you’ve never set up local WordPress, tools like Local, XAMPP, or DevKinsta are fine. Prefer command line? WP-CLI is brilliant but optional. Keep it simple: install WordPress locally, log in, and be ready to add a theme folder.

Quick mindset tip: ship something small first, then iterate. Themes grow fast; you’re not carving stone you’re shaping clay.

Block theme or classic theme

Classic themes rely on PHP templates like index.php, single.php, and archive.php. Modern block themes use HTML templates (templates/*.html), Global Styles, and the Site Editor. Both work; the ecosystem now leans block-first, and your skills will age better if you learn it.

Here’s the short, honest comparison.

ApproachWhen it shinesWhat you manageLearning curve
Block themeYou want visual control in the Site Editor and scalable styles via theme.jsonTemplates as HTML, styles and options via theme.jsonModern concepts, less PHP, more editor-driven
Classic themeYou need full PHP control or maintain legacy projectsTemplates as PHP + enqueue styles/scriptsFamiliar to old-school devs but less future-proof

Block templates, parts, and theme.json are documented in the official Theme Handbook and Block Editor guides, which are the gold standard for up-to-date details.

Create the theme folder

In your local site, go to wp-content/themes/ and create a new folder:

starterpress/

Inside it, you’ll add:

starterpress/
style.css
theme.json
functions.php
templates/
index.html
single.html
page.html
archive.html
parts/
header.html
footer.html
assets/
css/
js/

Keep it minimal. You can add patterns, block styles, or a screenshot.png later.

Add the stylesheet header

Even in a block theme, style.css is required because WordPress reads its header to recognise the theme and show its details in the dashboard. Put this at the top of style.css:

/*
Theme Name: StarterPress
Theme URI: https://example.com/starterpress
Author: Alex WebPro
Author URI: https://example.com
Description: A clean block theme starter with theme.json and Site Editor support.
Version: 1.0.0
Tested up to: 6.6
Requires at least: 6.2
Requires PHP: 7.4
Text Domain: starterpress
License: GPL-2.0-or-later
*/

You can leave the rest of style.css empty for now; we’ll drive design through theme.json and only add CSS when truly needed. WordPress uses the style.css header for theme data and requires the file at the theme root, even for block themes.

WordPress dashboard preview of a custom-built theme ready for activation

Add a minimal theme.json

This file controls Global Styles for your whole theme: colours, fonts, spacing, layout widths, and which controls the editor should show or hide. Create theme.json:

{
"version": 3,
"settings": {
"appearanceTools": true,
"color": {
"palette": [
{ "slug": "ink", "name": "Ink", "color": "#111111" },
{ "slug": "paper", "name": "Paper", "color": "#ffffff" },
{ "slug": "primary", "name": "Primary", "color": "#3a68ff" },
{ "slug": "accent", "name": "Accent", "color": "#ff7a59" }
],
"defaultPalette": false
},
"typography": {
"fontFamilies": [
{
"fontFamily": "system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, sans-serif",
"slug": "system",
"name": "System Sans"
}
],
"fluid": true
},
"layout": {
"contentSize": "720px",
"wideSize": "1200px"
}
},
"styles": {
"typography": {
"fontFamily": "var(--wp--preset--font-family--system)",
"lineHeight": "1.6"
},
"elements": {
"heading": {
"typography": { "lineHeight": "1.3" },
"color": { "text": "var(--wp--preset--color--ink)" }
}
},
"color": { "text": "var(--wp--preset--color--ink)", "background": "var(--wp--preset--color--paper)" }
}
}

This gives you a sane default palette, readable line-heights, and fluid typography. version: 3 is the current schema that arrived with WordPress 6.6, bringing some changes to preset behaviour; sticking with v3 keeps you future-ready.

See also  Managing WordPress Websites Using Mobile or Tablets

Add template parts

Template parts keep your layout DRY. Create parts/header.html:

<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"1.5rem","bottom":"1.5rem"}}}} -->
<div class="wp-block-group alignfull" style="padding-top:1.5rem;padding-bottom:1.5rem">
<!-- wp:site-title {"level":2} /-->
<!-- wp:navigation {"overlayMenu":"never","layout":{"type":"flex","justifyContent":"left"}} /-->
</div>
<!-- /wp:group -->

Then parts/footer.html:

<!-- wp:group {"align":"full","style":{"spacing":{"padding":{"top":"2rem","bottom":"2rem"}}}} -->
<div class="wp-block-group alignfull" style="padding-top:2rem;padding-bottom:2rem">
<!-- wp:paragraph {"align":"center"} -->
<p class="has-text-align-center"<script>document.write(new Date().getFullYear())</script> StarterPress</p>
<!-- /wp:paragraph -->
</div>
<!-- /wp:group -->

Template parts are reusable sections like headers and footers that multiple templates can include.

Add your first templates

Create templates/index.html:

<!-- wp:template-part {"slug":"header"} /-->

<!– wp:group {“layout”:{“type”:”constrained”},”style”:{“spacing”:{“blockGap”:”2rem”,”padding”:{“top”:”2rem”,”bottom”:”2rem”}}}} –>
<div class=“wp-block-group” style=“padding-top:2rem;padding-bottom:2rem”>
<!– wp:query {“queryId”:1,”query”:{“perPage”:10,”postType”:”post”},”displayLayout”:{“type”:”list”}} –>
<div class=“wp-block-query”>
<!– wp:post-template –>
<!– wp:post-title {“isLink”:true} /–>
<!– wp:post-excerpt /–>
<!– wp:post-date /–>
<!– wp:separator {“opacity”:”css”,”className”:”is-style-wide”} /–>
<!– /wp:post-template –>

<!– wp:query-pagination –>
<!– wp:query-pagination-previous /–>
<!– wp:query-pagination-numbers /–>
<!– wp:query-pagination-next /–>
<!– /wp:query-pagination –>
</div>
<!– /wp:query –>
</div>
<!– /wp:group –>

<!– wp:template-part {“slug”:”footer”} /–>

Create templates/single.html:

<!-- wp:template-part {"slug":"header"} /-->

<!– wp:group {“layout”:{“type”:”constrained”},”style”:{“spacing”:{“padding”:{“top”:”2rem”,”bottom”:”2rem”}}}} –>
<div class=“wp-block-group” style=“padding-top:2rem;padding-bottom:2rem”>
<!– wp:post-title /–>
<!– wp:post-featured-image {“align”:”wide”} /–>
<!– wp:post-content /–>
<!– wp:comments /–>
</div>
<!– /wp:group –>

<!– wp:template-part {“slug”:”footer”} /–>

Create templates/page.html:

<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:post-title /-->
<!-- wp:post-content /-->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer"} /-->

Create templates/archive.html:

<!-- wp:template-part {"slug":"header"} /-->
<!-- wp:group {"layout":{"type":"constrained"}} -->
<div class="wp-block-group">
<!-- wp:archive-title /-->
<!-- wp:term-description /-->
<!-- wp:query {"displayLayout":{"type":"list"},"query":{"inherit":true}} -->
<div class="wp-block-query">
<!-- wp:post-template -->
<!-- wp:post-title {"isLink":true} /-->
<!-- wp:post-excerpt /-->
<!-- /wp:post-template -->
<!-- wp:query-pagination /-->
</div>
<!-- /wp:query -->
</div>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer"} /-->

The template loader follows a hierarchy to decide which template to use for each URL. In block themes those are HTML files, yet the hierarchy logic is the same, with index.php still a safety net for classics.

Add feature support and safe enqueues

Block themes don’t need mountains of code. Still, a small functions.php helps you register theme support and load scripts the right way.

Create functions.php:

<?php
/**
* StarterPress functions
*/
if ( ! function_exists( ‘starterpress_setup’ ) ) {
function starterpress_setup() {
// Let WordPress manage the document title
add_theme_support( ‘title-tag’ );// Enable featured images
add_theme_support( ‘post-thumbnails’ );// HTML5 markup support
add_theme_support( ‘html5’, [ ‘search-form’, ‘comment-form’, ‘comment-list’, ‘gallery’, ‘caption’, ‘style’, ‘script’ ] );// Custom logo
add_theme_support( ‘custom-logo’, [
‘height’ => 64,
‘width’ => 64,
‘flex-width’ => true,
‘flex-height’ => true,
] );// Editor styles (optional if you add editor-specific CSS)
add_editor_style( ‘assets/css/editor.css’ );
}
}
add_action( ‘after_setup_theme’, ‘starterpress_setup’ );

function starterpress_assets() {
// Main stylesheet (WordPress auto-loads style.css, but enqueueing gives control/versioning)
wp_enqueue_style( ‘starterpress’, get_stylesheet_uri(), [], ‘1.0.0’ );

// Optional script
wp_enqueue_script( ‘starterpress’, get_template_directory_uri() . ‘/assets/js/main.js’, [], ‘1.0.0’, true );
}
add_action( ‘wp_enqueue_scripts’, ‘starterpress_assets’ );

That’s it: titles, thumbnails, HTML5, logo, and assets. Keep JavaScript small and purposeful.

Make a sensible homepage

You can create a dedicated homepage template templates/front-page.html and let the user set a static front page in Settings. Add:

<!-- wp:template-part {"slug":"header"} /-->

<!– wp:group {“layout”:{“type”:”constrained”},”style”:{“spacing”:{“padding”:{“top”:”3rem”,”bottom”:”3rem”},”blockGap”:”2rem”}}} –>
<div class=“wp-block-group” style=“padding-top:3rem;padding-bottom:3rem”>
<!– wp:heading {“level”:1} –>
<h1>Welcome to StarterPress</h1>
<!– /wp:heading –>

<!– wp:paragraph –>
<p>This theme is light, accessible, and easy to customise in the Site Editor.</p>
<!– /wp:paragraph –>

<!– wp:buttons –>
<div class=“wp-block-buttons”>
<!– wp:button {“text”:”Read the blog”,”className”:”is-style-fill”} /–>
</div>
<!– /wp:buttons –>
</div>
<!– /wp:group –>

<!– wp:template-part {“slug”:”footer”} /–>

See also  On Page Optimization SEO Basic Tricks in Content Writing That Actually Work

This gives users a clear hero with a call-to-action. You can add patterns later for reusable sections like features, testimonials, or pricing.

Optional classic fallback loop

If you need a PHP fallback (for hybrid/legacy cases), add a simple index.php so WordPress never panics:

<?php
// Minimal classic loop (fallback)
get_header();
if ( have_posts() ) :
while ( have_posts() ) : the_post();
the_title( '<h1>', '</h1>' );
the_content();
endwhile;
else :
echo '<p>No content found.</p>';
endif;
get_footer();

WordPress will use the hierarchy to pick the most specific template, falling back to index.php when needed. Block themes don’t require this, but it’s handy if you’re migrating or experimenting.

Patterns when you’re ready

Patterns are reusable block layouts. You can register your own via the patterns/ directory or reference the Pattern Directory. Start simple and add patterns only when you have truly reusable sections.

Example: add patterns/hero.php and register a hero block pattern, or reference directory patterns through theme.json if you want curated choices for users. Use patterns to speed content creation, not to bloat the theme.

Styling the right way

Resist the urge to dump everything in CSS. Use theme.json for:

  • Colour system
  • Typography scales
  • Spacing defaults
  • Layout widths

Then reserve CSS for component quirks. If you must add custom CSS, create assets/css/global.css and import it via functions.php. Keeping most style decisions inside theme.json ensures consistency and editor controls stay in sync for non-developers. The official guide calls theme.json the canonical way to define block editor settings and styles, which is exactly why we use it.

Accessibility without drama

Keep headings in order, ensure colour contrast is strong, and avoid tiny buttons. In the Site Editor, test with keyboard only if you can’t navigate, your visitors probably can’t either. Add alt text to images and keep link text meaningful. Small habits save big headaches.

Performance basics

Your theme is already light, but don’t sabotage it. Prefer system fonts or one web font with limited weights. Only enqueue scripts you truly use. Reuse blocks and patterns instead of bespoke heavy sections everywhere. The fastest theme is the one that doesn’t ship code it doesn’t need.

Packaging to share or ship

Drop a screenshot.png in the theme root (1200×900 is safe), write a quick readme.txt that explains features and requirements, and set a GPL-compatible licence. If you plan translations, prefix strings with your text domain starterpress and generate a POT later. That’s it clean, distributable, and simple.

Troubleshooting common “first theme” issues

Theme doesn’t show up
You likely missed the style.css header or put files in the wrong folder. The header must be present and style.css must live in the theme root.

No colours or fonts appear
Your theme.json may be missing version or you didn’t define presets. Define a palette and fonts, and ensure version is set to 3 for current behaviour in WordPress 6.6+.

Header isn’t showing navigation
Make sure you added a Navigation block in parts/header.html and created a menu in the Site Editor. The Navigation block holds links; there’s no old-school register_nav_menus needed for block navigation unless you’re targeting classic menus.

Styles look different in editor vs front-end
Add add_editor_style() for editor parity if you must, but prefer theme.json so both sides share the same system of styles.

Upgrades you can add next

Block styles
Ship variations for the core Button or Image blocks. These are tiny CSS files scoped to blocks.

Pattern library
Add patterns users can drop anywhere: hero, features grid, pricing, FAQs. This transforms your theme from “skeleton” to “starter kit”.

Design tokens
Create a richer palette with semantic names like brand, surface, muted, and map them to colours. Designers love meaningful tokens.

Custom page templates
Add templates/landing.html with a no-header layout, or templates/narrow.html for focused reading. Templates let you shape the reading experience quickly.

Can you actually build your own theme

Short answer: yes. You just did the foundations, and the rest is layering design and interactivity. The Site Editor makes layout changes visual, while theme.json standardises design tokens and editor options. When you need logic, you add minimal PHP. If you prefer old-school routes, a classic theme is still viable, but block-first is where WordPress keeps investing, so the value of your skills increases there. The Theme Handbook confirms the dual path and points you to both classic and block topics.

See also  Create a Professional Business Website with WordPress Templates (Free & Easy)

Using AI smartly in your workflow

You absolutely can lean on AI to draft copy for sections like hero blurbs or feature lists, then polish it with a human tone. If you need to rewrite a paragraph for clarity or SEO, try an AI Reword Tool once to speed things up, then edit by hand so your brand voice stays intact. Use AI as a co-writer, not a crutch.

Frequently asked questions

How to develop a WordPress theme from scratch

Start with the required style.css header so WordPress recognises your theme, add a minimal theme.json for global styles, create template parts (header and footer), then build templates like index.html, single.html, and page.html. Finally, add a small functions.php for enqueues and support flags. Activate your theme in Appearance and iterate inside the Site Editor. This is the modern, block-first path that scales cleanly.

Can I build my own theme in WordPress

Of course. Keep the first version bare-bones and stable. Use theme.json for tokens (colours, fonts, spacing), keep HTML templates readable, and only write small CSS for edge cases. For advanced layouts, add Patterns so non-tech users can drop sections with one click. You control the complexity and grow as needed.

Is WordPress theme development easy

It’s easier than it’s ever been. Block themes remove a lot of PHP glue, so you focus on design systems and layout rather than template logic. Classic theming still works but requires more code, especially for loops and template conditionals. If you follow the official docs and keep your first build small, you’ll get confident quickly. Small wins compound.

Can ChatGPT build a WordPress website

It can’t click buttons in your dashboard, but it can generate code, scaffold files, explain errors, and draft copy for sections you’ll drop into templates or patterns. Ask for concrete snippets and file structures, then paste them into your theme. You’re still the builder; AI is the assistant that writes the boring boilerplate.

A tiny quality checklist before you hit Activate

Ship it checklist
1 Your style.css has a proper header and sits in the theme root.
2 theme.json has version: 3 and sane defaults for typography, colour, and spacing.
3 You have parts/header.html and parts/footer.html, plus templates/index.html, single.html, and page.html.
4 functions.php only enqueues what you really need.
5 You can change colours and fonts in the Site Editor without touching code.

WordPress theme file structure showing core PHP and CSS files

Where to learn more

When you want deeper dives into templates, hierarchy, and block theming concepts, the official Theme Handbook and Block Editor guides are the most reliable and current resources. Bookmark them and read in short bursts ten minutes a day beats a weekend cram.

Final thoughts

You don’t need a giant framework to start. You need a clear file structure, sensible defaults, and confidence to iterate. With theme.json, parts, and templates, your first version is already more maintainable than many legacy themes. Keep it lean, document your choices, and add patterns only when you see repeatable sections. Do that, and you’ll ship themes that are fast, editable, and built to last.

Leave a Comment