Developer Theme Structure

Theme Structure

A theme is a folder of Blade templates that decides how a site looks. Sites in theme mode pick their theme in the admin, and one installation can hold any number of themes. This article covers the folder anatomy and creating a theme of your own.

Where themes live

Themes sit in the application root under themes/:

themes/
└── default/
    ├── theme.json         (required)
    ├── preview.png        (optional)
    ├── assets/            (your sources, if you build any)
    ├── dist/              (built CSS/JS, served to the browser)
    └── views/
        ├── layouts/app.blade.php
        ├── default.blade.php
        ├── page/show.blade.php
        ├── post/show.blade.php
        ├── archive/show.blade.php
        ├── preview/nav.blade.php
        └── blocks/
            ├── text.blade.php
            ├── hero.blade.php
            └── ...

A directory becomes a theme only when it contains a valid theme.json. Directories without one are ignored completely — no view namespace, and no entry in the theme picker. Every recognised theme is registered as a Blade view namespace: themes/default/views/page/show.blade.php is addressable as themes.default::page.show. The theme list in the admin (site creation and Settings → Appearance) is the list of valid manifests.

The manifest

theme.json is required. Only name is mandatory, and it must be a non-empty string — everything else is optional:

{
    "name": "Default",
    "version": "1.0.0",
    "author": "Laravix",
    "description": "The theme shipped with Laravix.",
    "screenshot": "preview.png"
}
  • name — the label shown in the theme picker. Without it, the theme doesn't exist as far as Laravix is concerned.
  • version and author — rendered under the name in the picker as a version · author byline.
  • description — free-form text for your own use.
  • screenshot — path to a preview image relative to the theme folder. If you leave it out, Laravix looks for preview.svg, preview.webp, preview.png or preview.jpg in the theme root, in that order.

Views and the fallback to the default theme

A theme's view namespace resolves in two places, in order:

  1. themes/{yourtheme}/views
  2. themes/default/views

That means a theme only has to contain the templates it actually changes. If your theme has no post/show.blade.php, the default theme's is rendered — with your layouts/app.blade.php if you overrode that one. A theme consisting of nothing but theme.json and a custom layout is perfectly valid.

The default theme itself has no fallback; it is the bottom of the stack.

  • views/{type}/show.blade.php — the template for a content type (page, post, archive, plugin types). When neither your theme nor the default theme has one, views/default.blade.php renders instead — so a theme works even for content types it never heard of.

  • views/layouts/app.blade.php — the shared layout: <head> with SEO tags, header navigation, @yield('content'), footer. The default theme also defines @stack('head') and @stack('scripts'), which the builder content view uses to inject styles and scripts.

  • views/blocks/{key}.blade.php — one view per classic block type (text, hero, cards, columns, button, button_group, divider). Used when content was built from the classic block array rather than the visual builder — see Templates and View Data.

  • views/preview/nav.blade.php — optional partial used by the admin's live navigation preview to render just the navigation part.

  • views/docs/…, views/changelog/… — plugins first look for their views inside the active theme (themes.{theme}::docs.show) and fall back to their own bundled views, so a theme can restyle plugin pages.

Theme assets

Everything the browser has to download lives in the theme's dist/ directory. It is not served directly — public/themes/{theme} is a symlink pointing at it:

php artisan laravix:theme:link

laravix:install runs this for you. Run it by hand after you add a new theme, and add --force to replace links that already exist. Themes without a dist/ directory are skipped.

Reference a built file from a template with Laravix::themeAsset(), which returns null when the file isn't there:

@if ($themeStylesheet = \Laravix\Cms\Laravix::themeAsset('app.css', $site->theme))
    <link rel="stylesheet" href="{{ $themeStylesheet }}">
@endif

The returned URL carries a ?v= stamp taken from the file's modification time, so a rebuilt asset busts the browser cache on its own.

How you produce dist/ is up to you — Vite, esbuild, Tailwind's CLI, or hand-written CSS copied in. Laravix only cares that the built files end up there. Keep your sources in assets/ (or anywhere outside dist/) so a rebuild never overwrites them.

Note: The default theme ships no dist/ directory. It loads the stylesheet bundled with the core (Laravix::asset('app.css')public/vendor/laravix/app.css) plus Font Awesome from a CDN, and then layers a theme stylesheet on top only if one exists. Your own theme is free to skip the core stylesheet entirely and bring its own.

Creating a theme

Because of the fallback, you start from an empty folder rather than a copy:

  1. Create the folder and its manifest:

    mkdir -p themes/mytheme
    
    { "name": "My Theme", "version": "1.0.0", "author": "You" }
    
  2. Override only what you want to change. Copy individual files out of themes/default/views as you need them — starting with layouts/app.blade.php — and leave the rest to the fallback.

  3. If you build CSS or JS, output it into themes/mytheme/dist/ and run php artisan laravix:theme:link.

  4. Optionally drop in a preview.png.

  5. In the admin, open Settings → Appearance and select the new theme. The change is live immediately.

Copying the whole default theme still works, but it means every future fix in the default templates stops reaching you.

Themes and upgrades

php artisan laravix:upgrade never touches themes/ — the folder is yours. The default theme is published only once, during installation (the installer skips publishing when themes/default already exists). If you want a pristine copy of the default theme later, publish it manually:

php artisan vendor:publish --tag=laravix-theme

This writes theme.json, views/ and assets/. It does not create a dist/ — the default theme has none.

Laravix Documentation · 25.08.2026
Star on GitHub