Build a Custom WordPress Theme

Build a multi-template WordPress theme from blank files using PHP.

Introduction

30 Second Summary

Every WordPress site you visit uses a theme to control how it looks and feels. Most people install pre-built themes, but the developers who build those themes from scratch are the ones companies actually hire.

In this project, you will build a custom WordPress theme entirely from code. Starting with blank files, you'll create a professional business website with blog posts, page templates, and a navigation menu.

What You'll Build

When you're done, you'll open your browser and see a fully styled business website powered by your own custom theme. You can publish a blog post and watch it appear in a formatted listing, click through to an About page that uses its own clean layout, and point to your name as the theme author in the WordPress admin.

By the end of this project, you'll have:

  • A custom theme that appears in the WordPress admin under Appearance > Themes with your name as the author, built from just two starting files.
  • A blog listing page where you can publish posts and see them rendered with titles, dates, and excerpts using The Loop.
  • A page template that gives static pages like About and Contact their own distinct layout, proving you understand the WordPress template hierarchy.
  • A navigation menu registered and rendered through WordPress's menu system, with all styles loaded the proper WordPress way through functions.php.
  • Secret Mission: Build a custom front page with a hero section and services grid that automatically overrides the default homepage.

Are there any prerequisites?

None. This guide walks you through installing everything you need, including a local WordPress environment. You just need a Windows computer with VS Code installed.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. You're creating a custom WordPress theme entirely from code, giving you a portfolio-ready project that proves to employers you understand WordPress development fundamentals.

Set Up Your Local WordPress Environment

To develop a custom WordPress theme, you need a local WordPress installation running on your machine.

This lets you build and test your theme without paying for web hosting or risking changes on a live site. In this step, you'll install Local (a free tool that creates a complete WordPress environment with one click) and create your first site.

In this step, get ready to:
  • Download and install Local on your Windows machine.
  • Create a new WordPress site called "developer-starter" in Local.
  • Open the site's theme folder in VS Code.
Download and install Local

Local is a free desktop application that installs a complete WordPress server (PHP, MySQL, and all) on your computer. No command line setup needed.

  • Open your browser and navigate to https://localwp.com/releases/.
  • Click the download link for Windows.
  • Once the installer finishes downloading, double-click it to start the installation.

What to expect during installation

Windows Defender may show a pop-up asking you to allow network access. Click Allow access when prompted.

The installer may also ask you to create a free Local account. You can create one or skip this step.

  • Follow the installer prompts and accept the defaults.
  • Once installation completes, Local opens automatically.

You should see the Local application window open with an empty sites list.

Local won't install or open?

Make sure you are running the installer as an admin user. If your Windows username contains spaces or special characters, Local may fail to create sites. The workaround is to create a new Windows user with a simple username (no spaces or special characters).

Also check that no other development tools like MAMP, XAMPP, or Docker are running at the same time.

help me troubleshoot why Local won't install or open on my Windows machine

Create a new WordPress site in Local

Now that Local is running, you'll create a WordPress site that serves as your development playground.

  • In the Local app, click the + button in the bottom-left corner.
  • Select Create a new site and click Continue.
  • Enter developer-starter as the site name, then click Continue.
  • On the environment screen, keep Preferred selected and click Continue.
  • Set a WordPress username (e.g. admin) and a password you'll remember.
  • Leave the email as the default.
  • Click Add Site.

What happens when you click Add Site?

Local downloads WordPress, sets up a PHP server, creates a MySQL database, and configures everything automatically. This may take a minute or two on the first run.

Windows may show additional permission prompts during this process (for updating the hosts file and SSL certificates). Click Yes to allow Local to proceed.

Once provisioning finishes, your site appears in Local's sidebar with a green dot indicating it's running.

Site creation stuck or showing an error?

If the site creation seems frozen, check whether a Windows permission prompt is waiting behind the Local window. Look in your taskbar for a flashing shield icon.

If you see an error about the hosts file, right-click the file at C:\Windows\System32\drivers\etc\hosts, go to Properties, uncheck Read-only, and try again.

help me fix an error when creating a new site in Local on Windows

Open the theme folder in VS Code

Your WordPress site's files live inside Local's folder on your computer. You need to open the themes folder in VS Code so you can start building your custom theme.

  • In the Local app, make sure your developer-starter site is selected in the sidebar.
  • Click the Site folder link near the top of the site details panel (underneath the site name).
  • A File Explorer window opens showing folders named app, conf, and logs.
  • Navigate into app > public > wp-content > themes.

You should see the default WordPress themes listed here (like twentytwentyfive).

  • Switch to VS Code.
  • Click File in the top menu bar.
  • Click Open Folder.
  • In the dialog that appears, navigate to the same themes folder you just found in File Explorer.
  • Select the themes folder and click Select Folder.

Finding the themes folder quickly

Copy the path from the File Explorer address bar while you're in the themes folder. Then paste it directly into the VS Code "Open Folder" dialog's address bar to jump straight there.

In the VS Code Explorer sidebar, you should now see the default theme folders (like twentytwentyfive) listed. This confirms your theme folder is open and ready for development.

Don't see any theme folders in VS Code?

Make sure you opened the themes folder specifically, not the app or public folder. The full path looks like: Local Sites\developer-starter\app\public\wp-content\themes.

help me find my WordPress themes folder inside the Local site files on Windows

Before moving on, confirm your site is actually running by viewing it in the browser.

What do you think you'll see when you open the site? A blank page, or something WordPress provides by default?

  • Back in the Local app, click the Open site button near the top-right of the site panel.

Your browser opens and you see a fully functional WordPress site running the Twenty Twenty-Five theme. It has a header, sample content, and styling already applied.

Why is there already a theme active?

WordPress ships with default themes (named by year, like Twenty Twenty-Five). When you create a new site, WordPress automatically activates the latest default theme so the site has something to display right away.

In the next step, you'll replace this default theme with your own custom one built from scratch.

Your local WordPress environment is ready. Next up, you'll create your own custom theme from just two files and watch WordPress discover it.

Create Your First Theme

Your local WordPress environment is running and ready to go. Now it's time to build something of your own.

WordPress discovers themes by scanning a single folder. If it finds a style.css file with a special header comment inside that folder, it registers the theme. That is literally all it takes for WordPress to say "this is a theme I can use."

In this step, get ready to:
  • Create the developer-starter theme folder.
  • Add style.css and index.php to register and render your theme.
  • Activate the theme in WordPress admin.
Create your theme folder

WordPress needs each theme to live in its own folder inside wp-content/themes. You already have this themes folder open in VS Code from the previous step.

  • In VS Code's Explorer sidebar, right-click inside the themes folder.
  • Select New Folder.
  • Name the folder developer-starter and press Enter.

You should see developer-starter appear alongside the default theme folders (like twentytwentyfive) in the sidebar.

Add the theme files

WordPress only needs two files to recognize a classic theme: style.css (which tells WordPress the theme exists) and index.php (which tells WordPress what to display).

  • Right-click on the developer-starter folder and select New File.
  • Name the file style.css and press Enter.
  • Paste the following theme header comment into style.css:
/*
Theme Name: Developer Starter
Author: Your Name
Description: A custom business theme built from scratch for portfolio demonstration.
Version: 1.0
*/

What does this code do?

This is not regular CSS. It is a special comment block that WordPress reads to register your theme.

  • Theme Name is the only required field. WordPress uses it to identify and list your theme in the admin dashboard.
  • Author, Description, and Version provide metadata that appears on the Themes screen.
  • Save style.css by pressing Ctrl+S.
  • Switch to your browser and open your WordPress admin dashboard.
  • In the left sidebar, click Appearance, then click Themes.

You should see Developer Starter listed among the available themes. WordPress found your folder and read the header from style.css. Your theme has no screenshot yet, so it shows a blank preview.

Don't see Developer Starter listed?

  • Make sure your folder is named exactly developer-starter (lowercase, with a hyphen) and sits directly inside wp-content/themes.
  • Confirm that style.css is saved inside the developer-starter folder, not one level up in themes.
  • Check that the first line of style.css starts with /* and includes Theme Name: with a colon.

Still stuck? Help me figure out why my theme isn't showing up in WordPress.

Now create the file that controls what visitors see. This is where The Loop comes in.

  • Right-click on the developer-starter folder again and select New File.
  • Name the file index.php and press Enter.
  • Paste the following code into index.php:
<?php /* The Loop - fetch and display each post from the database */
if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

    <article>
        <!-- Link each post title to its full page -->
        <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
        <div class="post-meta">
            Posted on <?php echo get_the_date(); ?>
        </div>
        <div class="post-content">
            <!-- Display a short preview of the post -->
            <?php the_excerpt(); ?>
        </div>
    </article>

<?php endwhile; else : ?>
    <!-- Fallback if the database has no posts -->
    <p>No posts found.</p>
<?php endif; ?>

What does this code do?

This is The Loop. It is the core mechanism WordPress uses to pull content from the database and display it on a page.

  • have_posts() checks whether any posts exist. the_post() advances to the next post in the queue.
  • the_permalink() outputs the URL for a single post. the_title() outputs its title.
  • get_the_date() returns when the post was published. the_excerpt() displays a short preview snippet.
  • The else block handles the case where no posts exist at all.
  • Save index.php by pressing Ctrl+S.

✔️ Awesome, I've got everything!

Great. Make sure both style.css and index.php are saved inside your developer-starter folder.

ⓧ I'd like to double check the full code

Here is exactly what each file should contain at this point:

/*
Theme Name: Developer Starter
Author: Your Name
Description: A custom business theme built from scratch for portfolio demonstration.
Version: 1.0
*/
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

    <article>
        <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
        <div class="post-meta">
            Posted on <?php echo get_the_date(); ?>
        </div>
        <div class="post-content">
            <?php the_excerpt(); ?>
        </div>
    </article>

<?php endwhile; else : ?>
    <p>No posts found.</p>
<?php endif; ?>
Activate your theme
  • Switch back to your WordPress admin in the browser.
  • Navigate to Appearance, then Themes (you may already be on this page from earlier).
  • Find the Developer Starter theme card.
  • Hover over it and click Activate.

Before you view the site, predict what you will see. You wrote a loop that displays blog posts with titles, dates, and excerpts. Do you think it will look like a finished website, or more like raw unstyled text?

  • Click Visit Site at the top of the admin dashboard (or open your site URL in the browser).

You should see your blog posts displayed as plain text with linked titles, dates, and excerpts. It looks bare and unstyled. That is expected. Your index.php has no CSS yet.

Now look at the very top of the page. Notice that the WordPress admin toolbar (the dark bar that normally appears along the top edge) is completely missing.

Why is the admin toolbar missing?

WordPress injects the admin toolbar, plugin scripts, and its own styles through two special hooks: wp_head() and wp_footer(). Your index.php does not include these hooks yet.

Without them, WordPress has no place to inject its own code into your pages. This is the gap you will close in the next step by creating proper header.php and footer.php files.

Seeing a white screen or PHP error?

  • Check that index.php starts with <?php on the very first line with no spaces or blank lines before it.
  • Make sure you copied the entire code block, including the closing <?php endif; ?> at the bottom.
  • Confirm that the Developer Starter theme shows as Active on the Appearance > Themes page.

Still stuck? Help me debug a white screen after activating my custom WordPress theme.

Your custom theme is alive. You built it from just two files and it is already rendering dynamic content from the database. Next up, you will add proper WordPress structure so the admin toolbar reappears and your theme can tap into the full WordPress ecosystem.

Add Proper WordPress Structure

In the last step, you activated your custom theme and saw blog posts render on the page. But something was missing. The WordPress admin toolbar that normally sits at the top of every page was nowhere to be seen.

That missing toolbar reveals a real problem. Without wp_head() and wp_footer(), WordPress cannot inject its own scripts, the admin bar, or plugin functionality into your pages. Your theme is completely isolated from the WordPress ecosystem. In this step, you will give WordPress the hooks it needs by splitting your theme into proper template partials.

In this step, get ready to:
  • Create header.php and footer.php template partials with the required WordPress hooks.
  • Create functions.php to properly enqueue your stylesheet and register a navigation menu.
  • Update index.php to use the new template partials and restore the admin toolbar.
Create the header and footer templates

WordPress themes split reusable page structure into template partials. The header.php file holds everything from the opening <!DOCTYPE html> down to the start of page content. The footer.php file closes the page and provides the final hook WordPress needs.

  • In VS Code, create a new file called header.php inside your developer-starter theme folder (the same folder that contains style.css and index.php).
  • Paste the following code into 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">
    <!-- WordPress injects its styles, scripts, and admin bar here -->
    <?php wp_head(); ?>
</head>
<body <?php body_class(); ?>>

<header class="site-header">
    <div class="site-branding">
        <!-- Link the site name back to the homepage -->
        <h1><a href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php bloginfo( 'name' ); ?></a></h1>
        <p><?php bloginfo( 'description' ); ?></p>
    </div>

    <!-- Only show navigation if a menu is assigned to the primary location -->
    <?php if ( has_nav_menu( 'primary' ) ) : ?>
        <nav class="main-navigation">
            <?php wp_nav_menu( array( 'theme_location' => 'primary', 'container' => false ) ); ?>
        </nav>
    <?php endif; ?>
</header>

What does this code do?

This file sets up the full HTML document structure that every page on your site shares.

  • wp_head() is the critical hook. It lets WordPress inject stylesheets, scripts, meta tags, and the admin toolbar into your page's <head>.
  • language_attributes() and body_class() output dynamic HTML attributes that WordPress and plugins rely on.
  • The has_nav_menu('primary') check only renders navigation if you assign a menu later, preventing empty markup.
  • Save header.php.
  • Create another new file called footer.php in the same developer-starter theme folder.
  • Paste the following code into footer.php:
<footer class="site-footer">
    <p>&copy; <?php echo date( 'Y' ); ?> <?php bloginfo( 'name' ); ?>. All rights reserved.</p>
</footer>

<!-- WordPress injects scripts and renders the admin bar here -->
<?php wp_footer(); ?>
</body>
</html>

What does this code do?

wp_footer() is the partner hook to wp_head(). WordPress renders the admin toolbar via wp_footer() as a fallback, and plugins load their JavaScript here. Without it, interactive features break silently.

date( 'Y' ) outputs the current year dynamically, so your copyright notice stays up to date automatically.

  • Save footer.php.
  • Confirm that header.php and footer.php both appear in the VS Code Explorer sidebar alongside style.css and index.php.

Don't see the new files in the sidebar?

  • Make sure you created the files inside the developer-starter folder (not one level above in themes).
  • Check for typos in the filename. WordPress expects exactly header.php and footer.php (lowercase, no spaces).

Why aren't my new PHP files showing in my theme folder?

Register theme functions and connect the templates

WordPress themes use a special file called functions.php to register features with WordPress core. This is where you tell WordPress how to load your stylesheet and what menu locations your theme supports.

  • Create a new file called functions.php in the developer-starter theme folder.
  • Paste the following code into functions.php:
<?php

// Properly load the theme stylesheet the WordPress way
function developer_starter_enqueue_styles() {
    wp_enqueue_style( 'developer-starter-style', get_stylesheet_uri() );
}
add_action( 'wp_enqueue_scripts', 'developer_starter_enqueue_styles' );

// Register a navigation menu location for the theme
function developer_starter_setup() {
    register_nav_menus( array(
        'primary' => 'Primary Menu',
    ) );
}
add_action( 'after_setup_theme', 'developer_starter_setup' );

What does this code do?

  • wp_enqueue_style() tells WordPress to load your style.css file through its asset pipeline instead of hardcoding a <link> tag. This lets WordPress manage load order and avoid conflicts with plugins.
  • register_nav_menus() creates a menu location called primary that appears in the WordPress admin under Appearance. You will assign a menu to this location in a later step.
  • add_action() hooks your functions into specific WordPress events. wp_enqueue_scripts fires when the page loads assets. after_setup_theme fires when the theme initializes.
  • Save functions.php.

Why enqueue instead of a link tag?

Hardcoding a <link> tag in your HTML works, but it bypasses WordPress entirely. When you use wp_enqueue_style(), WordPress can manage dependencies, prevent duplicate loads, and let plugins add their own styles without conflicts. This is considered the correct approach for any production theme.

Now you need to tell index.php to use your new header and footer partials instead of rendering everything itself.

  • Open index.php in VS Code.
  • Select all the existing code and replace it with the following:
<?php get_header(); ?>

<main class="site-content">
    <?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

        <!-- Each post gets WordPress CSS classes via post_class() -->
        <article <?php post_class(); ?>>
            <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
            <div class="post-meta">
                Posted on <?php echo get_the_date(); ?>
            </div>
            <div class="post-content">
                <?php the_excerpt(); ?>
            </div>
        </article>

    <?php endwhile; else : ?>
        <p>No posts found.</p>
    <?php endif; ?>
</main>

<?php get_footer(); ?>

What changed here?

  • get_header() pulls in your header.php file (which includes wp_head() and the full HTML document opening).
  • get_footer() pulls in your footer.php file (which includes wp_footer() and closes the HTML document).
  • post_class() adds dynamic CSS classes to each article element, which WordPress and plugins use for styling and targeting.
  • The content is now wrapped in a <main class="site-content"> element for better structure and styling control.
  • Save index.php.

Before you refresh the browser, think about what you expect to see. Will the admin toolbar appear at the top of the page now that wp_head() and wp_footer() are in place?

  • Switch to your browser and refresh your local WordPress site.

The WordPress admin toolbar now appears as a dark bar at the top of the page. This confirms that WordPress can inject its scripts and UI into your theme through the hooks you added.

Admin toolbar still missing?

  • Make sure you are logged into WordPress. The admin toolbar only shows for logged-in users.
  • Check that header.php contains <?php wp_head(); ?> before the closing </head> tag.
  • Check that footer.php contains <?php wp_footer(); ?> before the closing </body> tag.
  • Verify that index.php starts with <?php get_header(); ?> and ends with <?php get_footer(); ?>.

My WordPress admin toolbar is not appearing even after adding wp_head and wp_footer. Help me debug.

✔️ Awesome, I've got everything!

Great. Double check you have saved all four files: header.php, footer.php, functions.php, and index.php.

ⓧ I'd like to double check the full code

Here is each file in full. Compare yours against these:

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(); ?>>

<header class="site-header">
    <div class="site-branding">
        <h1><a href="<?php echo esc_url( home_url( '/' ) ); ?>"><?php bloginfo( 'name' ); ?></a></h1>
        <p><?php bloginfo( 'description' ); ?></p>
    </div>

    <?php if ( has_nav_menu( 'primary' ) ) : ?>
        <nav class="main-navigation">
            <?php wp_nav_menu( array( 'theme_location' => 'primary', 'container' => false ) ); ?>
        </nav>
    <?php endif; ?>
</header>

footer.php:

<footer class="site-footer">
    <p>&copy; <?php echo date( 'Y' ); ?> <?php bloginfo( 'name' ); ?>. All rights reserved.</p>
</footer>

<?php wp_footer(); ?>
</body>
</html>

functions.php:

<?php

function developer_starter_enqueue_styles() {
    wp_enqueue_style( 'developer-starter-style', get_stylesheet_uri() );
}
add_action( 'wp_enqueue_scripts', 'developer_starter_enqueue_styles' );

function developer_starter_setup() {
    register_nav_menus( array(
        'primary' => 'Primary Menu',
    ) );
}
add_action( 'after_setup_theme', 'developer_starter_setup' );

index.php:

<?php get_header(); ?>

<main class="site-content">
    <?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

        <article <?php post_class(); ?>>
            <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
            <div class="post-meta">
                Posted on <?php echo get_the_date(); ?>
            </div>
            <div class="post-content">
                <?php the_excerpt(); ?>
            </div>
        </article>

    <?php endwhile; else : ?>
        <p>No posts found.</p>
    <?php endif; ?>
</main>

<?php get_footer(); ?>

Your theme now has proper WordPress structure. WordPress can inject scripts, load the admin bar, and communicate with plugins through your template hooks. Next up, you will add CSS styling to transform this from raw HTML into a professional-looking business website.

Style Your Business Theme

Your theme structure is complete and WordPress recognizes it. The admin toolbar appears, styles are enqueued, and your PHP templates are wired up correctly.

The problem is that your site still looks like raw, unstyled HTML. In this step, you will add professional CSS, create real content to display, and wire up a navigation menu so the result looks like a polished business website.

In this step, get ready to:
  • Add comprehensive CSS for the header, navigation, blog posts, and footer.
  • Create sample blog posts and an About page in the WordPress admin.
  • Set up a navigation menu and assign it to the Primary Menu location.
Add CSS styles to your theme

Your style.css currently only has the theme header comment. You will build up the stylesheet in sections, refreshing the browser between groups so you can see each layer of styling take effect.

  • In VS Code, open style.css.
  • Below the closing */ of the theme header comment, paste the following reset and base styles:
/* Reset and Base */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: #333;
}

a {
    color: #0073aa;
    text-decoration: none;
}

a:hover {
    color: #005177;
}

What does this code do?

The universal selector * removes default browser margins and padding so every element starts from zero. box-sizing: border-box makes width calculations include padding and borders, which prevents layout surprises.

The body rule sets a system font stack that looks native on every operating system. Link colors use WordPress's signature blue.

  • Directly below the reset styles, paste the header and branding styles:
/* Header */
.site-header {
    background: #1e293b;
    color: #fff;
    padding: 1rem 2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.site-branding h1 {
    font-size: 1.5rem;
    margin: 0;
}

.site-branding h1 a {
    color: #fff;
    text-decoration: none;
}

.site-branding p {
    color: #94a3b8;
    font-size: 0.9rem;
    margin: 0;
}

What does this code do?

The .site-header gets a dark slate background (#1e293b) and uses flexbox to push the site title to the left and navigation to the right. The branding styles make the site title white and the tagline a muted grey.

  • Save style.css and refresh your site in the browser.

You should see a dark header bar across the top of the page with your site title in white. The body text now uses clean system fonts instead of browser defaults.

Still seeing unstyled text?

Make sure you saved style.css in VS Code. Try a hard refresh with Ctrl+Shift+R to clear cached styles.

help me debug why my CSS changes are not showing up in the browser

  • Back in style.css, paste the navigation and main content styles below the header section:
/* Navigation */
.main-navigation ul {
    list-style: none;
    display: flex;
    gap: 1.5rem;
    margin: 0;
    padding: 0;
}

.main-navigation a {
    color: #e2e8f0;
    text-decoration: none;
    font-weight: 500;
}

.main-navigation a:hover {
    color: #fff;
}

/* Main Content */
.site-content {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 2rem;
}

What does this code do?

The navigation styles display menu links in a horizontal row using flexbox with even spacing. The .site-content wrapper centers blog content at 800px wide with auto margins, giving your posts a comfortable reading width.

  • Continue in style.css and paste the blog post styles below the main content section:
/* Blog Posts */
.post {
    margin-bottom: 3rem;
    padding-bottom: 2rem;
    border-bottom: 1px solid #e2e8f0;
}

.post:last-child {
    border-bottom: none;
}

.post h2 {
    font-size: 1.75rem;
    margin-bottom: 0.5rem;
}

.post h2 a {
    color: #1e293b;
}

.post h2 a:hover {
    color: #0073aa;
}

.post-meta {
    color: #64748b;
    font-size: 0.875rem;
    margin-bottom: 1rem;
}

What does this code do?

Each .post gets generous bottom spacing and a subtle border to separate entries visually. The :last-child selector removes the border from the final post so there is no trailing line. Post titles use dark navy and switch to WordPress blue on hover.

  • Save style.css and refresh the browser.

Your blog posts are now centered on the page with clear spacing between them. Each post title is large and dark, with a subtle separator line underneath.

  • Finally, paste the page template and footer styles at the bottom of style.css:
/* Page Template */
.page-content {
    max-width: 800px;
    margin: 2rem auto;
    padding: 0 2rem;
}

.page-content h1 {
    font-size: 2.5rem;
    margin-bottom: 1rem;
    color: #1e293b;
}

.page-content .entry-content p {
    margin-bottom: 1rem;
    font-size: 1.1rem;
}

/* Footer */
.site-footer {
    background: #1e293b;
    color: #94a3b8;
    text-align: center;
    padding: 2rem;
    margin-top: 3rem;
}

.site-footer p {
    margin: 0;
}

What does this code do?

The .page-content styles will apply to static pages once you create a page.php template in the next step. The footer matches the dark header with centered muted text, giving the site a consistent top-to-bottom frame.

  • Save style.css and refresh the browser.

You should now see a dark footer at the bottom of the page with your site name and a copyright notice in muted grey text.

Footer not appearing dark?

Check that the class site-footer in your CSS matches class="site-footer" in footer.php. Class names are case-sensitive.

help me figure out why my footer styles are not applying

✔️ Awesome, I've got everything!

Great. Make sure style.css is saved before moving on.

ⓧ I'd like to double check the full code

Your style.css should contain these sections in order. Scroll through your file and confirm each is present:

  • Theme header comment (Theme Name: Developer Starter)
  • Reset and Base (universal selector, body font, link colors)
  • Header (.site-header dark background, flexbox layout, branding styles)
  • Navigation (.main-navigation horizontal list, link colors)
  • Main Content (.site-content centered at 800px)
  • Blog Posts (.post spacing, borders, heading colors, .post-meta muted text)
  • Page Template (.page-content heading and paragraph styles)
  • Footer (.site-footer dark background, centered text)
Create sample content in WordPress

Your CSS is ready, but the default WordPress install only ships with one sample post and no pages. Adding real content lets you see how your styles handle multiple posts and different content types.

  • In your browser, go to the WordPress admin dashboard at your site's URL followed by /wp-admin.
  • Click Posts in the left sidebar, then click Add New Post.
  • Enter a title such as Getting Started with WordPress Development.
  • Add a paragraph of text in the content area. Any placeholder text works.
  • Click Publish, then confirm by clicking Publish again in the confirmation panel.

Why two publish clicks?

WordPress shows a confirmation panel to prevent accidental publishing. The first click opens the panel with scheduling and visibility options. The second click actually publishes the post.

  • Repeat the process to create a second post with a title such as Building Custom Themes from Scratch.
  • Now click Pages in the left sidebar, then click Add New Page.
  • Enter the title About and add a paragraph describing your business.
  • Click Publish and confirm.
  • Visit your site's front page in the browser.

You should see your two new posts listed alongside the default "Hello world!" post, each with a title, date, and excerpt separated by subtle border lines.

Set up a navigation menu

Your header.php already checks for a menu assigned to the primary location. Until you create and assign a menu in the admin, the navigation section stays hidden. Creating the menu makes it appear.

  • In the WordPress admin, click Appearance in the left sidebar.
  • Click Menus.
  • In the Menu Name field, type Main Navigation.
  • Click Create Menu.
  • On the left side of the page, find the Pages panel.
  • Check the box next to About and Sample Page (or any pages listed).
  • Click Add to Menu.
  • Scroll down to the Menu Settings section at the bottom of the page.
  • Check the box next to Primary Menu under Display location.
  • Click Save Menu.

What is a menu location?

A menu location is a slot your theme registers in functions.php using register_nav_menus(). The primary location you registered earlier tells WordPress where in your templates the menu should appear. Assigning a menu to that location connects admin-created links to your header.php output.

Before you refresh your site, what do you think the header will look like now that a menu is assigned to the Primary Menu location?

  • Visit your site's front page in the browser and refresh.

You should see a professional-looking business website with a dark header, your site title on the left, styled navigation links on the right, and blog posts with proper typography and spacing below.

Navigation links not appearing?

Make sure you checked the Primary Menu box under Display location and clicked Save Menu. Without that assignment, the has_nav_menu('primary') check in header.php returns false and hides the navigation entirely.

help me troubleshoot why my WordPress navigation menu is not showing up

Your theme now looks like a real business website. Next up, you will create a dedicated page template so static pages like About render with their own clean layout instead of the blog post format.

Create a Page Template

Your business theme is looking polished with a dark header, styled navigation, and properly formatted blog posts.

In this step, you'll discover how WordPress decides which template file to use for different types of content. You'll create a template specifically for pages.

In this step, get ready to:
  • Discover why your About page currently looks wrong.
  • Create a dedicated page.php template with a clean layout for static content.
  • Observe how WordPress automatically picks the right template for each content type.
Spot the problem on your About page

Your About page is a static page, not a blog post. Before you make any changes, think about what you'd expect a standalone page to look like. Should it display a date? An excerpt? A clickable title linking to itself?

  • In your browser, click About in your navigation menu to open your About page.
  • Look at how the page is rendered. Notice the "Posted on" date, the linked title, and the excerpt-style content.

Your About page looks exactly like a blog listing entry. That's not right for a standalone page.

Why does the About page look like a blog post?

WordPress uses a template hierarchy to decide which PHP file renders each piece of content. When it can't find a specific template for pages, it falls back to index.php as the universal default.

Right now your theme only has index.php, which is designed for blog listings. It shows dates, excerpts, and clickable titles. Those make sense for posts, but not for a static page like About.

Create page.php for static content

When WordPress finds a file called page.php in your theme folder, it automatically uses that template for all static pages instead of falling back to index.php.

  • In VS Code, create a new file called page.php in your developer-starter theme folder (the same folder as index.php).
  • Paste the following code into page.php:
<?php get_header(); ?>

<main class="page-content">
    <?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

        <!-- Display the page title as a standalone heading -->
        <h1><?php the_title(); ?></h1>
        <!-- Show the full page content, no excerpt or date -->
        <div class="entry-content">
            <?php the_content(); ?>
        </div>

    <?php endwhile; endif; ?>
</main>

<?php get_footer(); ?>

What does this code do?

  • get_header() and get_footer() pull in your existing header and footer, keeping the layout consistent across your site.
  • the_title() outputs the page title as a plain <h1> heading. No link wrapping it, because you're already on the page.
  • the_content() displays the full page content instead of a shortened excerpt.
  • The wrapper uses .page-content instead of .site-content, which targets the page-specific CSS you already have in style.css.

What's missing compared to index.php?

Notice what you deliberately left OUT. There's no get_the_date(), no the_excerpt(), and no link on the title. Pages are standalone content, so they don't need blog-post metadata.

  • Save page.php.
  • Switch to your browser and refresh your About page.

Before you look at the result, what do you expect to see? Will the About page still show a date and excerpt, or has something changed?

Your About page now renders with a large heading and the full content below it. No date, no excerpt, no clickable title. WordPress detected page.php and automatically started using it for pages instead of index.php.

About page still looks like a blog post?

  • Make sure you saved page.php in the correct folder. It must be in the same developer-starter theme folder as index.php.
  • Confirm the file is named exactly page.php (not pages.php or Page.php). WordPress matches template filenames exactly.
  • Try a hard refresh in your browser with Ctrl+Shift+R to clear any cached version of the page.

Still not working? Help me figure out why my page.php template isn't being used by WordPress.

Compare the two templates in action

You now have two templates working side by side. WordPress picks the right one automatically based on the content type.

  • Click your site title in the header to return to the homepage (your blog listing).
  • Notice how the blog listing still uses index.php. Posts display with linked titles, dates, and excerpts.
  • Click About in your navigation menu again.
  • Compare the two layouts. The About page uses page.php with a full-width heading, full content, and no date or blog styling.

What is the WordPress template hierarchy?

WordPress checks for template files in a specific order of priority. For pages, it looks for page.php first. If that doesn't exist, it falls back to index.php.

This hierarchy lets you control how different content types look without writing any conditional logic. You add a file with the right name, and WordPress uses it automatically.

✔️ Awesome, I've got everything!

Great. Double check you've saved page.php and confirmed the About page displays with the clean layout.

ⓧ I'd like to double check the full code

Here is the complete page.php file:

<?php get_header(); ?>

<main class="page-content">
    <?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>

        <h1><?php the_title(); ?></h1>
        <div class="entry-content">
            <?php the_content(); ?>
        </div>

    <?php endwhile; endif; ?>
</main>

<?php get_footer(); ?>

Your theme now has two distinct templates working together. WordPress handles the routing between them automatically, with no configuration needed from you. That's the power of the template hierarchy.

Secret mission

Build a Custom Front Page

Your homepage still shows the blog listing. What if you could override it with a marketing-style landing page featuring a hero section and services grid, without changing any WordPress settings? That is exactly what the template hierarchy lets you do.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine through Local. There are no cloud resources or ongoing costs to worry about.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The developer-starter WordPress site in Local (including the database, theme files, and sample content).
  • The Local application installed on your Windows machine.

Keep everything running

No action needed. Choose this if you are still actively building on your theme or plan to continue developing it soon.

  • Your developer-starter site stays in Local and you can reopen it anytime.
  • Local stays installed and ready for future WordPress development projects.

Pause - I'll come back to this later

Shut down running processes to free up system resources. Your site data is preserved and you can pick up where you left off later.

  • In Local, right-click your developer-starter site and select Stop site.
  • Close the Local application.
  • When you want to resume, reopen Local and click Start site on your developer-starter site.

Delete - I don't want to use this again

Remove all project resources from your machine. Follow these steps in order.

  • In Local, right-click your developer-starter site and select Delete. This removes all site files and the database.
  • To uninstall Local itself, open Windows Settings, then navigate to the Apps & features section.
  • Find Local in the list, click it, and select Uninstall.
  • To remove leftover configuration data, delete the hidden folder at AppData > Roaming > Local inside your user directory.

Nice Work!

Nice Work!

You just built a custom WordPress theme from scratch using nothing but code. No page builders, no drag-and-drop, no shortcuts.

You've learned how to:

  • Built a complete WordPress theme from blank files, with PHP templates that render dynamic content from the database using The Loop.
  • Used the WordPress template hierarchy to serve different layouts for blog listings, static pages, and the homepage without writing a single routing rule.
  • Enqueued styles and registered navigation menus through functions.php the proper WordPress way, keeping your theme compatible with the entire plugin ecosystem.
  • Secret Mission: Built a custom front-page.php template with a hero section and services grid that automatically overrides the default homepage, demonstrating template hierarchy priority.

Ready to quiz yourself?