Appearance
Generating a Theme
Introduction
php artisan kitloom:make-theme writes a new Vue theme and connects it to the application. The theme follows the shape of the bundled Vue themes: every template it needs, its own neutral styles, menu locations, a demo command and tests. It passes the checks as generated: its tests, PHPStan, Pint and the Vite build.
The generator comes with kitloom/modules, next to the module generator. You need an application with kitloom installed, with the Filament or the Nova admin; one of the skeletons is enough.
To turn a design you already have into a theme, see From an HTML Template. To learn what each file of a theme does, see Writing a Theme.
Generate the theme
bash
php artisan kitloom:make-theme Journal # posts and pages
php artisan kitloom:make-theme Recipes --type=recipe --taxonomy=cuisine \
--fields=summary:textarea,servings:number,source:url,vegetarian:toggle
php artisan kitloom:make-theme Studio --with=forms,sidebars # only some of what a theme may bringIt writes the package <name>-theme next to your other packages, then prints what it did and what to run next:
text
Next:
composer update kitloom/recipes-theme
php artisan recipes:demo
Appearance → Themes → Recipes, then: APP_THEME=Recipes npx vite build
php vendor/bin/phpunit --testsuite ThemesComposer is not run for you. The package lives in the directory of your path repository (packages/ when composer.json has none), and Composer links it into vendor/; edit it there.
| Option | What it does |
|---|---|
name | The theme's name: Recipes or travel-notes. Letters and digits; words split by dashes, spaces or capitals. |
--type=recipe | A type of the theme's own, with a model and a section in both admins. See A type of its own. |
--taxonomy=cuisine | A taxonomy of that type. Needs --type. |
--fields=servings:number,… | The type's fields, as name:type, separated by commas. Needs --type. Default: summary:textarea. |
--storage=tables | Where the fields live: meta (post meta, as WordPress keeps them) or tables (a row of a table of the theme). Needs --type. Default: meta. See Fields in a table. |
--with=forms,sidebars | What the theme brings. Default: all of it. See What a theme brings. |
--vendor=acme | The Composer vendor: acme/recipes-theme, namespace Acme\Theme\Recipes. Default: kitloom. |
--path=themes | Where the package goes. Default: the directory of a path repository in composer.json, otherwise packages/. |
A theme that already exists is left as it is and only connected. Run the same command in a second application to connect a theme you generated in the first.
What a theme brings
--with picks what the theme brings, comma-separated. Without it, the theme brings all of these:
| Feature | The theme gets |
|---|---|
forms | Contact Form 7 forms in a page's text (Forms), with the honeypot and Turnstile |
slider | [slide-anything] sliders (Sliders) |
social | A social links menu location, and Continue with Google on the account page (kitloom/social-accounts) |
account | The visitor's account on the page chosen in Settings → Reading: signing in and up, the lost password, the second step, the details, the sections modules add |
comments | Comments under posts and pages (kitloom/comments) |
sidebars | A sidebar widget zone, {name}-sidebar. The footer zone, {name}-footer-widgets, is declared without it too; both as WordPress's register_sidebar() |
What the theme draws with is required in its composer.json: forms and the slider bring their modules along. What it only shows when the site has it, such as comments, social sign-in, two-factor and SEO, is suggested.
A type of its own
--type gives the theme a type of its own, the way a WordPress theme registers one in functions.php:
- A model of the theme.
Models\Recipe extends Post, with its--fieldsas attributes over post meta:$recipe->servings. - Only while the theme is active. The type is declared to the site by the theme, not by kitloom/post-types.
- Both admins. A Recipes section in Filament and in Nova, whichever the site has. With
--taxonomy, a section for its terms too. - Templates.
archive-recipe,single-recipewith every field, andtaxonomy-cuisine.
Fields are text, textarea, number, url and toggle; a field without a type is text. Names a post already has, such as title, status or author, are refused.
The admin addresses carry the theme's name, for example /admin/saffron-recipes. A site installs all its themes at once, and two of them may bring a type of the same name.
To add a field later, change four places: a constant in <Name>.php, an attribute on the model, a field in both admin resources, and a line in Site/<Model>Details.php.
Fields in a table
With --storage=tables the type's fields leave post meta for a table of the theme, one row per record — the way the Saffron theme keeps a recipe's card:
bash
php artisan kitloom:make-theme Recipes --type=recipe --taxonomy=cuisine \
--fields=servings:number,minutes:number,source:url,vegetarian:toggle --storage=tables- A card table.
recipes_recipe_cards:post_id, then a column per field —numberan integer,togglea boolean, both indexed for lists that filter and sort by them;texta string;textareaandurltext. Its migration comes with the theme and runs on activation and with the demo. - The same attributes.
$recipe->servingsreads the card (Models\RecipeCard, the model'scardrelation); a record without a card reads as empty. - Written after the post. Both admins save the post first, then the card (
Cards\RecipeCardWriter); the demo writes through the same writer. - Gone with the record. Deleted for good, a record takes its card along; the trash keeps it.
The record itself stays a post: title, text, address, status, author and terms. What reads post meta does not see the card — the WXR export, Custom Fields, the REST API's meta, Search & Replace. Fields that are lists (ingredients, steps) are not generated: add a table of rows the way Saffron does.
The demo
php artisan <name>:demo fills a fresh site so the theme can be shown as it is sold:
- eight posts in three categories, with tags, quotes and lists, and a few comments with replies;
- the Home, Journal, About, FAQ, Contact, Account and Privacy pages;
- menus with sub-items, widgets in both zones, a slider and a contact form;
- with
--type, six records with every field filled in.
The pictures are drawn by the theme's Demo\<Name>Art: hills, shapes, arches and waves in eight palettes. The words live in Demo\<Name>Content, the one file to rewrite for a theme of your own.
The demo command is there whichever theme is active, so a theme gets its content before it is switched on.
Build and activate
composer update <vendor>/<name>-themephp artisan <name>:demo, if you want the demo content.- Build it:
APP_THEME=<Name> npx vite build, ornpm run build:themesfor all of them. See Installing Themes. - Activate it on Appearance → Themes. The button stays disabled until the theme is built. See Installing Themes.
The theme's README says where in the admin each thing on the page is edited.
Tests
The application gets the theme's tests in the Themes testsuite:
bash
php vendor/bin/phpunit --testsuite ThemesThey check that the demo fills the site once, that the front page, the blog, the contact and account pages show what they should, and that the widget zones carry their widgets. With --type, the theme also gets an admin form test for each admin in its own tests/; the application's Themes testsuite leaves out the one of the admin it does not run.
A child theme
--parent makes a child of an installed theme instead — a folder of themes/, or a package with --package — holding only what it changes; --copy starts it from some of the parent's files:
bash
php artisan kitloom:make-theme Inkwell --parent=Journal --copy=Components/Masthead.vue--type, --taxonomy, --fields and --with do not go with it: a child has its parent's. See Child Themes.
Next
- Writing a Theme: what each file of the theme does
- Child Themes
- Templates: the template hierarchy
- Building Assets