Skip to content

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 bring

It 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 Themes

Composer 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.

OptionWhat it does
nameThe theme's name: Recipes or travel-notes. Letters and digits; words split by dashes, spaces or capitals.
--type=recipeA type of the theme's own, with a model and a section in both admins. See A type of its own.
--taxonomy=cuisineA 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=tablesWhere 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,sidebarsWhat the theme brings. Default: all of it. See What a theme brings.
--vendor=acmeThe Composer vendor: acme/recipes-theme, namespace Acme\Theme\Recipes. Default: kitloom.
--path=themesWhere 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:

FeatureThe theme gets
formsContact Form 7 forms in a page's text (Forms), with the honeypot and Turnstile
slider[slide-anything] sliders (Sliders)
socialA social links menu location, and Continue with Google on the account page (kitloom/social-accounts)
accountThe 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
commentsComments under posts and pages (kitloom/comments)
sidebarsA 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 --fields as 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-recipe with every field, and taxonomy-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 — number an integer, toggle a boolean, both indexed for lists that filter and sort by them; text a string; textarea and url text. Its migration comes with the theme and runs on activation and with the demo.
  • The same attributes. $recipe->servings reads the card (Models\RecipeCard, the model's card relation); 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 ​

  1. composer update <vendor>/<name>-theme
  2. php artisan <name>:demo, if you want the demo content.
  3. Build it: APP_THEME=<Name> npx vite build, or npm run build:themes for all of them. See Installing Themes.
  4. 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 Themes

They 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 ​

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