Skip to content

Editor theming standard

Opal's editor theme is a small contract, not a collection of component palettes:

palette seeds → semantic --ui-* roles → component aliases → CSS or canvas painters

The canonical files are:

  • styles/theme/editor-theme.foundation.css — spacing, type, radii, motion, z-indexes, and the user-customizable palette seeds.
  • styles/theme/editor-theme.roles.css — the public color API. Dark and light declare the same names.
  • styles/theme/editor-theme.*.css — older subsystem token maps. These may alias roles while the editor is migrated.
  • src/game/editor-theme-palette.js — cached access to roles for canvas-drawn editor chrome.
  • src/game/editor-theme-events.js — invalidation contract for theme mode and palette changes.

Rules

  1. Dark is the default map. Light mode is a complete alternative map selected by :root[data-theme="light"]; never make light colors the unqualified default.
  2. Component styles consume --ui-* roles. They do not read --editor-h-*, --editor-s-bg, or lightness seeds and do not author raw hex, RGB, or HSL colors. A component may create a local alias such as --script-border: var(--ui-line) when that makes its hierarchy clearer.
  3. Declare every --ui-* role in editor-theme.roles.css, once in each mode. Do not redeclare a public role in a component or subsystem sheet.
  4. Name roles by purpose, not appearance. Use --ui-danger rather than --ui-red; use --ui-bg-raised rather than --ui-lighter-panel.
  5. Interaction and status colors use the shared state roles: --ui-bg-hover, --ui-bg-selected, --ui-accent[-soft], --ui-danger[-soft], --ui-warning[-soft], --ui-success[-soft], --ui-info[-soft], and --ui-focus. Stable feature identities use the matching --ui-category-* role rather than borrowing a warning or syntax color because it happens to have the desired hue.
  6. Canvas painters read a semantic role directly with readThemeToken. This keeps their dependency explicit and lets the Node harness inject the public role without emulating the browser's full custom-property cascade. Build the painter's palette through createThemePalette so reads are cached and invalidated by opal-editor-theme-change.
  7. Runtime/game colors are outside the editor theme. Art authored by a game, sprite colors, particle colors, and player-only backdrops stay explicit.

Adding or changing a role

Add the name to both the light and dark blocks in editor-theme.roles.css, derive each value from the palette seeds, and then consume it from the component. src/game/editor-theme-roles.test.js rejects one-sided roles, unknown references, declarations outside the role map, and raw color recipes in surfaces that have completed migration.

For a newly migrated canvas painter, verify all three paths: dark mode, light mode, and a live palette edit. For a DOM surface, verify dark and light plus keyboard focus, hover, selected, disabled, warning, and error states.

Incremental migration

The subsystem token sheets are transitional: many existing selectors still consume names such as --panel-card-bg or --stage-graph-bg. Move the token's recipe into a semantic role, turn the old name into an alias, then migrate call sites. Once a component has no direct seed or literal color reads, add it to the conformance test so it cannot regress. Script Studio's IDE shell, the complete compact top/bottom chrome, inspector tabs/menus, the scene hierarchy, Help/About, the project launcher, the stage interaction chrome, and the Sound Canvas with its Library and Inspector docks are guarded surfaces.