--- 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' => '
', 'after_widget' => '
', 'before_title' => '

', 'after_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' => '

', 'after_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
>
', '' ); ?>
'', ) ); ?>
``` ## 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() ) : ?>
%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 ''; comments_popup_link( sprintf( /* translators: %s: post title */ esc_html__( 'Leave a Comment on %s', 'theme-name' ), '' . get_the_title() . '' ) ); 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 |