Theme Anatomy
A theme is a folder with a style.css header; classic themes also need index.php, block themes templates/index.html.
wp-content/themes/{slug}/style.css + index.php (classic) or templates/index.html (block)A theme is a directory under wp-content/themes/. WordPress discovers it by scanning each directory for a style.css whose first comment block is a theme header. There is no registration step: the filesystem is the registry, and the active theme is recorded in the template and stylesheet options.
The minimum valid theme
A classic theme needs two files: style.css carrying the header, and index.php, the final fallback of the template hierarchy. A block theme needs style.css and templates/index.html; the presence of that HTML template is what makes WordPress treat the theme as a block theme.
The style.css header
Only Theme Name is required. WordPress also reads Theme URI, Author, Author URI, Description, Version, Requires at least, Tested up to, Requires PHP, License, License URI, Tags, Text Domain, Domain Path, and Template (child themes only).
What trips people up
The header must be the first comment in style.css in the theme root, not in a subfolder and not in functions.php. Naming the file in the header does not load it — WordPress never enqueues your stylesheet for you. Everything else (functions.php, header.php, footer.php, screenshot.png) is optional, and template files are found by the template hierarchy rather than by being listed anywhere.
Example
<?php
/**
* index.php — the fallback template. Required in a classic theme because it is
* the last entry in the template hierarchy: if nothing more specific matches,
* WordPress lands here.
*
* @package MyTheme
*/
get_header(); // loads header.php
<main id="primary">
if ( have_posts() ) :
while ( have_posts() ) : the_post();
<article post_class(); >
<h2>
<a href=" the_permalink(); "> the_title(); </a>
</h2>
the_excerpt();
</article>
endwhile;
the_posts_pagination();
else :
<p> esc_html_e( 'Nothing found.', 'mytheme' ); </p>
endif;
</main>
get_footer(); // loads footer.phpWhen to use it
- Scaffolding a new classic theme from scratch instead of forking a starter
- Working out why a theme folder does not appear under Appearance → Themes
- Deciding whether an existing theme is classic or block-based before editing it
- Auditing a client theme for a missing wp_head() or wp_footer() call
More examples
The style.css header block
Only Theme Name is mandatory; everything else is metadata shown in Appearance → Themes or used by the theme directory. Text Domain must match the string you pass to __() and esc_html_e() or translations silently do nothing. Requires PHP is enforced — WordPress refuses to activate a theme on an older PHP.
/*
Theme Name: My Theme
Theme URI: https://example.com/my-theme
Author: A Developer
Author URI: https://example.com
Description: A classic theme.
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.8
Requires PHP: 8.2
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: mytheme
Tags: blog, one-column
*/
/* Real CSS starts here. */header.php: wp_head() is not optional
Without wp_head() in header.php and wp_footer() in footer.php, nothing you enqueue reaches the browser and most plugins break. This is the first thing to check when a theme 'ignores' wp_enqueue_style().
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php wp_head(); // prints every enqueued style/script and plugin output ?>
</head>
<body <?php body_class(); ?>>
<?php wp_body_open(); // WP 5.2+, the hook plugins use to inject after <body> ?>Template tag shorthand and its longhand
Every the_x() template tag has a get_the_x() counterpart that returns instead of printing. Reach for the get_ form whenever you need to escape, modify, or test the value before output — the_title() gives you no chance to escape it.
<?php
// These pairs are the same thing. the_* echoes, get_the_* returns.
the_title(); // shorthand
echo get_the_title(); // longhand
the_permalink();
echo esc_url( get_the_permalink() );
// Templates conventionally use PHP's alternative syntax so the HTML stays readable.
if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<h2><?php the_title(); ?></h2>
<?php endwhile; endif;
// Identical in behaviour, braces form:
if ( have_posts() ) { while ( have_posts() ) { the_post();
echo '<h2>' . esc_html( get_the_title() ) . '</h2>';
} }
Discussion