# 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.

Source: https://featherpanel.com/docs/plugins/themes/

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

```json
{
  "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

```json
{
  "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:

```css
.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](https://featherpanel.com/docs/plugins/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.

```json
{
  "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

- [Frontend and Power SDK](https://featherpanel.com/docs/plugins/frontend/)
- [Widget slots](https://featherpanel.com/docs/plugins/widgets/)
- [Examples](https://featherpanel.com/docs/plugins/examples/)

## 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.
