> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qomicex.top/llms.txt
> Use this file to discover all available pages before exploring further.

# Theme system

## Theme system

The Qomicex Theme system (theme system)uses a **three-tier primitive → semantic → component semantic token** structure, carried by `.qtheme` theme packages, so the launcher and plugin UI reskin instantly and completely, **with no plugin-ui dist rebuild**.

## The three-tier semantic model

```text theme={null}
primitives (raw palette, optional for theme packages)
  └─ concrete color values (HSL/hex), for derivation only
semantic (semantic layer, authored by .qtheme authors)
  └─ semantic roles: background/foreground/accent/status… (dot-separated naming)
component (component layer equals CSS variables, the only layer read by var())
  └─ --background / --accent-foreground … (flat, mapping one-to-one with index.css)
```

* **v1 scope**: `.qtheme` expresses the semantic/component tiers directly (the flat `--*` variables); primitive derivation and the `theme.mjs` computation tier are deferred to v2
* **plugin-ui components consume only `var(--*)`**, with zero inline color values → switching themes reskins every component instantly, eliminating the stale "change a component, rebuild dist" problem

## Semantic token naming

Dot-separated naming, where `.` normalizes to `-` (`background.emphasis` → `--background-emphasis`).

### Color tokens (the standard component-level set = everything plugin-ui consumes)

| token (theme.json key) | CSS variable | Default (dark) | Meaning |
| - | - | - | - |
| background | `--background` | `230 20% 6%` | Page background |
| foreground | `--foreground` | `220 20% 93%` | Primary text |
| card / card-foreground | `--card` / `--card-foreground` | `228 18% 10%` / `220 20% 93%` | Card |
| popover / popover-foreground | `--popover` / `--popover-foreground` | `228 18% 10%` / `220 20% 93%` | Popover |
| primary / primary-foreground | `--primary` / `--primary-foreground` | `142 71% 48%` / `230 20% 6%` | Primary accent |
| secondary / secondary-foreground | `--secondary` / `--secondary-foreground` | `228 18% 14%` / `220 20% 93%` | Secondary |
| muted / muted-foreground | `--muted` / `--muted-foreground` | `228 10% 18%` / `228 8% 55%` | Muted |
| accent / accent-foreground | `--accent` / `--accent-foreground` | `228 18% 14%` / `220 20% 93%` | Accent background |
| destructive / destructive-foreground | `--destructive` / `--destructive-foreground` | `0 84% 60%` / `220 20% 93%` | Destructive |
| border | `--border` | `228 14% 21%` | Borders |
| input | `--input` | `228 14% 21%` | Input fields |
| ring | `--ring` | `142 71% 48%` | Focus ring |

**Extended semantics** (optional in v1, emitted as `--foreground-accent` and so on, for forward compatibility): `foreground.accent`, `foreground.muted`, `foreground.destructive`, `background.elevated`, `background.emphasis`, `background.sunken`, `border.strong`, `border.accent`, `accent.hover`, `accent.active`, `status.success`, `status.warning`, `status.error`.

### Non-color tokens

| token | CSS variable | Default | Description |
| - | - | - | - |
| radius | `--radius` | `0.625rem` | Corner radius |
| glass-blur | `--glass-blur` | `18px` | Frosted-glass blur |

`space.*`, `font.family/size/weight`, `motion.*`, and `shadow.*` belong to the v2 non-color extensions.

## `.qtheme` package format

```text theme={null}
.qtheme (zip or directory)
  theme.json        <- color theme (v1, see the schema below)
  theme.css         <- optional, namespaced injection (--qtx- prefix / scoped), v2
  theme.mjs         <- optional, CSS-in-JS computation layer (Sandbox), v2
  icon-theme.json   <- icon theme (v1.5, see below)
  fonts/            <- font contribution，v1.5 (injected via contributes.fontLinks)
```

### theme.json schema v1

```json theme={null}
{
  "schemaVersion": 1,
  "id": "catppuccin-mocha",
  "name": "Catppuccin Mocha",
  "type": "color",
  "scheme": "dark",
  "colors": {
    "background": "240 23% 9%",
    "accent.foreground": "240 23% 9%"
  },
  "radius": 10,
  "glassBlur": 18
}
```

| Field | Type | Required | Description |
| - | - | - | - |
| `schemaVersion` | number | ✅ | Currently only `1` |
| `id` | string | ✅ | `^[a-z0-9][a-z0-9-]*$` |
| `name` | string | ✅ | Display name |
| `type` | string | ❌ | Currently only `"color"` |
| `scheme` | string | ❌ | `"dark"` / `"light"` |
| `colors` | object | ✅ | Key = semantic token (may be dot-separated), value = HSL color string |
| `radius` | number | ❌ | Corner radius, in rem |
| `glassBlur` | number | ❌ | Frosted-glass blur, in px |

An invalid theme.json is rejected by `src/theme/schema.ts` with a friendly error.

## Theme manager

The theme manager (`src/theme/manager.ts`)provides:

* **`applyTheme(json)`**: validate, then inject CSS variables into `:root` → every component reskins instantly; persists the theme id to `localStorage`
* **`clearTheme()`**: remove the custom theme and fall back to the settings layer light/dark/preset
* **`restoreSavedTheme()`**: restore the registered theme at startup
* **`useTheme()`**: React Hook that subscribes to change events and drives re-rendering
* **`registerTheme()`**: register the bundled built-in `.qtheme` packages (including the four Catppuccin presets)

## Theme contributions (contributes)

Plugins and theme packages can declare three kinds of contributions through manifests; v1 supports color themes only:

| Type | Description | Status |
| - | - | - |
| **Color theme** | Full token mapping, with plugin UI and the launcher sharing one source → native controls reskin globally | ✅ Implemented in v1 |
| **Icon theme** | icon-theme.json: `{ "plugin-ui iconName": "path/svg \| codepoint \| url" }` | ✅ Implemented in v1.5 |
| **Font and ligature contribution** | Font resources + scoped `font-feature-settings` | ✅ Implemented in v1.5 |

## Icon theme (icon-theme.json)

The Icon theme (icon theme)is a new file in the `.qtheme` package, alongside `theme.json`, that provides override mappings for icons such as plugin sidebar entries and menu items.

### Format

```json theme={null}
{
  "schemaVersion": 1,
  "fonts": ["MyIconFont"],
  "icons": {
    "fa-solid fa-robot": { "type": "svg", "path": "M0 0h24v24H0z..." },
    "fa-solid fa-cog": { "type": "char", "codepoint": "⚙", "fontFamily": "MyIconFont" },
    "my-custom": { "type": "url", "url": "https://example.com/icon.png" }
  }
}
```

### Fields

| Field | Type | Required | Description |
| - | - | - | - |
| `schemaVersion` | number | ✅ | Currently only `1` |
| `fonts` | string\[] | ❌ | List of `font-family` names this theme depends on (declarative; font resources must be injected through `contributes.fontLinks`) |
| `icons` | object | ✅ | Icon mappings. Key = FontAwesome class name (such as `fa-solid fa-robot`) or a custom icon ID; value = icon definition object |

### Icon definition object

| Field | Type | type=svg | type=char | type=url |
| - | - | - | - | - |
| `type` | `"svg"` / `"char"` / `"url"` | ✅ | ✅ | ✅ |
| `path` | string | ✅ `<path d="...">` | — | — |
| `codepoint` | string | — | ✅ Character or entity | — |
| `fontFamily` | string | — | ❌ Custom font name | — |
| `url` | string | — | — | ✅ Image URL |

### Render priority

The `PluginIcon` component renders in this order:

* Look up the `src` key in the current theme's `icon-theme.json` → if it matches, render SVG/char/URL
* No match and `src` is a URL → `<img>`
* No match and `src` is a FontAwesome class name → `<FontAwesomeIcon>`
* No match at all → `null`

### Registration

Icon themes are registered through the theme manager and bound to color themes:

* `registerTheme(colorJson, iconJson?)` — register a color and icon theme together
* `registerIconTheme(themeId, iconJson)` — bind icons separately to an already registered color theme

Get the active theme's icon mappings with `getActiveIconTheme()` or `useIconTheme()` (a React Hook). The mappings update automatically when the theme switches.

## Font and ligature contribution

### manifest declaration

Plugin manifests declare font resources through the `contributes` field:

```json theme={null}
{
  "contributes": {
    "fontLinks": ["https://cdn.example.com/my-icons.css"]
  }
}
```

* `fontLinks` (string\[]): list of font CSS/CDN URLs. When the plugin activates, the launcher automatically injects `<link rel="stylesheet">` into `<head>`; it removes them when the plugin deactivates.
* You can use the `font-feature-settings` scoping convention to enable ligatures in specific areas (see below).

### Ligature scoping convention

The launcher predefines the `.font-ligatures` class (the plugin-internal equivalent is `.p-font-ligatures`); apply it to areas that need font ligatures (such as console logs or version diffs):

```html theme={null}
<div class="font-ligatures">-> => <!-- ligatures active --></div>
```

The corresponding CSS:

```css theme={null}
.font-ligatures {
  font-feature-settings: "liga" 1, "calt" 1;
  font-variant-ligatures: common-ligatures contextual;
}
```

Inside plugins, use `.p-font-ligatures` for the same effect.

## Relationship with plugin-ui

**Hard rule**: plugin-ui components may consume tokens only through `var(--*)`; inline color values are forbidden. An audit confirms `packages/plugin-ui/src/` has zero inline color values in total → switching themes reskins instantly with no dist rebuild.

## Preset theme

The launcher bundles four Catppuccin Preset theme (preset theme)packages (latte / frappe / macchiato / mocha), already migrated to the new token naming. When you switch presets or apply a custom `.qtheme`, every UI component that consumes `var(--*)` responds immediately.

## Plugin-driven Theme override

Plugins can dynamically override the launcher UI's CSS variables through `__PLUGIN_API__`, reskinning at runtime without shipping a `.qtheme` package.

### Priority levels

```text theme={null}
themeColor inline style (--primary/--ring/--primary-foreground, highest)
  └─ applyThemeOverride (:root:root, selector specificity 0,2,0)
    └─ .qtheme theme (:root)
      └─ index.css @layer base (lowest)
```

* The `<style>` injected by `applyThemeOverride` has higher specificity than `.qtheme` and `@layer base`
* If the user has set a theme color (the inline style from `themeColor.ts`), inline styling takes over `--primary`/`--ring`/`--primary-foreground`, so overrides of those three do not take effect

### API

| Method | Permission | Description |
| - | - | - |
| `getThemeColor` | `config:read` | Get the current seed color hex (`#rrggbb` or `null`) |
| `applyThemeOverride(vars)` | `config:write` | Override CSS variables (HSL format, whitelisted tokens) |
| `clearThemeOverride()` | `config:write` | Remove the override styles |

### Usage example

```js theme={null}
const __PLUGIN_API__ = window.__PLUGIN_API__

// read the current accent color and adjust the plugin UI
const seedColor = await __PLUGIN_API__.call('getThemeColor')
if (seedColor) {
  document.getElementById('my-plugin-card').style.borderColor = seedColor
}

// apply a custom theme override
await __PLUGIN_API__.call('applyThemeOverride', {
  'background': '240 23% 9%',
  'foreground': '220 20% 93%',
  'primary': '262 83% 58%',
  'accent': '262 83% 58%'
})

// clear it when the user cancels the custom theme
await __PLUGIN_API__.call('clearThemeOverride')
```

### Security limits

* **Token whitelist**: only 30 predefined semantic tokens are allowed (`background`, `foreground`, `primary`, `accent`, `border`, `status-*`, and so on)
* **HSL format validation**: values must strictly match `H S% L%` (such as `142 71% 48%`), rejecting arbitrary CSS injection
* Tokens outside the whitelist and invalid formats are silently ignored

### Lifecycle

* `applyThemeOverride` can be called repeatedly; the latest call overrides earlier ones
* **Overrides ARE cleared automatically when the plugin is disabled**: the disable path unconditionally calls `clearThemeOverride`, so the theme falls back to the active `.qtheme` or the built-in light/dark theme. You do not need to clean up in a disable hook; call `clearThemeOverride` explicitly only if you want to drop the override while the plugin stays enabled
* `clearThemeOverride` removes only the override styles; it does not affect `.qtheme` or the built-in light/dark themes


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.