Child Themes

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This sub-section’s WordPress bibliography lists the reference material consulted while preparing these pages.

A child theme is a theme that declares another installed theme as its parent and inherits everything it does not replace: templates, template parts, patterns, theme.json settings and styles, and PHP code. The parent can then be updated without losing the customizations, because they live in a different folder. This page is for theme developers and for agencies that adapt a third-party theme. It builds lumen-child on top of the Lumen Press block theme lumen, a child of the classic theme lumen-classic, and shows on a running site how WordPress merges the two layers.

This page was written against WordPress 7.1.3 (released 2026-10-06; 7.1.4, a security release of 2026-10-28, is the version to run in production), PHP 7.4 minimum and 8.3+ recommended, theme.json version 3 and WP-CLI 2.12.0, and checked on 2026-10-28. WordPress 7.2, in beta and due in December 2026, announces no change to child themes so far.

The parent themes used here are the skeletons from Themes Overview, with the templates and parts of Templates and Template Parts and the lumen/submissions-callout pattern of Theme Patterns. The commands run against the Docker Compose stack of Local Development Environments, with WP_DEBUG_LOG enabled.

Parent, child and the layers above them

Every complete theme can act as a parent. A child theme needs only a folder and a style.css whose Template header equals the parent’s folder name (its slug, not its Theme Name). When the child is active WordPress stores two options, stylesheet (the child) and template (the parent), and every theme-file lookup checks the child first.

Function Returns, with lumen-child active

get_stylesheet() / get_template()

lumen-child / lumen

get_stylesheet_directory(), get_stylesheet_directory_uri()

Path and URL of the child folder (the active theme)

get_template_directory(), get_template_directory_uri()

Path and URL of the parent folder

get_stylesheet_uri()

URL of the child’s style.css; it is the parent’s only when no child is active

get_theme_file_path( 'parts/header.html' ), get_theme_file_uri()

The child’s file if it exists, otherwise the parent’s (4.7)

get_parent_theme_file_path(), get_parent_theme_file_uri()

Always the parent’s file (4.7)

is_child_theme()

true

wp_get_theme()->parent()

A WP_Theme object for lumen; false without a parent

The naming is historical: "stylesheet" means the active theme and "template" means the theme that provides templates. A function named get_stylesheet_directory_uri() therefore never points at a stylesheet file, and it is the wrong function for anything that belongs to the parent.

For a block theme the design is a stack of four theme.json layers, merged in this order: core defaults < parent theme < child theme < user. The user layer is what the owner saves in the Site Editor (a wp_global_styles post, plus wp_template and wp_template_part posts for edited templates). It behaves like a grandchild stored in the database, which is the only "third level" WordPress offers.

A child theme is not always the right tool:

Need Tool

Different colours or fonts, same layout

A style variation in the parent, or Styles in the Site Editor (see Style Variations and Section Styles)

A few templates, parts or patterns changed, parent still maintained upstream

A child theme

New post types, blocks, shortcodes, roles

A plugin, never a theme ("plugin territory", see Themes Overview)

Most templates rewritten, parent abandoned or the result diverges for good

A fork: copy the parent under a new slug and maintain it as a theme of its own

A child of a block theme: lumen-child

Lumen Press uses lumen-child for a seasonal brand refresh: a darker red, a wider wide size, a dark header, a single post template that shows the submissions box, and an autumn version of that box.

wp-content/themes/lumen-child/
├── style.css                      # required: header with Template: lumen
├── theme.json                     # palette and layout overrides only
├── functions.php                  # optional; loaded before the parent's
├── parts/header.html              # replaces lumen/parts/header.html
├── templates/single.html          # replaces lumen/templates/single.html
└── patterns/submissions-callout.php  # same Slug as the parent pattern

style.css

/*
Theme Name: Lumen Child
Template: lumen
Description: Seasonal palette and small layout changes on top of the Lumen block theme.
Author: Lumen Press
Version: 0.1.0
Requires at least: 6.9
Tested up to: 7.1
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: lumen-child
*/

A block child theme does not need to load any stylesheet: the parent lumen has no CSS in style.css (only the header), and every style comes from theme.json. Text Domain must be the child’s own slug, not lumen.

theme.json

{
	"$schema": "https://schemas.wp.org/wp/7.1/theme.json",
	"version": 3,
	"settings": {
		"color": {
			"palette": [
				{ "slug": "paper", "name": "Paper", "color": "#ffffff" },
				{ "slug": "ink", "name": "Ink", "color": "#111111" },
				{ "slug": "lumen-red", "name": "Lumen red", "color": "#8c1d17" }
			]
		},
		"layout": {
			"wideSize": "1280px"
		}
	}
}

The child file is merged over the parent’s, with two different rules:

  • Objects merge key by key. The child sets only layout.wideSize, so contentSize (680px) still comes from the parent, and the parent’s styles section (background paper, text ink, links lumen-red) applies unchanged.

  • Preset lists replace each other. A child palette is not merged slug by slug with the parent’s: it replaces the whole theme palette. A child that lists only the colour it wants to change silently removes the others.

That second rule is the most common child-theme bug. With a first version of the child that listed only lumen-red, the merged theme palette contained a single colour, while the parent’s styles still referred to paper and ink, whose CSS variables were no longer generated:

docker compose exec cli wp theme activate lumen-child
docker compose exec cli wp eval \
  'echo wp_json_encode( wp_get_global_settings( array( "color", "palette", "theme" ) ) ), "\n";'
[{"slug":"lumen-red","name":"Lumen red","color":"#8c1d17"}]

With the full palette shown above, the merged settings are the ones intended:

docker compose exec cli wp eval '
echo wp_json_encode( wp_get_global_settings( array( "color", "palette", "theme" ) ), JSON_PRETTY_PRINT ), "\n";
echo wp_json_encode( wp_get_global_settings( array( "layout" ) ) ), "\n";
echo wp_json_encode( wp_get_global_styles( array( "color" ) ) ), "\n";'
[
    {
        "slug": "paper",
        "name": "Paper",
        "color": "#ffffff"
    },
    {
        "slug": "ink",
        "name": "Ink",
        "color": "#111111"
    },
    {
        "slug": "lumen-red",
        "name": "Lumen red",
        "color": "#8c1d17"
    }
]
{"contentSize":"680px","wideSize":"1280px"}
{"background":"var(--wp--preset--color--paper)","text":"var(--wp--preset--color--ink)"}

wp_get_global_settings() without a path returns the whole merged tree, and the theme, default and custom keys of a preset list are the origins (the user’s own colours appear under custom). The same rule applies to settings.blocks: a parent palette declared for one block (for example core/button) survives in the child until the child declares that block’s palette too. The detailed merge rules are in theme.json Settings.

Overriding a template part

A file in the child with the same path as a file in the parent replaces it. The child’s header adds the tagline and uses the palette for a dark band:

<!-- wp:group {"align":"full","backgroundColor":"ink","textColor":"paper","layout":{"type":"constrained"}} -->
<div class="wp-block-group alignfull has-paper-color has-ink-background-color has-text-color has-background">
	<!-- wp:group {"align":"wide","layout":{"type":"flex","justifyContent":"space-between"}} -->
	<div class="wp-block-group alignwide">
		<!-- wp:site-title {"level":0} /-->
		<!-- wp:site-tagline {"fontSize":"small"} /-->
		<!-- wp:navigation {"overlayMenu":"mobile","layout":{"type":"flex","justifyContent":"right"}} /-->
	</div>
	<!-- /wp:group -->
</div>
<!-- /wp:group -->

The part’s area (header) still comes from the parent’s templateParts list in theme.json, which the child inherits. Template-part blocks reference parts by slug only ({"slug":"header"}); a hard-coded "theme":"lumen" attribute would bypass the child’s file.

Overriding a template

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

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
	<!-- wp:post-title {"level":1} /-->
	<!-- wp:post-date /-->
	<!-- wp:post-featured-image {"align":"wide"} /-->
	<!-- wp:post-content {"layout":{"type":"constrained"}} /-->
	<!-- wp:pattern {"slug":"lumen/submissions-callout"} /-->
	<!-- wp:post-navigation-link {"type":"previous"} /-->
	<!-- wp:post-navigation-link /-->
</main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

Templates the child does not ship still come from the parent, and the Template Hierarchy is evaluated over both folders: a child that adds templates/single-lp_book.html takes over book pages even if the parent never had that file.

Overriding a pattern

A pattern file replaces the parent’s when it has the same Slug header; the file name alone is not enough.

<?php
/**
 * Title: Submissions call-out (autumn)
 * Slug: lumen/submissions-callout
 * Description: Autumn version of the parent theme's submissions box.
 * Categories: lumen/house, call-to-action
 * Keywords: submissions, manuscript, call to action
 * Viewport Width: 900
 *
 * @package Lumen_Child
 */

?>
<!-- wp:group {"backgroundColor":"lumen-red","textColor":"paper","layout":{"type":"constrained"},"style":{"spacing":{"padding":{"top":"var:preset|spacing|40","bottom":"var:preset|spacing|40","left":"var:preset|spacing|40","right":"var:preset|spacing|40"}}}} -->
<div class="wp-block-group has-paper-color has-lumen-red-background-color has-text-color has-background" style="padding-top:var(--wp--preset--spacing--40);padding-right:var(--wp--preset--spacing--40);padding-bottom:var(--wp--preset--spacing--40);padding-left:var(--wp--preset--spacing--40)"><!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading"><?php esc_html_e( 'Autumn submissions window', 'lumen-child' ); ?></h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><?php esc_html_e( 'Send us your manuscript before 30 November.', 'lumen-child' ); ?></p>
<!-- /wp:paragraph -->

<!-- wp:buttons -->
<div class="wp-block-buttons"><!-- wp:button -->
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="<?php echo esc_url( home_url( '/submissions/' ) ); ?>"><?php esc_html_e( 'Read the guidelines', 'lumen-child' ); ?></a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons --></div>
<!-- /wp:group -->

_register_theme_block_patterns() registers the active theme’s pattern files first and then the parent’s, skipping any slug already registered, so the child’s version wins. Patterns registered in PHP with register_block_pattern() follow a different rule: both functions.php files register them on init, and when both use the same name the later registration (normally the parent’s) replaces the earlier one. To drop such a pattern, call unregister_block_pattern() from the child on init at a later priority (see Theme Patterns).

Pattern file headers are cached per theme in a site transient (wp_theme_files_patterns-<hash>) since 6.6. The cache is refreshed when the theme is switched or its Version header changes, and bypassed when WP_DEVELOPMENT_MODE is theme or all. A pattern file added to an already active child theme is therefore ignored until one of these happens. On the test site the parent’s pattern stayed registered after patterns/submissions-callout.php was copied into the active child, and the override took effect after clearing that theme’s cache:

docker compose exec cli wp eval 'wp_get_theme()->delete_pattern_cache();'
# or, on a development site only:
docker compose exec cli wp config set WP_DEVELOPMENT_MODE theme

Checking which file wins

The following script prints the file each template, part and pattern is read from, calls the pluggable helper of the next section, and shows the template object the Site Editor would load. _get_block_template_file() is a private core function, fine for a diagnostic script but not for theme code.

<?php
// child-probe.php: run with "wp eval-file child-probe.php" while lumen-child is active.
foreach ( array( array( 'single', 'wp_template' ), array( 'index', 'wp_template' ), array( 'header', 'wp_template_part' ), array( 'footer', 'wp_template_part' ) ) as $t ) {
	$file = _get_block_template_file( $t[1], $t[0] );
	printf( "%-16s %-6s %s\n", $t[1], $t[0], str_replace( WP_CONTENT_DIR, 'wp-content', $file['path'] ) );
}
$p = WP_Block_Patterns_Registry::get_instance()->get_registered( 'lumen/submissions-callout' );
printf( "pattern %s: %s\n", $p['title'], str_replace( WP_CONTENT_DIR, 'wp-content', $p['filePath'] ?? '(no filePath)' ) );
printf( "lumen_reading_time( 1000 ) = %d\n", lumen_reading_time( 1000 ) );
$tpl = get_block_template( get_stylesheet() . '//single' );
printf( "%s source=%s theme=%s has_theme_file=%s\n", $tpl->id, $tpl->source, $tpl->theme, var_export( $tpl->has_theme_file, true ) );
wp_template      single wp-content/themes/lumen-child/templates/single.html
wp_template      index  wp-content/themes/lumen/templates/index.html
wp_template_part header wp-content/themes/lumen-child/parts/header.html
wp_template_part footer wp-content/themes/lumen/parts/footer.html
pattern Submissions call-out (autumn): wp-content/themes/lumen-child/patterns/submissions-callout.php
lumen_reading_time( 1000 ) = 5
lumen-child//single source=theme theme=lumen-child has_theme_file=true

The user layer belongs to the child

Everything the owner saves in the Site Editor is stored against the active stylesheet. On the test site, Global Styles posts created for both themes were tied to different wp_theme terms:

wp-global-styles-lumen -> wp_theme term: lumen
wp-global-styles-lumen-child -> wp_theme term: lumen-child

Two consequences follow. Switching a live site from lumen to lumen-child leaves the owner’s Styles changes and edited templates behind with lumen; export them first (Site Editor export or Create Block Theme, see Create Block Theme and Version Control) and move them into the child’s files. And a template edited in the Site Editor (a wp_template post) wins over both the child’s and the parent’s file until the owner resets it, so a child-theme change to templates/single.html is invisible on a site where that template was customized.

Load order of functions.php

Both functions.php files run; the child’s does not replace the parent’s. wp-settings.php loops over wp_get_active_and_valid_themes(), which returns the child’s path first and the parent’s second, loads each theme’s text domain and includes its functions.php. Both files of the test themes write a log line:

<?php
/**
 * Lumen Child functions. Loaded before the parent theme's functions.php.
 *
 * @package Lumen_Child
 */

defined( 'ABSPATH' ) || exit;

error_log( 'functions.php loaded: lumen-child' );

/**
 * Replaces the parent's pluggable reading-time helper.
 *
 * The parent defines lumen_reading_time() inside if ( ! function_exists() ), so the
 * child's definition, loaded first, wins. Do not copy any other parent function here.
 *
 * @param int $words Word count.
 * @return int Minutes.
 */
function lumen_reading_time( $words ) {
	return (int) max( 1, ceil( $words / 200 ) );
}

The parent’s file contains error_log( 'functions.php loaded: lumen (parent)' ); and wraps its helper in if ( ! function_exists( 'lumen_reading_time' ) ). Any request writes, in wp-content/debug.log:

functions.php loaded: lumen-child
functions.php loaded: lumen (parent)

What follows from the order:

  • A child can replace a parent function only if the parent made it pluggable with function_exists(). Redeclaring any other parent function is a fatal "Cannot redeclare" error, which is why a child must never start as a copy of the parent’s functions.php.

  • Code at the top level of the child’s file runs before the parent has defined anything. Work that depends on the parent (removing its hooks, calling its functions) belongs in a hook: remove_action() for a parent callback added at file load succeeds on after_setup_theme or later, never at the child’s top level.

  • Callbacks on the same hook and priority run in the order they were added, so the child’s run first. A child that must run after the parent uses a higher priority number.

A child of a classic theme: lumen-classic-child

In a classic theme style.css holds real CSS, and what the child must do depends on how the parent loads it. lumen-classic (see Themes Overview) enqueues get_stylesheet_uri() under the handle lumen-classic-style, which is the common case and the one that needs care:

Parent loads Child must

Its own file with get_template_directory_uri() or get_parent_theme_file_uri( 'style.css' ), and the child’s with get_stylesheet_uri() when is_child_theme()

Nothing

Only get_stylesheet_uri() (the active theme’s file)

Enqueue the parent’s file itself, ideally under the parent’s handle, and its own file with that handle as a dependency

No stylesheet at all (block themes)

Enqueue its own style.css only if it adds CSS

What goes wrong

With a child that enqueues nothing, the parent’s handle now points at the child’s file, and the parent CSS is not loaded at all:

<link rel='stylesheet' id='lumen-classic-style-css' href='https://lumenpress.test/wp-content/themes/lumen-classic-child/style.css?ver=0.1.0' media='all' />

A child that tries to load "the parent stylesheet" with get_stylesheet_directory_uri() . '/style.css' and omits the version produced three tags for the same child file, versioned with the WordPress version because wp_enqueue_style() falls back to it when $ver is omitted:

<link rel='stylesheet' id='lumen-classic-parent-css' href='https://lumenpress.test/wp-content/themes/lumen-classic-child/style.css?ver=7.1.3' media='all' />
<link rel='stylesheet' id='lumen-classic-child-style-css' href='https://lumenpress.test/wp-content/themes/lumen-classic-child/style.css?ver=7.1.3' media='all' />
<link rel='stylesheet' id='lumen-classic-style-css' href='https://lumenpress.test/wp-content/themes/lumen-classic-child/style.css?ver=0.1.0' media='all' />

Child-theme recipes in older books, the Ultimate WordPress Handbook among them, predate the current guidance and should not be copied as printed. The oldest recipe puts an @import of the parent stylesheet at the top of the child’s style.css; the Theme Handbook no longer shows it, because the browser fetches the two files one after the other and the import bypasses WordPress’s dependency and versioning system. Enqueue-based recipes go wrong in the ways shown above: the child’s directory function (get_stylesheet_directory_uri()) used where the parent’s is meant, no dependency handle, so the order of the two files is not guaranteed, or no version, so caches are not refreshed after a parent update. Finally, the classic recipe is not needed for a block child theme, which usually has no stylesheet code at all.

The correct functions.php

<?php
/**
 * Lumen Classic Child functions. Loaded before lumen-classic/functions.php.
 *
 * @package Lumen_Classic_Child
 */

defined( 'ABSPATH' ) || exit;

/**
 * Enqueues the parent stylesheet, then the child stylesheet that depends on it.
 *
 * The parent enqueues get_stylesheet_uri() under the handle 'lumen-classic-style'. With a child theme
 * active that URI points at the child's style.css, so the child registers the handle first with the
 * parent's own file; the parent's later wp_enqueue_style() call then reuses the registered handle.
 */
function lumen_classic_child_enqueue_styles() {
	$parent = wp_get_theme( get_template() );

	wp_enqueue_style(
		'lumen-classic-style',
		get_template_directory_uri() . '/style.css',
		array(),
		$parent->get( 'Version' )
	);

	wp_enqueue_style(
		'lumen-classic-child-style',
		get_stylesheet_uri(),
		array( 'lumen-classic-style' ),
		wp_get_theme()->get( 'Version' )
	);
}
add_action( 'wp_enqueue_scripts', 'lumen_classic_child_enqueue_styles' );

The child’s style.css starts with the header (Theme Name: Lumen Classic Child, Template: lumen-classic, Text Domain: lumen-classic-child) followed by its own rules. The resulting tags, printed with wp_print_styles() from wp eval-file after do_action( 'wp_enqueue_scripts' ):

lumen-classic-style        https://lumenpress.test/wp-content/themes/lumen-classic/style.css deps=[] ver=0.1.0
lumen-classic-child-style  https://lumenpress.test/wp-content/themes/lumen-classic-child/style.css deps=[lumen-classic-style] ver=0.1.0
<link rel='stylesheet' id='lumen-classic-style-css' href='https://lumenpress.test/wp-content/themes/lumen-classic/style.css?ver=0.1.0' media='all' />
<link rel='stylesheet' id='lumen-classic-child-style-css' href='https://lumenpress.test/wp-content/themes/lumen-classic-child/style.css?ver=0.1.0' media='all' />

Each file appears once, the parent first, each with its own theme’s version. Reusing the parent’s handle is what stops the parent’s own wp_enqueue_style() from adding the child’s file again: a second call with an already registered handle only enqueues it and ignores the new source. The handle differs per parent theme, so read the parent’s functions.php (or wp_styles()->registered) before writing the child. get_parent_theme_file_uri( 'style.css' ) is an equivalent, newer spelling of the parent URL.

A parent theme can make all this unnecessary by loading its own file with get_template_directory_uri() and, when is_child_theme(), the child’s file with get_stylesheet_uri(); well-maintained commercial and default classic themes do that, and their children only add CSS. Classic asset loading is covered in functions.php, Theme Supports and Assets.

Other parent files follow the same lookup as templates. get_template_part( 'template-parts/content' ), locate_template() and get_theme_file_path() check the child folder first, so a classic child overrides single.php or template-parts/content.php by shipping a copy; for PHP includes inside the child use require get_theme_file_path( 'inc/helpers.php' ); rather than a hard-coded path.

Scaffolding with WP-CLI

wp scaffold child-theme writes the folder, a style.css header and a functions.php with the two enqueues:

docker compose exec cli wp scaffold child-theme lumen-classic-child --parent_theme=lumen-classic \
  --theme_name="Lumen Classic Child" --author="Lumen Press" --author_uri=https://lumenpress.example/
Success: Created '/var/www/html/wp-content/themes/lumen-classic-child'.
./.editorconfig
./functions.php
./style.css

The generated functions.php enqueues get_template_directory_uri() . '/style.css' under the handle <parent>-style (here lumen-classic-style, which happens to be the parent’s real handle) and the child file with that handle as a dependency, both with a hard-coded version 0.1.0 that does not follow later releases. Other options: --theme_uri, --activate, --enable-network and --force (overwrite). For a block parent the same generated code loads the parent’s header-only style.css for nothing; delete that enqueue, and the whole functions.php if the child has no PHP. Create Block Theme can also create a block child theme from the Site Editor, including the owner’s current changes.

Translating a child theme

A child theme has its own text domain (lumen-child), used in its patterns and PHP. Strings that come from parent files keep the parent’s domain (lumen) and its translations; translation files are not shared between the two. During bootstrap WordPress loads each active theme’s text domain from the folder named by its Domain Path header (default /languages), so lumen-child/languages/lumen-child-de_DE.mo is found without code. A child keeping its files elsewhere loads them explicitly:

add_action(
	'after_setup_theme',
	function () {
		load_child_theme_textdomain( 'lumen-child', get_stylesheet_directory() . '/i18n' );
	}
);

load_theme_textdomain() in a child would look in the parent’s directory by default; use the child variant. Themes from the WordPress.org directory also get language packs under wp-content/languages/themes/, which take precedence over bundled files. See Internationalization.

No grandchild themes, and broken parents

A child theme cannot be a parent. Both broken setups were tested with WP-CLI:

lumen-grandchild: theme_parent_invalid - The "lumen-child" theme is not a valid parent theme.
lumen-orphan: theme_no_parent - The parent theme is missing. Please install the "lumen-old" parent theme.
Error: The "lumen-child" theme is not a valid parent theme.
Error: The parent theme is missing. Please install the "lumen-old" parent theme.

The first two lines come from wp_get_theme( $slug )->errors(), the last two from wp theme activate. Such themes appear under "Broken Themes" in Appearance > Themes and are left out of wp theme list. The usual cause of the second error is a Template header holding the parent’s display name or a renamed folder (a parent unzipped as lumen-main/ from a Git host).

When to fork instead

A child theme pays off while the parent keeps receiving updates you want. It becomes a liability when the child overrides most templates (parent fixes no longer reach them), relies on parent internals that are not pluggable or documented, or targets a parent that is abandoned. Forking means copying the parent under a new slug, changing Theme Name, Text Domain and the Update URI header (so the directory theme with the old slug is never offered as an update), and maintaining it in version control as a theme of its own.

Errors and pitfalls

Symptom Cause and fix

Colours of the parent disappear in the child

The child’s palette replaces the parent’s; list every colour, changed or not.

Child header file ignored

A Site Editor edit of the part exists in the database (reset it), or the template-part block hard-codes "theme":"lumen".

Child pattern ignored

Different Slug header, or the pattern cache; clear it or use WP_DEVELOPMENT_MODE theme while developing.

"Cannot redeclare function" on activation

A parent function was copied into the child; only pluggable functions can be replaced.

Classic parent CSS missing, child CSS loaded twice

The parent enqueues get_stylesheet_uri(); enqueue the parent file under the parent’s handle (see above).

?ver=7.1.3 on theme CSS

wp_enqueue_style() called without $ver; pass the theme’s Version header.

Styles and edited templates lost after switching to the child

The user layer is stored per stylesheet; export before switching.

Strings in the child not translated

The child uses the parent’s text domain, or load_theme_textdomain() instead of load_child_theme_textdomain().

Version notes

Version Change

4.7

get_theme_file_path(), get_theme_file_uri(), get_parent_theme_file_path(), get_parent_theme_file_uri().

5.9

Block themes; a block child theme inherits templates, parts and theme.json.

6.0

Child themes may ship their own patterns/ folder; same slug overrides the parent.

6.2

A child inherits the parent’s style variations (styles/*.json).

6.6

theme.json version 3; pattern file headers cached in a site transient for every environment.

What older books and tutorials get wrong

Older advice Current practice

Import the parent CSS with @import in the child’s style.css

Enqueue with wp_enqueue_style(), the parent URL from get_template_directory_uri() or get_parent_theme_file_uri(), and the parent handle as the child’s dependency.

Enqueue the parent file with get_stylesheet_directory_uri()

That function returns the child’s folder; it loads the child file twice.

Every child theme needs the enqueue boilerplate

Block children usually need no PHP at all; style.css plus theme.json is a complete child.

Copy the parent’s functions.php into the child to change it

The child’s file runs first, in addition to the parent’s; copying causes fatal redeclaration errors.