Skip to content

Child Themes ​

Introduction ​

A child theme changes another theme — its parent — without copying it. It holds only what it changes: a template, a layout, a component, a few colours. Everything else is the parent's, and when the parent is updated with composer update, the child gets the update too.

It works the way a WordPress child theme does. Where WordPress reads Template: twentytwentyfive from the child's style.css, kitloom reads the parent's name from the child's composer.json:

json
{
    "extra": {
        "kitloom": {
            "theme": {
                "parent": "Journal"
            }
        }
    }
}

A folder themes/Inkwell/ with that file and one changed template is a whole child theme. It needs no service provider and no package, and Appearance → Themes lists it beside the others.

Child themes: a child of Blade on Appearance → Themes and on the site

Make one ​

From the application:

bash
php artisan kitloom:make-theme Inkwell --parent=Journal

This writes themes/Inkwell/:

FileWhat it is
composer.jsonThe parent's name, the child's name and description for its card, the parent's demo command
resources/style.cssThe child's own CSS, loaded after the parent's
README.mdHow to override, and every file of the parent you may override

To start from some of the parent's files, name them with --copy. A Blade view is named by its path under resources/views, with or without .blade.php, dots or slashes; a Vue or React file by its path under resources/js:

bash
php artisan kitloom:make-theme Ledger --parent=BladeTheme --copy=layouts.app,partials/styles
php artisan kitloom:make-theme Inkwell --parent=Journal --copy=Components/Masthead.vue,Templates/page

Run on a child that already exists, the command copies only the files you name, and never over a file the child already has.

--package makes a Composer package instead of a folder: acme/inkwell-theme with a service provider, a test, the parent's package in require, wired into the application like any theme. Use it for a child you will install on several sites; use a folder for one site.

bash
php artisan kitloom:make-theme Inkwell --parent=Journal --package --vendor=acme

The parent must be installed: a folder of themes/ or a package that declares a theme. A child of a child is refused, as in WordPress.

What a child can change ​

Templates ​

The template hierarchy asks for each template name in the child first, then in the parent, before it moves to the next name. On a single book the hierarchy asks for single-book, single, singular, index; if the parent has single-book and the child has only single, the parent's single-book wins. This is what locate_template() does in WordPress.

For /about/, page-about and page-12 are found in neither theme and page is found in the child first, so the child's template is used; for a post, single is found only in the parent, so the parent's template is used.

Blade views ​

A child's view of a name comes before the parent's, however it is named: @include('partials.footer'), @extends('theme::layouts.app'), a template, a shortcode's view. Put resources/views/partials/footer.blade.php in the child and every page has the child's footer, including pages drawn by the parent's templates.

parent:: names the parent's own view. A child's layout can wrap the parent's instead of copying it:

blade
{{-- themes/Ledger/resources/views/layouts/app.blade.php --}}
@extends('parent::layouts.app')

A Blade child's resources/style.css goes into the page head, in @siteHead, after the parent's styles. Every bundled theme keeps its colours in CSS variables, so a child that changes colours needs only this:

css
:root { --accent: #1d4ed8; }

Vue and React components ​

In a Vue or React theme, @/ imports look in the child first, then in the parent. Put resources/js/Components/Masthead.vue in the child and every template that imports @/Components/Masthead.vue gets the child's masthead, the parent's templates included. @parent/ always means the parent's file, for a component that wraps the parent's:

vue
<script setup>
import ParentMasthead from '@parent/Components/Masthead.vue';
</script>

<template>
    <ParentMasthead v-bind="$attrs" />
    <p class="masthead-note">Printed in the hills</p>
</template>

A relative import inside the parent (./Masthead.vue) is not looked up in the child: only @/ is.

The child is built on its own, into public/build-Inkwell:

bash
APP_THEME=Inkwell npx vite build

npm run build:themes builds it too, with every other theme. A child without resources/app.js is built from the parent's entry, and its resources/style.css is imported after the parent's styles. A child with an entry of its own is built from that.

Every bundled Vue and React theme imports its templates from virtual:kitloom-templates, which holds both themes' templates by name, so any of them can be a parent. A theme of your own does the same:

js
import templates from 'virtual:kitloom-templates';

createInertiaApp({
    resolve: (name) => (templates[name] ?? templates.index).default,
    // …
});

Words ​

A child's lang/<locale>.json is read after the parent's, so its lines win.

What stays the parent's ​

While a child is active, the parent counts as active too, as WordPress loads both themes' functions.php. So everything the parent's service provider adds keeps working:

  • its menu locations and widget zones, with the menus and widgets already in them;
  • its post types, with their admin screens in Filament and Nova;
  • the page parts its templates read, and the parts the child's provider adds after them;
  • its database tables: activating the child runs the parent's migrations first, then the child's;
  • its settings on Appearance → Customize, with values of the child's own;
  • its demo command and its screenshot, until the child has its own.

A child with a service provider of its own (--package) boots after its parent, so a part or a listener it adds wins.

Activating ​

On Appearance → Themes, the child's card says Child of Journal. While the child is active, the parent's card says Parent of Inkwell. The console does the same:

bash
php artisan site:theme Inkwell

A child whose parent is not installed — or whose parent's package is switched off along with a module — is listed with the reason and cannot be activated.

The first time a child is activated, it starts from the parent's settings: the colours and the logo the owner picked stay. WordPress resets them instead. After that, the two themes keep separate values.

For AI agents ​

describe_site names the active theme and its parent, and resolve_url tells which template a page is drawn with. An agent asked to change a colour uses theme_customize; it does not write theme files.

Next ​

kitloom — modules and themes for Laravel.
Pricing · Services · License · Refunds · Privacy · Community · [email protected]