Tree
Expected structure
data/themes/acme/
|- theme.json
|- style.css
|- slots/
| |- head.php
| |- sidebar.php
| |- topbar.php
| `- footer.php
|- parts/
| |- sidebar_logo.php
| |- sidebar_nav.php
| |- sidebar_user.php
| |- topbar_title.php
| `- topbar_notifications.php
`- pages/
`- dashboard.php
Everything is optional except theme.json. The horizon theme in src/themes_builtin/horizon/ is the concrete reference implementation.
Stylesheet
How to write style.css
The engine injects your CSS only when the theme is active, but it does not rewrite selectors for you. Your rules should therefore be scoped with the active theme attribute.
[data-theme="acme"] .sidebar {
width: 100%;
border-right: 0;
border-bottom: 1px solid var(--border);
}
[data-theme="acme"] .main {
margin-left: 0;
}
Without the
[data-theme="acme"] prefix, your CSS may either miss the shell entirely or create collisions with other themes.Overrides
Supported slots, parts and pages
| Type | Supported names | Use |
|---|---|---|
| Slots | head, sidebar, topbar, footer | Replace a large shell area. |
| Parts | sidebar_logo, sidebar_nav, sidebar_user, topbar_title, topbar_notifications | Adjust a smaller subsection without copying the whole slot. |
| Pages | dashboard, then other ids reserved by the engine | Render a complete custom screen for a supported page. |
In practice, the page extension point currently wired by the core is pages/dashboard.php. Other names are prepared at engine level, but not all called yet.
Guardrails
Important constraints and limits
style.cssgoes through a security filter and remains size-limited.- Only a fixed set of slot, part and page names is allowed.
- An advanced theme runs real PHP, so it must be treated as trusted application code.
- A theme cannot invent new extension points if the engine does not already know them.