---
name: wp-theme-development
description: WordPress theme development best practices and standards. Use when building new themes, creating custom templates, implementing theme features, working with template hierarchy, customizer options, or FSE block themes, or when user mentions "theme development", "child theme", "template hierarchy", "theme.json", "customizer", "template parts", "block theme", "classic theme", or "theme standards".
---
# WordPress Theme Development Skill
## Overview
Comprehensive guide for developing WordPress themes following best practices. Covers both classic PHP-based themes and modern FSE (Full Site Editing) block themes. **Core principle:** Build accessible, performant, and WordPress-standards-compliant themes.
## When to Use
**Use when:**
- Building new WordPress themes (classic or block)
- Creating child themes
- Implementing template hierarchy
- Adding customizer options
- Setting up FSE templates and template parts
- Troubleshooting theme issues
**Don't use for:**
- Plugin development (use wp-plugin-development)
- Block development specifically (use wp-gutenberg-blocks)
- Security auditing (use wp-security-review)
## Theme Types Overview
| Type | Description | Key Files |
|------|-------------|-----------|
| **Classic Theme** | PHP templates, customizer | style.css, functions.php, templates/*.php |
| **Block Theme** | FSE, theme.json based | style.css, theme.json, templates/*.html, parts/*.html |
| **Hybrid Theme** | Mix of both approaches | All of the above |
| **Child Theme** | Extends parent theme | style.css, functions.php |
## Classic Theme Structure
```
theme-name/
├── assets/
│ ├── css/
│ │ ├── style.css # Compiled styles
│ │ └── editor-style.css # Editor styles
│ ├── js/
│ │ ├── main.js # Main scripts
│ │ └── navigation.js # Nav scripts
│ ├── images/
│ └── fonts/
├── inc/
│ ├── customizer.php # Customizer settings
│ ├── template-functions.php # Template helpers
│ ├── template-tags.php # Template tags
│ ├── theme-setup.php # Theme setup
│ └── walker-nav.php # Custom walkers
├── template-parts/
│ ├── header/
│ │ ├── site-branding.php
│ │ └── site-navigation.php
│ ├── footer/
│ │ ├── footer-widgets.php
│ │ └── site-info.php
│ ├── content/
│ │ ├── content.php
│ │ ├── content-page.php
│ │ ├── content-single.php
│ │ └── content-none.php
│ └── components/
│ ├── post-meta.php
│ └── pagination.php
├── languages/
│ └── theme-name.pot
├── 404.php
├── archive.php
├── comments.php
├── footer.php
├── front-page.php
├── functions.php
├── header.php
├── index.php
├── page.php
├── screenshot.png # 1200x900px
├── search.php
├── sidebar.php
├── single.php
├── style.css
└── readme.txt
```
## Block Theme Structure
```
theme-name/
├── assets/
│ ├── css/
│ │ └── custom.css
│ ├── js/
│ └── fonts/
├── parts/
│ ├── header.html
│ ├── footer.html
│ └── sidebar.html
├── patterns/
│ ├── hero.php
│ ├── featured-posts.php
│ └── call-to-action.php
├── styles/
│ ├── blue.json # Color variations
│ └── dark.json
├── templates/
│ ├── 404.html
│ ├── archive.html
│ ├── front-page.html
│ ├── home.html
│ ├── index.html
│ ├── page.html
│ ├── search.html
│ └── single.html
├── functions.php
├── screenshot.png
├── style.css
├── theme.json
└── readme.txt
```
## Required Files
### style.css Header
```css
/*
Theme Name: Theme Name
Theme URI: https://example.com/theme
Author: Your Agency
Author URI: https://youragency.com
Description: A custom WordPress theme with modern features.
Version: 1.0.0
Requires at least: 6.0
Tested up to: 6.5
Requires PHP: 8.0
License: GPL v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: theme-name
Tags: block-patterns, block-styles, custom-colors, custom-logo, editor-style, full-site-editing, wide-blocks
Theme Name is based on starter theme components.
*/
```
### functions.php Setup
```php
esc_html__( 'Primary Menu', 'theme-name' ),
'footer' => esc_html__( 'Footer Menu', 'theme-name' ),
'social' => esc_html__( 'Social Menu', 'theme-name' ),
)
);
// HTML5 markup support.
add_theme_support(
'html5',
array(
'search-form',
'comment-form',
'comment-list',
'gallery',
'caption',
'style',
'script',
'navigation-widgets',
)
);
// Custom logo support.
add_theme_support(
'custom-logo',
array(
'height' => 100,
'width' => 400,
'flex-width' => true,
'flex-height' => true,
)
);
// Custom background support.
add_theme_support(
'custom-background',
array(
'default-color' => 'ffffff',
'default-image' => '',
)
);
// Block editor support.
add_theme_support( 'wp-block-styles' );
add_theme_support( 'align-wide' );
add_theme_support( 'responsive-embeds' );
// Editor color palette.
add_theme_support(
'editor-color-palette',
array(
array(
'name' => esc_html__( 'Primary', 'theme-name' ),
'slug' => 'primary',
'color' => '#0066cc',
),
array(
'name' => esc_html__( 'Secondary', 'theme-name' ),
'slug' => 'secondary',
'color' => '#6c757d',
),
array(
'name' => esc_html__( 'Dark', 'theme-name' ),
'slug' => 'dark',
'color' => '#212529',
),
array(
'name' => esc_html__( 'Light', 'theme-name' ),
'slug' => 'light',
'color' => '#f8f9fa',
),
)
);
// Editor font sizes.
add_theme_support(
'editor-font-sizes',
array(
array(
'name' => esc_html__( 'Small', 'theme-name' ),
'slug' => 'small',
'size' => 14,
),
array(
'name' => esc_html__( 'Normal', 'theme-name' ),
'slug' => 'normal',
'size' => 16,
),
array(
'name' => esc_html__( 'Large', 'theme-name' ),
'slug' => 'large',
'size' => 20,
),
array(
'name' => esc_html__( 'Huge', 'theme-name' ),
'slug' => 'huge',
'size' => 32,
),
)
);
}
add_action( 'after_setup_theme', 'theme_name_setup' );
/**
* Set content width.
*/
function theme_name_content_width() {
$GLOBALS['content_width'] = apply_filters( 'theme_name_content_width', 1200 );
}
add_action( 'after_setup_theme', 'theme_name_content_width', 0 );
/**
* Register widget areas.
*/
function theme_name_widgets_init() {
register_sidebar(
array(
'name' => esc_html__( 'Sidebar', 'theme-name' ),
'id' => 'sidebar-1',
'description' => esc_html__( 'Add widgets here.', 'theme-name' ),
'before_widget' => '',
'before_title' => '
',
)
);
register_sidebar(
array(
'name' => esc_html__( 'Footer', 'theme-name' ),
'id' => 'sidebar-footer',
'description' => esc_html__( 'Footer widget area.', 'theme-name' ),
'before_widget' => '',
'after_widget' => '
',
'before_title' => '',
)
);
}
add_action( 'widgets_init', 'theme_name_widgets_init' );
/**
* Enqueue scripts and styles.
*/
function theme_name_scripts() {
// Main stylesheet.
wp_enqueue_style(
'theme-name-style',
get_stylesheet_uri(),
array(),
THEME_NAME_VERSION
);
// Additional styles.
wp_enqueue_style(
'theme-name-main',
get_template_directory_uri() . '/assets/css/main.css',
array( 'theme-name-style' ),
THEME_NAME_VERSION
);
// Main script.
wp_enqueue_script(
'theme-name-script',
get_template_directory_uri() . '/assets/js/main.js',
array(),
THEME_NAME_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
// Navigation script.
wp_enqueue_script(
'theme-name-navigation',
get_template_directory_uri() . '/assets/js/navigation.js',
array(),
THEME_NAME_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
// Localize script data.
wp_localize_script(
'theme-name-script',
'themeNameData',
array(
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'theme_name_nonce' ),
)
);
// Comment reply script.
if ( is_singular() && comments_open() && get_option( 'thread_comments' ) ) {
wp_enqueue_script( 'comment-reply' );
}
}
add_action( 'wp_enqueue_scripts', 'theme_name_scripts' );
/**
* Enqueue editor styles.
*/
function theme_name_editor_styles() {
add_editor_style(
array(
'assets/css/editor-style.css',
)
);
}
add_action( 'after_setup_theme', 'theme_name_editor_styles' );
/**
* Include required files.
*/
require get_template_directory() . '/inc/template-tags.php';
require get_template_directory() . '/inc/template-functions.php';
require get_template_directory() . '/inc/customizer.php';
```
## Template Hierarchy
### Template Selection Priority
```
# Single Post
single-{post-type}-{slug}.php
single-{post-type}.php
single.php
singular.php
index.php
# Page
page-{slug}.php
page-{id}.php
page.php
singular.php
index.php
# Category Archive
category-{slug}.php
category-{id}.php
category.php
archive.php
index.php
# Custom Post Type Archive
archive-{post-type}.php
archive.php
index.php
# Taxonomy Archive
taxonomy-{taxonomy}-{term}.php
taxonomy-{taxonomy}.php
taxonomy.php
archive.php
index.php
# Author Archive
author-{nicename}.php
author-{id}.php
author.php
archive.php
index.php
# Date Archive
date.php
archive.php
index.php
# Search Results
search.php
index.php
# 404 Error
404.php
index.php
# Front Page
front-page.php
home.php (if showing posts)
page.php (if showing page)
index.php
# Blog Home
home.php
index.php
```
### Template Parts Usage
```php
>
'
' . esc_html__( 'Pages:', 'theme-name' ),
'after' => '
',
)
);
?>
```
## Template Tags
### Custom Template Tags (inc/template-tags.php)
```php
%2$s';
if ( get_the_time( 'U' ) !== get_the_modified_time( 'U' ) ) {
$time_string = '';
}
$time_string = sprintf(
$time_string,
esc_attr( get_the_date( DATE_W3C ) ),
esc_html( get_the_date() ),
esc_attr( get_the_modified_date( DATE_W3C ) ),
esc_html( get_the_modified_date() )
);
printf(
'%s',
'' . $time_string . ''
);
}
endif;
if ( ! function_exists( 'theme_name_posted_by' ) ) :
/**
* Prints HTML with meta information for the current author.
*/
function theme_name_posted_by() {
printf(
'%s',
'' . esc_html( get_the_author() ) . ''
);
}
endif;
if ( ! function_exists( 'theme_name_post_thumbnail' ) ) :
/**
* Displays an optional post thumbnail.
*/
function theme_name_post_thumbnail() {
if ( post_password_required() || is_attachment() || ! has_post_thumbnail() ) {
return;
}
if ( is_singular() ) :
?>
the_title_attribute( array( 'echo' => false ) ),
)
);
?>
%s%s',
esc_html__( 'Posted in ', 'theme-name' ),
$categories_list
);
}
$tags_list = get_the_tag_list( '', esc_html__( ', ', 'theme-name' ) );
if ( $tags_list ) {
printf(
'%s%s',
esc_html__( 'Tagged ', 'theme-name' ),
$tags_list
);
}
}
if ( ! is_single() && ! post_password_required() && ( comments_open() || get_comments_number() ) ) {
echo '';
}
edit_post_link(
sprintf(
/* translators: %s: Name of current post */
esc_html__( 'Edit %s', 'theme-name' ),
'' . get_the_title() . ''
),
'',
''
);
}
endif;
```
## Customizer Implementation
### Customizer Settings (inc/customizer.php)
```php
add_panel(
'theme_name_options',
array(
'title' => esc_html__( 'Theme Options', 'theme-name' ),
'description' => esc_html__( 'Configure theme settings.', 'theme-name' ),
'priority' => 130,
)
);
// Header Section.
$wp_customize->add_section(
'theme_name_header',
array(
'title' => esc_html__( 'Header', 'theme-name' ),
'panel' => 'theme_name_options',
'priority' => 10,
)
);
// Header Layout Setting.
$wp_customize->add_setting(
'header_layout',
array(
'default' => 'default',
'sanitize_callback' => 'theme_name_sanitize_select',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'header_layout',
array(
'label' => esc_html__( 'Header Layout', 'theme-name' ),
'section' => 'theme_name_header',
'type' => 'select',
'choices' => array(
'default' => esc_html__( 'Default', 'theme-name' ),
'centered' => esc_html__( 'Centered', 'theme-name' ),
'minimal' => esc_html__( 'Minimal', 'theme-name' ),
),
)
);
// Sticky Header Toggle.
$wp_customize->add_setting(
'sticky_header',
array(
'default' => false,
'sanitize_callback' => 'theme_name_sanitize_checkbox',
'transport' => 'refresh',
)
);
$wp_customize->add_control(
'sticky_header',
array(
'label' => esc_html__( 'Enable Sticky Header', 'theme-name' ),
'section' => 'theme_name_header',
'type' => 'checkbox',
)
);
// Footer Section.
$wp_customize->add_section(
'theme_name_footer',
array(
'title' => esc_html__( 'Footer', 'theme-name' ),
'panel' => 'theme_name_options',
'priority' => 20,
)
);
// Footer Copyright Text.
$wp_customize->add_setting(
'footer_copyright',
array(
'default' => '',
'sanitize_callback' => 'wp_kses_post',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
'footer_copyright',
array(
'label' => esc_html__( 'Copyright Text', 'theme-name' ),
'description' => esc_html__( 'Enter custom copyright text for the footer.', 'theme-name' ),
'section' => 'theme_name_footer',
'type' => 'textarea',
)
);
// Selective refresh for footer copyright.
$wp_customize->selective_refresh->add_partial(
'footer_copyright',
array(
'selector' => '.site-info',
'render_callback' => 'theme_name_customize_partial_copyright',
)
);
// Typography Section.
$wp_customize->add_section(
'theme_name_typography',
array(
'title' => esc_html__( 'Typography', 'theme-name' ),
'panel' => 'theme_name_options',
'priority' => 30,
)
);
// Body Font Size.
$wp_customize->add_setting(
'body_font_size',
array(
'default' => 16,
'sanitize_callback' => 'absint',
'transport' => 'postMessage',
)
);
$wp_customize->add_control(
'body_font_size',
array(
'label' => esc_html__( 'Body Font Size (px)', 'theme-name' ),
'section' => 'theme_name_typography',
'type' => 'number',
'input_attrs' => array(
'min' => 12,
'max' => 24,
'step' => 1,
),
)
);
}
add_action( 'customize_register', 'theme_name_customize_register' );
/**
* Sanitize select input.
*
* @param string $input Input value.
* @param object $setting Setting object.
* @return string Sanitized value.
*/
function theme_name_sanitize_select( $input, $setting ) {
$input = sanitize_key( $input );
$choices = $setting->manager->get_control( $setting->id )->choices;
return ( array_key_exists( $input, $choices ) ? $input : $setting->default );
}
/**
* Sanitize checkbox.
*
* @param bool $checked Whether checked.
* @return bool
*/
function theme_name_sanitize_checkbox( $checked ) {
return ( ( isset( $checked ) && true === $checked ) ? true : false );
}
/**
* Render copyright partial.
*/
function theme_name_customize_partial_copyright() {
$copyright = get_theme_mod( 'footer_copyright' );
if ( $copyright ) {
echo wp_kses_post( $copyright );
} else {
printf(
/* translators: %s: Site name */
esc_html__( '© %1$s %2$s', 'theme-name' ),
esc_html( gmdate( 'Y' ) ),
esc_html( get_bloginfo( 'name' ) )
);
}
}
/**
* Enqueue customizer preview script.
*/
function theme_name_customize_preview_js() {
wp_enqueue_script(
'theme-name-customizer',
get_template_directory_uri() . '/assets/js/customizer.js',
array( 'customize-preview' ),
THEME_NAME_VERSION,
true
);
}
add_action( 'customize_preview_init', 'theme_name_customize_preview_js' );
/**
* Output customizer CSS.
*/
function theme_name_customizer_css() {
$body_font_size = get_theme_mod( 'body_font_size', 16 );
$css = "
body {
font-size: {$body_font_size}px;
}
";
wp_add_inline_style( 'theme-name-style', $css );
}
add_action( 'wp_enqueue_scripts', 'theme_name_customizer_css' );
```
## Child Theme Setup
### style.css
```css
/*
Theme Name: Theme Name Child
Theme URI: https://example.com/theme-child
Description: Child theme for Theme Name
Author: Your Agency
Author URI: https://youragency.com
Template: theme-name
Version: 1.0.0
License: GPL v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: theme-name-child
*/
/* Custom styles below */
```
### functions.php
```php
get( 'Version' )
);
// Child theme stylesheet.
wp_enqueue_style(
'theme-name-child-style',
get_stylesheet_uri(),
array( $parent_style ),
wp_get_theme()->get( 'Version' )
);
}
add_action( 'wp_enqueue_scripts', 'theme_name_child_enqueue_styles' );
```
## Accessibility Requirements
### Skip Link
```php
```
```css
/* Skip link styles */
.skip-link {
position: absolute;
left: -9999rem;
top: 2.5rem;
z-index: 999999999;
padding: 0.5rem 1rem;
background-color: #000;
color: #fff;
}
.skip-link:focus {
left: 1rem;
}
```
### Accessible Navigation
```php
```
### Screen Reader Text
```css
.screen-reader-text {
border: 0;
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
word-wrap: normal !important;
}
.screen-reader-text:focus {
background-color: #f1f1f1;
border-radius: 3px;
box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.6);
clip: auto !important;
clip-path: none;
color: #21759b;
display: block;
font-size: 0.875rem;
font-weight: 700;
height: auto;
left: 5px;
line-height: normal;
padding: 15px 23px 14px;
text-decoration: none;
top: 5px;
width: auto;
z-index: 100000;
}
```
## Security Best Practices
```php
// ❌ BAD: Unescaped output.
echo $title;
echo get_the_title();
// ✅ GOOD: Always escape output.
echo esc_html( $title );
echo esc_html( get_the_title() );
// ❌ BAD: Unescaped URLs.
// ✅ GOOD: Escape URLs.
// ❌ BAD: Unescaped attributes.
// ✅ GOOD: Escape attributes.
// ❌ BAD: Using include with user input.
include $_GET['template'];
// ✅ GOOD: Validate template names.
$allowed = array( 'header', 'footer', 'sidebar' );
$template = sanitize_file_name( $_GET['template'] );
if ( in_array( $template, $allowed, true ) ) {
get_template_part( $template );
}
```
## Performance Optimization
### Lazy Load Images
```php
// WordPress 5.5+ handles lazy loading automatically.
// For older versions or custom implementation:
function theme_name_lazy_load_images( $content ) {
return str_replace( '
';
}
add_action( 'wp_head', 'theme_name_preload_assets', 1 );
```
## Theme Check Compliance
Before release, ensure:
- [ ] Passes Theme Check plugin (no errors)
- [ ] Passes Theme Sniffer (PHP coding standards)
- [ ] All strings are translation-ready
- [ ] Uses proper prefixing for functions, classes, options
- [ ] Includes screenshot.png (1200x900px)
- [ ] Includes readme.txt with proper format
- [ ] No bundled plugins (recommend instead)
- [ ] GPL-compatible license
- [ ] Escapes all output
- [ ] Sanitizes all input
- [ ] Uses proper enqueue functions
- [ ] Supports core WordPress features
## Severity Definitions
| Severity | Description |
|----------|-------------|
| **Critical** | Theme won't work, security vulnerability |
| **Warning** | Theme Check failure, accessibility issue |
| **Info** | Best practice suggestion, optimization |