Visual Themes (13 bundled skins + custom themes)
Omniscio ships thirteen bundled visual skins and lets you import your own theme files, all driven by CSS variables so picking a skin recolours every component without touching code. Covers where the theme grid lives, how to import, switch, delete or write a theme, what a theme can restyle, and the light-mode and accent-contrast rules a theme has to keep.
What it is
Omniscio ships with 13 bundled skins and supports importing your own .theme.json files. Themes are CSS-variable driven — picking a skin updates a small set of custom properties (--accent-rgb, --panel-bg, --panel-border, --color-surface-*, --accent-glow, etc.) and every component in the app recolors automatically. That means you never have to touch code to reskin the app, and theme authors never have to maintain a fork. Bundled options range from clean corporate (Linear, Vercel Clean, Minimal Luxury) to atmospheric (Aurora, Gradient, Neon Accent, Frosted Tiers) to glassy/textured (Glassmorphism, Liquid Glass, Neumorphic, Soft Depth, Warm Charcoal, Spotify). Liquid Glass is the newest — a cohesive frosted-glass skin (translucent blurred panels, accent glow, rounded corners) with both dark and light variants.
The atmospheric skins (Glassmorphism and Aurora) paint two soft blurred colour blobs behind the whole app. Those blobs are sized off the longer edge of the window, so they cover a monitor rotated to portrait as generously as a wide landscape one — on a tall screen, sizing them off width alone left two small dots in opposite corners with a flat dead band between. On any landscape window the sizing is mathematically identical to what it has always been, so nothing changes there. The blobs are static (they don't drift) — an animated blur re-rasterizes every frame and was a measured cause of typing lag — and Low Power Mode drops the blur entirely.
Where to find it
Settings → Appearance holds the Visual Theme grid of thirteen cards and the Custom Themes grid beneath it, with Import and Open themes folder buttons on the custom side. The full authoring reference sits one click further along, behind Custom Theme Guide in the same pane.
How it behaves
How to use it
- Pick a bundled skin. Settings → Appearance → the Visual Theme grid shows 13 cards, each with a small preview of base, panel, accent, and text colors. Click one — the app recolors instantly without a reload.
- Import a custom theme. Same pane, Custom Themes grid below. Click Import → file picker filtered to
.json→ select a.theme.jsonfile. The schema is validated; invalid files get a clear error (bad field names, missing required keys). Valid imports land in{userData}/themes/{id}.theme.jsonand appear as a new card in the custom grid. - Switch between bundled and custom. Any theme card is click-to-apply. Omniscio remembers your last choice; a fresh install starts on the default dark skin.
- Open the themes folder. The Open themes folder button reveals
{userData}/themes/in your OS file explorer — useful for dropping in theme files shared by others, or editing a custom theme by hand and seeing it reload. - Delete a custom theme. Any custom card has a trash button. Bundled themes can't be deleted (they re-seed from the binary anyway).
- Write your own. Structure is a JSON file with
id,name,description,author,version: 1, adark: { ... }palette (required), an optionallight: { ... }palette, and a smallpreview: { ... }for the card. Each palette maps a small set of camelCase keys to CSS colors — Omniscio translates those into the CSS variables at runtime. The full reference ships with Omniscio; fetch it via Settings → Appearance → Custom Theme Guide. Beyond surfaces / panels / accent / badges, a theme can also set the bodyfontFamilyand thefontFamilyMono(code blocks, terminal, diagnostics, IDs), the tooltiptooltipBg/tooltipBorder/tooltipText, the non-dial shadow/depth tokens (overlayElevationfor floating menus,shadowTooltip/shadowSheet/shadowBtnHover), and the full status-colour language (statusAgent/NeedsYou/Error/Ready/Question/Approval/Ember+ the-strongon-light variants +linkRgb) — the same tokens the app's error / warning / success / info badges, alerts and status chips read, so those recolour with the theme too. (The chat-message depth and the Motion & Polish depth stay under their own Appearance toggles, not the theme.)
For agents
How it works
Bundled theme definitions are a single TypeScript array in /src/renderer/src/features/settings/visual-theme-definitions.ts (VISUAL_THEME_DEFINITIONS). Custom themes are loaded from {userData}/themes/ by /src/main/services/custom-theme-service.ts: each file is parsed, validated against the Zod schema in /src/shared/custom-theme-types.ts, and exposed to the renderer. Applying a theme sets CSS variables on the document root via a THEME_VAR_MAP (camelCase JSON key → CSS custom property name) — this is why every component that uses bg-surface-100 or text-accent just works without per-component changes. Note the Tailwind surface utilities read the --color-surface-N-rgb twin (not the hex var), so buildCustomThemeCSS derives that twin from each hex/rgb surface value — without it a custom theme's surfaces wouldn't actually recolour. For semantic status colours, components must use the status-* tokens (not raw text-red-400 etc.) so a theme can recolour them; a ratcheting guard (tests/unit/lint/no-raw-status-color.test.ts) blocks new raw status colours. See custom-theme-tokens-contract.md. IPC surface is 5 channels registered in /src/main/ipc/custom-theme-handlers.ts: CUSTOM_THEMES_LIST, CUSTOM_THEME_IMPORT, CUSTOM_THEME_DELETE, CUSTOM_THEMES_OPEN_FOLDER, CUSTOM_THEME_GUIDE_GET. One push channel — CUSTOM_THEMES_CHANGED — fires on import/delete so the Appearance grid refreshes without a full app rerender. The Appearance settings component is /src/renderer/src/features/settings/AppearanceSettings.tsx. The 13 bundled names: Glassmorphism, Linear, Warm Charcoal, Aurora, Soft Depth, Neon Accent, Spotify, Minimal Luxury, Gradient, Neumorphic, Frosted Tiers, Vercel Clean, Liquid Glass. Every bundled theme has a light-mode variant (.theme-<id>:not(.dark)) that must stay visibly distinct — a hue-tinted surface scale and/or a clearly different accent (Vercel Clean's identity in light mode is deliberately monochrome white + black accent) — while keeping secondary text at WCAG AA on the tinted surfaces; both directions are locked by tests/unit/lint/light-theme-identity.test.ts. The light-variant blocks live outside @layer base on purpose: Tailwind tree-shakes custom layer rules whose class names never appear literally in source (theme classes are runtime-constructed), which once silently stripped every light variant from the shipped bundle — survival in the compiled CSS is locked by tests/unit/lint/light-theme-compiled-survival.test.ts. Solid-accent button labels adapt to the live accent: a white label on most accents — with a crisp dark halo (a tight text-shadow, --on-accent-text-shadow) so small 12px labels stay readable on a mid/colored fill — and a dark label on a near-white accent like Vercel Clean's, whose fill stays LIGHT (never deepened, else dark-on-dark), all via --on-accent-color. On a too-pale accent that still uses white (gold / GitHub blue / Spotify green / Aurora, ~2.1–3.0:1) the solid PRIMARY button additionally deepens its fill through a button-only --btn-accent-fill (never the global --accent-rgb, so links / tints / borders are untouched) until white clears WCAG AA; the status-strong fills (danger / warning / success) carry the same halo via their own .bg-status-*-strong.text-white rules (kept OFF .amc-btn-raised, whose block the raised-chrome guards read for box-shadow depth). Computed in accent-contrast.ts (pickOnAccentText + deepenAccentFill); locked by raised-accent-label-adaptive.test.ts + filled-button-label-halo.test.ts + accent-contrast.test.ts. A bundled theme id is registered in five aligned places (the VISUAL_THEME_IDS list + the visualTheme Zod enum in /src/shared/ipc-schemas/settings/appearance-settings.ts, the .dark.theme-<id> + .theme-<id>:not(.dark) CSS blocks in /src/renderer/src/styles/globals.css, and the preview card in VISUAL_THEME_DEFINITIONS); parity is enforced by tests/unit/shared/visual-theme-parity.test.ts.
Related
- chat-depth.md — the Chat Depth dial (Flat / Soft / Glass) in the same Appearance pane, which controls how much shadow and glassy depth the chat messages have, independent of the theme
- use-super-prompts.md — another example of an extensible asset imported via
.json - daily-digest.md — digests render in whatever theme you've picked; sharing artifacts inline both the light and dark CSS so viewers see either
Last verified 2026-09-23