Theming

Opus supports light and dark themes via CSS custom properties and a React context provider.

OpusThemeProvider

File: components/OpusThemeProvider.tsx

import { OpusThemeProvider, useOpusTheme } from "@/components/fields";

<OpusThemeProvider theme="dark">
  {children}
</OpusThemeProvider>
  • Sets data-theme="light" or data-theme="dark" on document.documentElement by default (covers portalled overlays)
  • Pass applyToDocument={false} to manage data-theme on your own container instead
  • useOpusTheme() reads the current theme from context (falls back to nearest [data-theme] on the document)

Overlay providers (ToastProvider, ContextMenuProvider) call useOpusTheme() so portaled content picks up the correct tokens. An optional theme prop on those providers overrides the context when needed.

CSS tokens

File: app/globals.css

Tokens are scoped to [data-theme="light"] and [data-theme="dark"]:

TokenPurpose
--opus-panelPanel / card background
--opus-borderDefault border colour
--opus-border-strongEmphasised border
--opus-textPrimary text
--opus-mutedSecondary text
--opus-accentBrand purple — headings, links, focus
--opus-accent-softAccent background wash
--opus-errorValidation errors
--opus-input-bgInput background
--opus-input-fillInput fill
--opus-shadowBox shadow
--opus-overlay-backdropModal/drawer/menu backdrop
--opus-overlay-blurBackdrop blur amount
--opus-control-heightStandard control height (28px)
--opus-input-radiusBase border radius (4px)
--opus-input-radius-largeLarge radius (6px)

Components reference these tokens in their CSS modules — they do not hardcode theme colours.

Theme toggle

Component: ThemeToggleField
Used in: development shell header

Sun/moon icons (Font Awesome) switch between light and dark. In the dev shell, the choice persists to localStorage.

Utility classes

Defined in globals.css:

  • .opus-panel-title — uppercase accent heading with bottom rule
  • .opus-panel-heading — flex row for title + actions (e.g. “More” link, copy button)

Overlay consistency

Modal, dialog, drawer, command palette, and context menu backdrops share --opus-overlay-backdrop and --opus-overlay-blur so overlay depth looks the same in both themes.

Using theme in new components

  1. Style with var(--opus-*) tokens in CSS modules — never hardcode #fff / #000 for surfaces that should theme-switch.
  2. If you use applyToDocument={false}, ensure portalled content still has access to [data-theme] tokens (either on a wrapper or on document.documentElement).
  3. Wrap app sections in OpusThemeProvider at the highest level that needs theme awareness.