View .md

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 packUI pack
FileFrontend/theme.json (+ CSS)Frontend/ui.json
ChangesColours, accents, background, extra CSSLayout: hide, replace or add parts of the shell and pages
Picked inPreferences > Appearance > Theme packsPreferences > Appearance > UI packs
Admin default and lockapp_theme_pack_default, app_theme_pack_lockapp_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"
}
FieldNotes
idPack id inside the plugin. Defaults to default.
nameShown in the picker.
previewOptional preview image URL.
tokens.light, tokens.darkDesign tokens as HSL values without hsl().
accentsAccent colours offered with the pack.
defaultsAppearance defaults applied with the pack, such as backgroundType.
cssA 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
    }
  ]
}
FieldNotes
shell.replaceMap of shell slot to a replacement.
pagesPage takeovers. match is the route; set hide: true or a replace.
hideSlot ids to hide.
actionsButtons added to a toolbar slot. label and slot are required; the optional component opens in the panel.
themeOptional theme pack to pair with this UI pack.

Several packs fit in one file as { "packs": [ ... ] }.

Slots

Slot ids used by the panel:

SlotPart of the panel
shell.navbarTop navigation bar
shell.sidebarSidebar
shell.page-headerPage header
shell.mainMain content area
shell.backgroundPage background
toolbar.server-consoleToolbar on the server console
toolbar.server-headerServer 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 inside Frontend/Components/, shown in an iframe. Plugin pages and iframes are covered on Frontend.
  • remote: a JavaScript module inside Frontend/Components/ whose default export (or a named PluginSlot export) is a React component. It is imported into the panel page and gets slot and plugin as 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.

Frequently asked questions

Can I force one theme for every user?

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.

Does a theme pack's CSS apply all the time?

No. The CSS file of a theme pack is only applied while that pack is selected.