Create your first theme
Go from an empty folder to a rendering Salieno Core theme using the scaffolder or the three required files by hand.
On this page
Create your first theme#
This article takes you from an empty repository to a theme that renders on the storefront. You will use the built-in scaffolder for the fast path, then build the same thing by hand so you understand exactly what the engine requires. For the bigger picture behind these files, read Theme overview first; for a field-by-field tour of the manifests, follow the "Next" pointer at the end.
A theme is just a directory under resources/views/themes/{name}/. Core privileges none of them — the active theme is whatever the operator has stored, so your directory has to be complete enough to render the pages it claims. The good news is that "renders at all" needs only three files.
The fast path: scaffold#
Core ships an Artisan command that writes a valid, installable theme for you. Run it from the Laravel app root (core/public_html/core):
php artisan template:scaffold acmeBy default this clones the first-party `salieno` theme into resources/views/themes/acme/, rewrites the new theme.json with your name and version 1.0.0, and publishes its assets to public/assets/themes/acme/. You get a complete, styled theme you can activate immediately and then edit down to your own design.
If you would rather start from nothing and add views one at a time, pass --minimal:
php artisan template:scaffold acme --minimalThe minimal skeleton writes only the essentials — theme.json, sections.json, layouts/app.blade.php, a starter assets/css/theme.css, and a README.md. You can also clone a different installed theme with --from={name}. The command refuses a name that already exists, and lowercases the name to the allowed a-z 0-9 _ - set.
When it finishes it prints where the theme and its published assets landed, and reminds you to edit theme.json and activate it in the admin panel.
The three required files, by hand#
You do not need the scaffolder. The engine's completeness check (App\Services\ThemeService) requires exactly these on disk:
| File | Why it is required |
|---|---|
layouts/app.blade.php | The page shell every page and Livewire component wraps itself in. |
sections.json | The ordered section list the page builder renders. |
theme.json | The runtime manifest — must contain at least name and version. |
Miss any one and the theme is reported invalid and cannot be activated. Here is the smallest set that actually renders.
`resources/views/themes/acme/theme.json` — the runtime manifest. name and version are the only hard requirements; the rest is metadata Core reads for the gallery and the mode switcher:
{
"name": "Acme",
"version": "1.0.0",
"author": "Your Name",
"description": "A custom Salieno Core storefront theme.",
"preview": "preview.jpg",
"supports": {
"sections": true,
"dark_mode": false,
"rtl": false
},
"colors": {
"primary": "#4F46E5",
"secondary": "#0A0A0A"
}
}`resources/views/themes/acme/sections.json` — the ordered list of section slugs the home/product pages render, under the top-level secs key. A slug here that has no matching sections/{slug}.blade.php is simply skipped, so you can list your roadmap and fill it in later:
{
"secs": ["hero", "features", "pricing", "faq", "cta"]
}`resources/views/themes/acme/layouts/app.blade.php` — the shell. Keep it self-contained for now: pull your own precompiled CSS with themeAsset(), read a colour from the manifest with themeMeta(), and host page content in the Livewire/Blade slot. Note the @hasSection bridge — controllers render pages that @yield('content'), while Livewire auth pages pass a {{ $slot }}, and this one line serves both:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="scroll-smooth">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ gs('site_name') }}</title>
{{-- Ship precompiled CSS in assets/ — no @vite or @tailwind at runtime. --}}
<link rel="stylesheet" href="{{ themeAsset('css/theme.css') }}">
<style>:root { --primary: {{ themeMeta('colors.primary', '#4F46E5') }}; }</style>
@livewireStyles
</head>
<body>
<main id="main-content">
@hasSection('content')
@yield('content')
@else
{{ $slot ?? '' }}
@endif
</main>
@livewireScripts
</body>
</html>That is a theme. themeAsset('css/theme.css') resolves to a public URL with an automatic ?v=<mtime> cache-buster, so add a real stylesheet at assets/css/theme.css (plain, precompiled CSS — never @tailwind/@apply, which only work in a bundled build). Assets and their security allowlist are covered in CSS, JavaScript & images; the shell itself, including the frontend layout and the Livewire slot bridge, is covered in Layouts and the page shell.
Your directory now looks like this:
resources/views/themes/acme/
├── theme.json
├── sections.json
├── layouts/
│ └── app.blade.php
└── assets/
└── css/
└── theme.cssActivate it and see the storefront#
For local development you do not package or upload anything — the folder being present in resources/views/themes/ is enough. Publish its assets and turn it on:
- Publish the theme's
assets/into the public directory. The scaffolder does this for you; by hand, re-run the scaffold's publish step or use the admin panel's activate flow, which publishes on activation. - Open Admin → Frontend → Themes. Your theme appears in the gallery, read straight from
theme.json(name, version, description, preview). - Activate it. Core stores it as the operator's choice — every
themes.*view now resolves tothemes.acme.*, and the helperactiveThemeName()returnsacme. - Visit the storefront. The home page renders through
layouts/app.blade.php, and the sections you have provided appear in the order listed insections.json.
Because you have not shipped home.blade.php or any sections/*.blade.php yet, most of the page will be empty — that is expected. From here you add real pages (Pages and how they resolve), real sections (Building sections), and the auth screens (Authentication pages). A theme must ship every public view it needs, because with no bundled default there is nothing to fall back to but a neutral Core holding page.
Next#
You now have a theme that renders. Before you build it out, understand what each file is for and how the two manifests differ — continue to Theme anatomy & the two manifests.