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

TypeSupported namesUse
Slotshead, sidebar, topbar, footerReplace a large shell area.
Partssidebar_logo, sidebar_nav, sidebar_user, topbar_title, topbar_notificationsAdjust a smaller subsection without copying the whole slot.
Pagesdashboard, then other ids reserved by the engineRender 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.css goes 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.