Themes
Four palettes preserve control and instrument meaning from a dark cockpit to direct sunlight.
On this page
Display settings
Theme changes presentation; the control labels and behavior keep their meaning.
Code
import { useState, type Dispatch, type SetStateAction, type ReactElement } from "react";
import { Button } from "@bosunkit/components/button";
import { Input } from "@bosunkit/components/input";
import { Stack } from "@bosunkit/components/stack";
import { Inline } from "@bosunkit/components/inline";
import { Label } from "@bosunkit/components/label";
import { Popover } from "@bosunkit/components/popover";
import { Text } from "@bosunkit/components/text";
import { Heading } from "@bosunkit/components/heading";
import { PresentationScope } from "@bosunkit/provider";
import { THEMES, type Theme } from "@bosunkit/tokens/theme";
import { useId } from "react";
import "../control-styles/example.css";
export const description: string =
"Switch a local theme and open the popover to compare controls and their overlay without changing the page theme.";
export default function Example(): ReactElement {
const [theme, setTheme]: [Theme | "inherit", Dispatch<SetStateAction<Theme | "inherit">>] = useState<
Theme | "inherit"
>("inherit");
const id: string = useId();
return (
<Stack space="lg">
<Inline>
<Button
textCase="none"
aria-pressed={theme === "inherit"}
variant={theme === "inherit" ? "primary" : "default"}
onClick={(): void => {
setTheme("inherit");
}}
>
Page theme
</Button>
{THEMES.map((name: Theme): ReactElement => (
<Button
key={name}
textCase="none"
aria-pressed={theme === name}
variant={theme === name ? "primary" : "default"}
onClick={(): void => {
setTheme(name);
}}
>
{name}
</Button>
))}
</Inline>
<PresentationScope theme={theme === "inherit" ? undefined : theme}>
<div className="foundation-theme-preview">
<Stack space="lg">
<Heading level={2} variant="heading-component">
Display settings
</Heading>
<Text color="text-dim">
Theme changes presentation; the control labels and behavior keep their meaning.
</Text>
<Stack space="xs">
<Label htmlFor={id}>Profile name</Label>
<Input id={id} defaultValue="Nav station" />
</Stack>
<Inline>
<Button textCase="none">Save profile</Button>
<Button textCase="none" disabled>
Unavailable
</Button>
<Popover label="Theme scope" trigger={<Button textCase="none">Open themed popover</Button>}>
<Text>This overlay follows the specimen theme.</Text>
</Popover>
</Inline>
</Stack>
</div>
</PresentationScope>
</Stack>
);
}
The four themes
| Theme | Mode | Intended setting | Character |
|---|---|---|---|
| Night | Dark | A dark cockpit with dark-adapted eyes | Amber interface text and controls on black; instruments use the same amber hierarchy. |
| Dusk | Dark | Evening or a nav station below deck | Neutral charcoal with one teal accent. |
| Day | Light | Daylight at a nav station or in the saloon | Light neutral surfaces with a readable accent step for text. |
| Bright | Light | Direct sunlight on a helm display | White surfaces, dark text and stronger separation. |
A software palette does not measure ambient light or calibrate a display’s luminance. Check the actual device at its intended brightness and viewing distance. Night uses the amber interface palette, and red marks only the alarm. Instruments use their own amber hierarchy and keep conventional port/starboard colors and labels where those colors communicate direction.
Surfaces and foregrounds
These values come from the same Bosunkit stylesheets loaded by the examples. Ratios are computed against --bg;
a control on another surface needs a check against that surface.
| Token | Night | Dusk | Day | Bright |
|---|---|---|---|---|
--bgPage surface | #0000001.0:1 | #0B0C0F1.0:1 | #F2F3F51.0:1 | #FFFFFF1.0:1 |
--surfaceRecessed well | #14100A1.1:1 | #15161A1.1:1 | #FFFFFF1.1:1 | #ECEEF11.2:1 |
--surface-elevatedActive surface | #221A101.2:1 | #2021271.2:1 | #FFFFFF1.1:1 | #FFFFFF1.0:1 |
--borderRegion hairline | #3A2C161.6:1 | #26272E1.3:1 | #D5D8DD1.3:1 | #C9CDD41.6:1 |
--border-brightControl outline | #523D1E2.0:1 | #33353D1.6:1 | #B9BEC61.7:1 | #AAB0B92.2:1 |
--textPrimary text | #EAA75E10.2:1 | #E7E9EC16.1:1 | #17181C16.0:1 | #0B0C0F19.6:1 |
--text-dimSupporting text | #8F61303.9:1 | #8B8F986.0:1 | #5B60685.7:1 | #4B50588.1:1 |
--text-faintSources and section labels | #8F61303.9:1 | #878B945.7:1 | #666B754.8:1 | #5D626B6.1:1 |
--bk-action-textAccent text | #FFC87813.8:1 | #14B8A67.9:1 | #0D645E6.3:1 | #0D645E7.0:1 |
Do not use a fill color as small text merely because it belongs to the palette. Selected and notification fills have paired text tokens on Control styles. The color foundation records the remaining contrast limits of accent and alert-fill tokens.
Controls and states
Normal, flat, raised, indent, selected, amplified, thumb and notification roles share enabled, hover, pressed, activated, focused and disabled palettes. Integration navigation aliases the matching role rather than introducing another accent. Use the Control styles matrix to inspect each combination.
Selection survives hover and press. Keyboard focus adds a ring. Disabled blocks activation, read-only preserves inspection, and pending reports unfinished work. See Interaction states for working examples and state precedence. Typography, radius, spacing and target size remain separate foundations.
Instruments, alerts and special color families
| Family | Theme rule |
|---|---|
| Regular / enhanced instruments | Neutral values and an accent emphasis; Night uses amber with brighter enhanced values. |
| Alert priority | Color and the shared priority mark travel together. A theme does not remap urgency. |
| Data integrity | Stale and invalid readings retain their indication, label and age. |
| Port / starboard | Preserve navigational color meaning and directional cues. |
| Automation media | Preserve medium identity through labels; Night uses the documented amber and grey medium palette. |
| Data series | Stable categories across themes, with labels or line patterns where hue is insufficient. |
| Notifications and validation | Ordinary operation feedback, separate from the alert palette. |
| Token | Night | Dusk | Day | Bright |
|---|---|---|---|---|
--instrument-regular-primary-colorRegular instrument | #EAA75E | #FFFFFF | #3D3D3D | #3D3D3D |
--instrument-enhanced-primary-colorEnhanced instrument | #FFC878 | #14B8A6 | #0F766E | #0F766E |
--alert-alarm-colorAlarm fill / mark | #E90F20 | #E1000E | #E30019 | #EB0014 |
--alert-warning-colorWarning fill / mark | #BA5A00 | #FA7D00 | #FE9413 | #FE9413 |
--alert-caution-colorCaution fill / mark | #897300 | #FFCC16 | #FFDB42 | #FFDB42 |
--alert-low-integrity-border-colorLow integrity | #433700 | #8E6A00 | #6B4F00 | #8F6B00 |
--alert-invalid-border-colorInvalid | #A60000 | #A60000 | #7F0000 | #FF4D47 |
▲ alarm
● warning
■ caution
Three colored edges with no priority label or mark.
Theme choice, brightness and reset
- Auto follows the operating system: Dusk for dark and Day for light. It never chooses Night.
- An explicit choice persists in this browser until it is changed. It is a device preference, not vessel data or browsing history.
- Brightness adjusts presentation independently of the palette. Alert presentation and the control that restores brightness remain reachable.
- Reset in Brilliance menu restores the configured default brightness and Auto, so the palette follows the system again.
The design site stores its own theme choice. Kit applications use the shared theme store for theme preference, brightness and size class. Their storage keys and controls are distinct; switching the design site does not change an application’s persisted settings.
Scopes and overlays
Use PresentationScope to show one theme inside another. It sets data-theme on its region; leaving the prop unset
inherits the surrounding presentation. Applications import @bosunkit/tokens/index.css once. Components consume
variables and do not inspect the theme name to choose their own colors. Both live theme explorers follow the
page theme by default; selecting an explicit specimen theme overrides only that region. Choose Page theme to
return to inheritance.
A portaled overlay must receive the trigger’s theme, size class and language. Use the shared overlay components and check the rendered overlay itself: inheriting the page root can look correct until a Day specimen opens on a Night page. The live popover above demonstrates this boundary. Do not assume every overlay inherits a local scope.
This region uses Day. Change the page theme, then open each overlay.
Code
import { useState, type Dispatch, type ReactElement, type SetStateAction } from "react";
import { Button } from "@bosunkit/components/button";
import { Inline } from "@bosunkit/components/inline";
import { Stack } from "@bosunkit/components/stack";
import { TaskDialog } from "@bosunkit/components/task-dialog";
import { Select } from "@bosunkit/components/select";
import { Tooltip } from "@bosunkit/components/tooltip";
import { Text } from "@bosunkit/components/text";
import { PresentationScope } from "@bosunkit/provider";
import "../control-styles/example.css";
export const description: string =
"A Day region keeps its palette, size and language when a list, dialog or tooltip opens.";
export default function Example(): ReactElement {
const [open, setOpen]: [boolean, Dispatch<SetStateAction<boolean>>] = useState(false);
return (
<PresentationScope theme="day" lang="en">
<div className="foundation-theme-preview" data-testid="scoped-overlay-example">
<Stack space="lg">
<Text>This region uses Day. Change the page theme, then open each overlay.</Text>
<Select.Root defaultValue="nav">
<Select.Trigger aria-label="Display location">
<Select.Value />
</Select.Trigger>
<Select.Portal><Select.Positioner alignItemWithTrigger={false} ><Select.Popup ><Select.List>
<Select.Item value="nav">Nav station</Select.Item>
<Select.Item value="helm">Helm</Select.Item>
</Select.List></Select.Popup></Select.Positioner></Select.Portal>
</Select.Root>
<Inline>
<Button
textCase="none"
onClick={(): void => {
setOpen(true);
}}
>
Open scoped dialog
</Button>
<Tooltip content="The tooltip uses the Day palette too.">
<Button textCase="none">Hover or focus for a tip</Button>
</Tooltip>
</Inline>
<TaskDialog
open={open}
onOpenChange={(open): void => { if (!open) ((): void => {
setOpen(false);
})(); }}
title="Display location"
actions={
<Button
textCase="none"
onClick={(): void => {
setOpen(false);
}}
>
Close example
</Button>
}
>
<Text>The dialog keeps the presentation of the region that opened it.</Text>
</TaskDialog>
</Stack>
</div>
</PresentationScope>
);
}
Every kit overlay (lists, menus, pickers, dialogs, popovers, tooltips and toasts) copies its presentation scope into the portal, and one opened from a dialog or a toast stacks above it. With no explicit scope, they inherit the page theme.
Documents, charts and generated artwork
Reports use the same four themes in the site. Static investor documents and emails resolve their chosen palette at export time; they do not inherit a recipient's browser theme. Screenshots retain the theme in which they were captured. Explain their context rather than trying to recolor pixels with a surrounding CSS scope.
Mermaid reads computed roles from its own container and renders again when the effective theme changes. Two independent diagrams must keep separate palettes even though the renderer has global configuration. Verify the SVG itself, its source disclosure and its controls. An inherited page background is not proof that generated labels and connector colors have updated.
Use the token guide for renderer and export boundaries. Check categorical series, editorial accents and diagram roles as well as the ordinary page and control palettes.
Coverage and verification
The four palettes cover the ported instrument families and the documented control-state roles. The source vocabulary
is pinned to OpenBridge 2.0.0-next.128. The kit is a React port with Bosun’s typography and palettes; it does not
contain every upstream component or every automation and augmented-reality token.
Token tests check complete control-state definitions, readable selected/notification text pairs and nested scopes. Component verification also needs keyboard and pointer behavior, actual foreground/background contrast, focus, forced colors, reduced motion and overlays in every theme. Passing token tests alone is not a blanket component accessibility or bridge-display claim.
Related
- Control styles: semantic palettes, state swatches and text pairs.
- Interaction states: state meanings and working controls.
- Color: reserved meaning and outstanding contrast pairs.
- Brilliance menu: brightness, theme choice and reset.