OpenProgram / Web UI

Themes are complete data sets; components read semantic tokens only

This answers three questions: where themes are selected and persisted, which surfaces a setting affects, and how theme additions or edits avoid updating only some components, windows or states.

Audience: Web / Desktop implementersStatus: current specificationScope: colors, fonts and cross-window themes

Design boundaries

CSS custom properties remain the sole runtime mechanism. Do not introduce JS theme objects, CSS-in-JS or a second component skin system. Built-in themes must provide identical token sets; components must not branch on specific theme names such as dark, light .

Included

  • Appearance and Font in Settings
  • First frame, React hydration and system color-scheme changes
  • Main window, desktop overlays and new windows
  • Surfaces, text, borders, states, focus, composer and DAG

Excluded

  • Changing component dimensions, layouts or interaction logic
  • Automatically rewriting saved user CSS overlays
  • Business logic conditional on theme names
  • Forcing application themes onto syntax highlighting or website content

How settings affect components

1 · Select

Settings

Appearance has four layers: Mode, named theme pack, accent color and CSS overlay. Font options come from font-pref.ts.

2 · Persist

Browser preference

Schema 3 writes to agentic_theme_style + agentic_theme_mode; accent and overlay belong to the same preferences family. Fonts also maintain a cookie for the first frame.

3 · Apply

HTML attributes

The first-frame script and hook set data-theme, data-custom-css, accent tokens and --font-sans.

4 · Resolve

Complete token files

Every theme pack defines the same token contract; accent overrides only three accent tokens; CSS overlays apply on top of the current pack.

5 · Consume

All components

The Tailwind bridge, CSS modules and desktop overlays all read tokens without checking specific theme names.

Diagnosing intermittent theme switches

Desktop startup and system color-scheme updates prefer the complete valid theme-prefs.json preferences saved by the main UI. Legacy browser-storage migration is used only when the file is missing or its mode/style is invalid. Raw browser-database byte scans cannot identify the latest record, must not override valid desktop preferences and must not restore cleared accent colors. After loading, the web page uses its own localStorage as authority and synchronizes the desktop cache.

The desktop main page records successful message-send times, first-screen initialization, settings changes, system color events, cross-window preference synchronization and HTML theme-attribute changes. Records contain only timestamps, fixed event names, theme enums and system light/dark state, excluding chat content, page URLs, CSS and user paths. Under desktop userData, theme-events.json retains at most 64 records; write failures do not affect theme rendering. The log identifies the source of intermittent switches and does not establish that unknown triggers are fixed.

Complete token contract

Theme files provide values; components define usage. The same purpose uses the same token name across every theme.

Surface

--bg-primary / secondary / tertiary--bg-input / hover / selected--surface-popover / tooltip

Text

--text-bright / primary--text-secondary / muted--nav-color / hover

Boundary & depth

--border / border-light--border-popover--shadow-* / --scrim-*

Action & status

--theme-accent--theme-accent-fill / fill-hover--accent-green / red / yellow--success-soft / warning-soft / danger-soft

Interaction

--selection-bg / --focus-ring--chip-bg / --chip-ring--meter-fill / --meter-track

Component-owned roles

--composer-surface / shadow / filter--provider-icon-bg--dag-ghost
SettingDirect stateAffected scopeProhibited
Appearancedata-themeBackgrounds, text, borders, shadows, state colors, tab focus, composer, popover, DAG and desktop overlaysWriting component-level [data-theme="light"]
Accent--accent-orange / fill / hoverPrimary-action color, selection borders and link colors on desktop error pages and directory listingsAdding a fourth accent token name
Font--font-sansbody, button, input, select, textarea and optgroup, except explicit mono regionsRedeclaring system font stacks in ordinary components
CSS overlay#user-custom-css + data-custom-cssOptional snippet over the current theme pack; unspecified values still come from that packTurning overlay into a fourth skin card
Theme familiesThemePrimary colorScope
Beigebeige-dark / beige-lightOrange / coralSame component roles; only theme values change
Neutraldark / lightBlueDefault Button, primary-action fills, selected states, links and accent borders
Auroraaurora / aurora-lightTealSame component roles, with dark foreground for filled-element contrast

Built-in themes share one component structure

New chatBrowserTerminal
Same tab, text hierarchy and button purposesThemes replace token values, not component structure or behavior.

Implementation comparison

OptionAdoptedRationale and boundariesMissing capability
CSS custom properties + data-themeAdoptedNative cascading covers Web, CSS modules, Tailwind arbitrary values and same-origin desktop overlays.CSS alone cannot validate missing theme tokens, so add a static contract check.
Tailwind @themeAs a bridgeExpose CSS tokens as utility names without storing another set of color values.Cannot dynamically generate CSS imports; a check script aligns theme files with TS ids.
JavaScript theme object / ContextRejectedDuplicates color values and cannot naturally cover native CSS, the first frame or Electron overlays.No runtime per-component theme props.
Component-level [data-theme] branchesRejectedNew themes would inevitably miss edits; theme files should provide component-role tokens instead.Components must not know built-in theme ids.
light-dark() Only color-schemeRejectedExpresses only light and dark, not the three Beige, Neutral and Aurora packs.Keep explicit values for each theme.

Specification basis:CSS Custom Properties Level 1, CSS Color Adjustment color-scheme. Existing implementation entry points: apps/web/lib/prefs/theme-pref.ts, apps/web/app/globals.css and apps/web/app/styles/themes/.

Acceptance criteria

  • Theme listSettings displays only Beige / Neutral / Aurora; check scripts establish agreement between CSS imports, theme files and allowed desktop overlay values.
  • Complete tokensSix built-in themes (three packs × light/dark) provide the same token set;custom-light.css is only a legacy-snippet fallback with values matching beige-light.
  • Accent colorAn independent picker overrides --accent-orange / --accent-fill / --accent-orange-hover; an empty value uses the current pack default. Desktop error-page and directory-listing link colors follow the override, while the window background remains the pack's --bg-primary.
  • CSS overlayUser CSS overlays the current pack, gated by the Enable switch; legacy [data-theme=custom] selectors are rewritten to data-custom-css.
  • Component isolationOutside theme files, no component CSS branches on specific built-in theme names.
  • Cross-window consistencyAll six theme-pack ids pass unchanged to desktop overlays; desktop window backgrounds use that theme's --bg-primary.
  • Font consistencyOrdinary text and native form controls inherit the same --font-sans, with explicit exceptions for mono regions.
  • Primary-color matrixNeutral light/dark themes use blue, Beige uses orange, and Aurora uses teal; pack-default accents continue to come from CSS files.
  • Active desktop tabIn dark mode, the tab bar's --tab-active-bg blends the content background with 12% white, shared by ordinary tabs and split groups. The selected background remains stable on hover; light mode and browser tabs retain the content background.
  • Visible acceptanceInspect the main window, tabs, composer, popovers and desktop overlays in dark, light and aurora using the default installed App.

Text-entry controls and native selects use one existing border in the theme accent color on focus. Focus adds no outline, offset ring or box-shadow frame and does not change control dimensions. Search and composer wrappers own their boundary; their inner editor stays borderless. Navigation, checkboxes, radios, sliders and embedded third-party document surfaces retain their own keyboard-focus treatments.

Implementation status

The current implementation uses Obsidian-style Appearance: Mode × named theme pack × accent color × CSS overlay.

Schema 3's THEME_STYLE_PAIRS remains the light/dark matrix of Beige / Neutral / Aurora. Custom is no longer a fourth style card; existing custom / custom-light migrates to beige, saved CSS enables the overlay, and old selectors are rewritten to data-custom-css. Accent color persists independently and overrides only three accent tokens. Composer, provider icons and DAG consume role tokens only. Desktop BrowserWindow backgrounds still use the pack's --bg-primary; error-page and directory-listing links use the accent override when present. Theme-contract checks are included in the complete Web checks.

Windows tests use Node.js fileURLToPath to convert module URLs into local paths, avoiding an extra slash before drive paths; theme behavior and assertions remain unchanged.