Theme packs and UI packs
Restyle FeatherPanel with a plugin theme pack (design tokens and CSS) or change its layout with a UI pack that hides, replaces or adds parts of the shell and pages.
On this page
Two plugin features change how the panel looks without touching its code:
| Theme pack | UI pack | |
|---|---|---|
| File | Frontend/theme.json (+ CSS) | Frontend/ui.json |
| Changes | Colours, accents, background, extra CSS | Layout: hide, replace or add parts of the shell and pages |
| Picked in | Preferences > Appearance > Theme packs | Preferences > Appearance > UI packs |
| Admin default and lock | app_theme_pack_default, app_theme_pack_lock | app_ui_pack_default, app_ui_pack_lock |
Defaults use the form pluginId:packId. Create either kind from Admin > Dev > Plugins with the Theme pack or UI
pack template.
Theme packs
{
"id": "default",
"name": "Ocean",
"preview": null,
"tokens": {
"light": {
"background": "210 40% 98%",
"foreground": "222 47% 11%",
"card": "0 0% 100%",
"card-foreground": "222 47% 11%",
"primary": "199 89% 48%",
"primary-foreground": "0 0% 100%",
"muted": "210 40% 96%",
"muted-foreground": "215 16% 47%",
"border": "214 32% 91%",
"ring": "199 89% 48%"
},
"dark": {
"background": "222 47% 7%",
"foreground": "210 40% 98%",
"card": "217 33% 12%",
"card-foreground": "210 40% 98%",
"primary": "199 89% 48%",
"primary-foreground": "0 0% 100%",
"muted": "217 33% 17%",
"muted-foreground": "215 20% 65%",
"border": "217 33% 17%",
"ring": "199 89% 48%"
}
},
"accents": ["custom:#0ea5e9"],
"defaults": { "backgroundType": "aurora" },
"css": "theme.css"
}
| Field | Notes |
|---|---|
id | Pack id inside the plugin. Defaults to default. |
name | Shown in the picker. |
preview | Optional preview image URL. |
tokens.light, tokens.dark | Design tokens as HSL values without hsl(). |
accents | Accent colours offered with the pack. |
defaults | Appearance defaults applied with the pack, such as backgroundType. |
css | A CSS file next to theme.json, applied only while the pack is selected. |
One plugin can ship several theme packs by wrapping them in { "themes": [ ... ] }.
UI packs
{
"id": "default",
"name": "Compact layout",
"theme": null,
"shell": {
"replace": {
"shell.navbar": { "component": "navbar.html" }
}
},
"pages": [
{ "match": "/dashboard", "replace": { "component": "home.html" } }
],
"hide": ["toolbar.server-header"],
"actions": [
{
"slot": "toolbar.server-console",
"label": "Quick tools",
"component": "quick-tools.html",
"priority": 10
}
]
}
| Field | Notes |
|---|---|
shell.replace | Map of shell slot to a replacement. |
pages | Page takeovers. match is the route; set hide: true or a replace. |
hide | Slot ids to hide. |
actions | Buttons added to a toolbar slot. label and slot are required; the optional component opens in the panel. |
theme | Optional theme pack to pair with this UI pack. |
Several packs fit in one file as { "packs": [ ... ] }.
Slots
Slot ids used by the panel:
| Slot | Part of the panel |
|---|---|
shell.navbar | Top navigation bar |
shell.sidebar | Sidebar |
shell.page-header | Page header |
shell.main | Main content area |
shell.background | Page background |
toolbar.server-console | Toolbar on the server console |
toolbar.server-header | Server header toolbar |
Each slot is wrapped in an element with class="fp-plugin-slot" and data-fp-slot="<id>", so theme CSS can target it:
.fp-plugin-slot[data-fp-slot="shell.navbar"] {
border-bottom: 1px solid hsl(var(--border));
}
Replacements
A replacement (in shell.replace, pages[].replace or overrides.json) takes one of:
component: an HTML file insideFrontend/Components/, shown in an iframe. Plugin pages and iframes are covered on Frontend.remote: a JavaScript module insideFrontend/Components/whose default export (or a namedPluginSlotexport) is a React component. It is imported into the panel page and getsslotandpluginas props.hide: true: remove the slot.
Paths containing .. are ignored.
overrides.json
Frontend/overrides.json uses the same hide, replace and actions entries, but it applies all the time, from
every installed plugin, whether or not a UI pack is selected. Use it for small, targeted changes. replace is a list
of entries with a slot field; when two plugins replace the same slot, the one loaded later wins.
{
"hide": [],
"replace": [
{ "slot": "shell.background", "component": "background.html" }
],
"actions": []
}
Admins can hide individual UI pack pages and actions from the plugin’s Visibility tab.
Related
Frequently asked questions
Yes. Set app_theme_pack_default to pluginId:themeId and turn on app_theme_pack_lock in the admin settings. Users then see the pack as locked by the administrator. UI packs have the same pair, app_ui_pack_default and app_ui_pack_lock.
No. The CSS file of a theme pack is only applied while that pack is selected.