Skip to content

From an HTML Template ​

Introduction ​

Most sites start from a design that already exists as static HTML: a template bought or downloaded, or pages a designer handed over. This page is the way from such a template to a kitloom theme whose every word and picture is edited in the admin — "wiring a template into the CMS".

Two bundled themes were made exactly like this, and are the examples here:

ThemeFromWhat the admin edits
JournalStart Bootstrap's Clean Blog (MIT)Only what WordPress already has: posts and pages, featured images, excerpts, the reading settings, menus, a form
BureauStart Bootstrap's Agency (MIT)The same, plus a field group on the front page for the landing sections: services, portfolio, timeline, team, logos, contact

The rest of Writing a Theme still applies; this page is the order to do things in and the decisions along the way.

1. Check the license ​

The theme's code becomes yours to ship, so the template must allow it:

  • MIT, BSD, Apache 2.0 — fine. Keep the copyright notice: put the template's license into a NOTICE.md in the theme, and leave the comment at the top of its CSS as it is.
  • GPL — the theme would have to be GPL too, and it could not be sold with kitloom; do not mix them.
  • "Free for personal use", no license at all — not usable.

Photos and illustrations often come under a different license from the code, or none. Leave them out: the pictures should come from the site's media library anyway. Journal and Bureau ship none of their template's photos.

2. Map the pages to templates ​

Every page of the template becomes a template of the hierarchy. Write the table down first:

Template pageJournalBureau
The home pagefront-page — or the blog itself while the latest posts are at /front-page: the one-page landing
A postsinglesingle
"About", "Contact"pagepage
A list of postsindex (the blog page), archive (category, tag, author, date), searchindex, archive, search — drawn as portfolio tiles
(not in the template)404404

index is required: every page falls back to it. A template the HTML does not have (search, 404, an author page) is made in the template's style.

3. Map the content to the admin ​

This is the decision that makes the theme "editable". For every piece of text and every picture, ask where an editor will change it — and prefer the places WordPress already has. A site moved from WordPress then looks right on the first day, and editors find things where they expect them.

In the templateWhere it is editedExample
The site's name, the taglineSettings → GeneralNavbar brand, the blog's big title
The menuAppearance → Menus, in a location the theme registers or one added on Menu Locationsjournal-primary, bureau-primary
Social iconsA second menu location with Custom links; the icon follows from the addressjournal-social, bureau-social
Footer linksA third menu locationbureau-footer
A page's title and textThe pageAbout, Contact
The line under a titleThe excerpt — written by handJournal's subtitles
A header photoThe featured imageJournal's mastheads
A photo for listsThe featured image of the blog page (Settings → Reading)Journal's category headers
What is the home page, what is the blogSettings → ReadingBoth themes
A contact formForms (Contact Form 7), its shortcode in a page — or chosen in a fieldJournal's Contact page, Bureau's contact section
Repeated blocks of a landing page: services, team, timeline, logosA field group of Custom Fields, shown on the front pageBureau's "Front page sections"

Fields are for what WordPress has no place for. Journal needs none; Bureau needs one group, because "three services with an icon each" is not a post, a page or a menu.

4. Make the package ​

Generate it rather than starting from an empty folder: php artisan kitloom:make-theme Journal (Generating a Theme) writes the package below with every template, a demo command and tests, wired into the application and passing the checks. The steps that follow replace its neutral styles and templates with the template's.

text
journal-theme/
├── composer.json          # extra.laravel.themes + extra.kitloom.theme
├── LICENSE.md, NOTICE.md  # your license; the template's
├── README.md              # what is edited where — the admin's help
├── screenshot.jpg         # 800×600, shown on Appearance → Themes
├── src/
│   ├── ThemeServiceProvider.php
│   ├── Site/              # page parts the theme computes
│   ├── Demo/              # the demo content
│   └── Console/           # the demo command
├── database/migrations/   # Bureau: the field group
├── resources/
│   ├── app.js
│   ├── css/clean-blog.css # the template's CSS, as it is
│   ├── css/journal.css    # the theme's own additions
│   ├── views/app.blade.php
│   └── js/
│       ├── Layouts/, Components/
│       └── Templates/     # front-page.vue, index.vue, single.vue…
└── tests/

The provider names the theme, the engine and the page parts each template gets; the menu locations the theme needs on a fresh site are registered when it boots (others are added on Menu Locations):

php
class ThemeServiceProvider extends BaseThemeServiceProvider
{
    protected string $theme = 'Journal';

    protected string $engine = 'inertia';

    protected array $parts = [
        '*' => [SiteInfo::class, Content::class, Masthead::class, Menus::class, Locations::class, ShortcodeData::class, Head::class, Schema::class],
        'front-page' => [Posts::class, LatestPosts::class],
        'index' => [Posts::class],
        'archive' => [Posts::class],
    ];

    protected function bootTheme(): void
    {
        $this->app->make(PageParts::class)->provide(Masthead::class, MastheadProvider::class);
        $this->app->make(MenuLocations::class)->register(['journal-primary' => 'Journal header']);
    }
}

5. Bring the styles ​

Take the template's compiled CSS as it is — Bootstrap included — and put the theme's own rules in a second file. Updating the template later is then a file copy. Remove only what you cannot ship: Bureau's copy has the two url(...) photos taken out.

Replace the template's JavaScript with Vue. A Bootstrap template uses its script for a few things, each a few lines in a component:

Template behaviourIn the theme
The navbar collapse (data-bs-toggle)A ref toggling the show class
The navbar that hides on scroll, or darkens (navbar-shrink)A scroll listener in the layout
ScrollspyThe last section[id] above 40 % of the window lights up its menu item
A modalA component in <Teleport to="body"> with the template's classes, closed by Escape
An icon font (Font Awesome)Inline SVG — Bootstrap Icons are MIT

No jQuery, no Bootstrap JS, no new npm package.

6. Turn the markup into templates ​

Copy the template's HTML into the Vue components and replace each piece of sample content with a prop. Every template gets the parts the provider lists: content (the record, term or author), posts (a list), site, menus, menuLocations, shortcodes…

vue
<Masthead variant="post" :image="masthead.image" :title="content.title" :subtitle="masthead.subtitle">
    <template #meta>
        <PostMeta :author="content.author" :date="content.formattedDate" />
    </template>
</Masthead>

What the template needs but the site does not give directly is a page part of the theme, computed on the server: Journal's Masthead picks the header photo (the record's, else the blog page's, else the front page's); its LatestPosts lists the newest posts under a static front page; Bureau's Sections turns the front page's fields into what the landing draws. A part is also what a headless frontend gets as JSON.

A few rules:

  • Titles and excerpts carry HTML entities (&amp;). Decode them as text; never put them into the page with v-html.
  • Links to the site go through Inertia; links elsewhere stay plain. A menu item /#team scrolls when the section is on the page and opens the front page at it otherwise — Inertia scrolls to the anchor after a visit.
  • A page's text may hold shortcodes: kitloom leaves <div data-shortcode="…"> where each one was. The theme draws the ones it knows — the contact form at least — and keeps the HTML around them.
  • Give every layout a single root element: Inertia passes the page props to the template, and Vue warns about props it cannot place.

7. Fields, when WordPress has no place ​

Bureau's landing sections are an ACF-style field group, written by a theme migration that runs on activation:

php
// database/migrations/…_add_bureau_front_page_fields.php
public function up(): void
{
    FrontPageFields::install();   // GroupWriter::save() of the group, unless it is there
}

The group is the array Advanced Custom Fields exports — one tab per section, page_type == front_page as its location — so the admin shows it on that page only, in Filament and in Nova alike. The page part reads the values with $page->field('services'); images, posts and the form come back as ids, and the part turns them into pictures (Images), list entries (Entries) and the form's data.

8. A demo ​

A {theme}:demo command fills the site with neutral content written the way the admin writes it — PostWriter, TermWriter, MenuEditor, SettingWriter, $page->setFields() — so the theme can be seen before real content exists. Make the pictures yourself (SVG drawn in PHP is enough) rather than shipping photos.

9. Build and check ​

bash
npm run build:themes                  # or: APP_THEME=Journal npx vite build
php artisan journal:demo

Activate the theme in Appearance → Themes, then go through every template and every admin field once. The bundled themes keep that check as tests: the pages and their props on the demo (tests/ of the theme, run from a skeleton with the theme active), and the admin form of the front page in each admin.

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