Themes
Theme packages are plugins. Ship the same .hcp layout, require the ui permission, declare slots in contributes.themes, and set "categories": ["themes"] when the package should appear under File → Themes. Users pick an active theme from View → Theme or Settings → General → Appearance.
See Theme plugins for manifest fields and Marketplace → Theme listings for catalog publishing.
Custom appearance themes extend the built-in Light, Dark, System, and High contrast options in Settings → General. Plugin themes appear in the same dropdown once registered.
HarborClient styles the app with --mac-* CSS custom properties defined in src/renderer/src/styles.css. When a plugin theme is active, the host sets data-theme="plugin-<pluginId>-<themeId>" on <html> and applies your token overrides or injected stylesheet. Built-in light/dark/system behavior is unchanged when a builtin theme is selected.
Themes can be registered two ways:
- JavaScript — call
registerTheme(hc, theme)orhc.themes.register(theme)fromactivate(). - JSON import — point the manifest contribution at a Theme Designer export file (see JSON theme import). No
activate()call is required for those entries.
Requires the ui permission. For JavaScript registration, call registerTheme(hc, theme) or hc.themes.register(theme) from activate() — registration disposables are tracked automatically.
JSON theme import
Declare an import path on the contribution to ship a palette without JavaScript:
{
"contributes": {
"themes": [
{
"id": "solarized",
"title": "Solarized Dark",
"type": "dark",
"import": "exported.json"
}
]
}
}The file must be a harborclientExport: "theme" envelope — the same shape as File → Themes → Designer export:
{
"harborclientVersion": 1,
"harborclientExport": "theme",
"title": "Solarized Dark",
"type": "dark",
"theme": {
"surface": "#002b36",
"accent": "#268bd2"
},
"stylesheet": "styles.css"
}| Field | Description |
|---|---|
harborclientVersion | Always 1 |
harborclientExport | Always "theme" |
theme | Token overrides without the --mac- prefix |
title / type | Present in the export; manifest id / title / type remain authoritative |
stylesheet | Optional plugin-relative CSS filename, or inlined CSS after first read |
On first read, if stylesheet points at an existing CSS file inside the plugin directory, HarborClient inlines the CSS text into the JSON on disk. Later reads treat the value as already-inlined CSS (idempotent). Theme-only packages can omit renderer and main entirely.
See the Solarized theme example and Theme plugins.
registerTheme(hc, theme)
Signature: (hc: PluginContext, theme: ThemeContribution) => Disposable
Convenience wrapper around hc.themes.register. Prefer this for single-theme plugins.
import { registerTheme } from '@harborclient/sdk';
registerTheme(hc, {
id: 'solarized',
title: 'Solarized Dark',
type: 'dark',
colors: { surface: '#002b36' }
});Use defineTheme(theme) when you want to define the theme object in a separate module with full ThemeContribution typing.
Theme color tokens
Override any of these keys in colors. Each maps to --mac-<token> on the document root.
| Token | Used for |
|---|---|
surface | Main content background |
header | Top header strip (sidebar search + request tab bar) |
page-header | Page title header background (PageHeader) |
page-header-text | Page title header primary text |
page-header-muted | Page title header description and decorative icons |
sidebar | Left sidebar background |
sidebar-toolbar | Sidebar/footer toolbar strip background |
sidebar-rail | Activity rail background |
sidebar-rail-active | Active/hover activity rail section fill |
sidebar-rail-text | Activity rail icons and labels |
sidebar-rail-separator | Activity rail hairline between item groups |
sidebar-section | Sidebar section headers |
sidebar-section-text | Sidebar section header labels and chevrons |
footer | Footer status bar background |
footer-text | Footer primary text |
footer-muted | Footer de-emphasized text |
footer-icon-active | Active footer icon toggle color |
toolbar-action-active | Pressed sidebar toolbar action icon color |
breadcrumb-background | Request editor breadcrumb bar track |
breadcrumb-segment | Breadcrumb chevron segment fill |
git-staged | Git-backed request names staged for commit |
git-uncommitted | Git-backed request names with tracked unstaged changes |
git-unstaged | Git-backed request names not yet added to the repository |
control | Panels, inputs, footer bar |
field | Input field fill |
separator | Borders and dividers |
text | Primary text |
text-secondary | Secondary labels |
muted | De-emphasized text |
accent | Links, focus rings, primary actions |
selection | Selected row / highlight fill |
tab-bar | Request editor tab bar strip background |
tab-active | Active request/editor tab fill |
tab-inactive | Inactive request/editor tab fill |
tab-hover | Inactive request/editor tab hover and focus-visible fill |
tab-text | Active (and hover/focus) request/editor tab label color |
tab-text-inactive | Inactive request/editor tab label color |
tab-unsaved | Request/markdown tab title when the tab has unsaved changes |
tab-underline | Active request tab underline |
resize-separator | Resizable panel separator track and edge border |
resize-handle | Resizable panel grip (and high-contrast chrome accents) |
variable-token | {{variable}} syntax highlight in editors |
danger, danger-light, warning, success, info | Status colors |
method-get, method-post, … | HTTP method badge colors |
See the Solarized theme example for a complete theme plugin.
hc.themes.getActive()
Available since v2.0.0
Signature:() => Promise<ActiveTheme>
Returns the currently active theme — either a built-in id or a plugin theme reference.
const active = await hc.themes.getActive();
if (active.source === 'plugin') {
console.log(active.pluginId, active.themeId);
}hc.themes.onDidChange(listener)
Available since v2.0.0
Signature:(listener: (theme: ActiveTheme) => void) => Disposable
Fires when the user changes the appearance theme in Settings or when the host resets theme after plugin deactivation.
hc.themes.onDidChange((theme) => {
if (theme.source === 'plugin' && theme.themeId === 'solarized') {
hc.ui.showToast('Solarized theme active');
}
});hc.themes.register(theme)
Available since v2.0.0
Signature:(theme: ThemeContribution) => Disposable
Manifest:contributes.themes
| Parameter | Type | Description |
|---|---|---|
id | string | Theme id unique within your plugin |
title | string | Label in the appearance dropdown |
type | 'light' | 'dark' | Sets color-scheme and Electron native chrome base |
colors | Partial<Record<ThemeColorToken, string>> | Optional color token overrides |
metrics | Partial<Record<ThemeMetricToken, string>> | Optional typography/geometry overrides (CSS strings such as 14px) |
stylesheet | string | Optional plugin-relative CSS file for complex themes |
Provide colors, metrics, a stylesheet, or a combination. Use colors / metrics for token swaps; use stylesheet when you need selectors beyond :root (for example plugin-specific tweaks under [data-theme='plugin-…']).
When the user selects your theme, the persisted value is plugin:<pluginId>:<themeId>. If the plugin is disabled or uninstalled while its theme is active, HarborClient falls back to System.
hc.themes.register({
id: 'solarized',
title: 'Solarized Dark',
type: 'dark',
colors: {
surface: '#002b36',
sidebar: '#073642',
control: '#073642',
text: '#839496',
'text-secondary': '#93a1a1',
accent: '#268bd2',
selection: 'rgba(38, 139, 210, 0.25)'
},
metrics: {
'layout-font-size': '14px',
'scrollbar-width': '10px'
}
});