Skip to content
design
Foundations

Control styles

Semantic control palettes express emphasis and selection while sharing Bosun’s surfaces, typography and interaction states.

On this page
Aa
enabled--raised-enabled-…
Aa
hover--raised-hover-…
Aa
pressed--raised-pressed-…
Aa
activated--raised-activated-…
Aa
focused--raised-focused-…
Aa
disabled--raised-disabled-…

These are token swatches, not disabled or focusable controls. Try real controls on the Interaction states page.

The specimen follows the page theme. Choose a style or explicitly preview another theme. Each swatch reads the same palette tokens available to components.
Code
import { useState, type CSSProperties, type Dispatch, type SetStateAction, type ReactElement } from "react";
import { Select } from "@bosunkit/components/select";
import { Stack } from "@bosunkit/components/stack";
import { Text } from "@bosunkit/components/text";
import { PresentationScope } from "@bosunkit/provider";
import { THEMES, type Theme } from "@bosunkit/tokens/theme";
import "./example.css";

export const description: string =
  "Choose a control style and theme to inspect its token pairs in every interaction state.";
const STYLES: readonly string[] = [
  "normal",
  "flat",
  "raised",
  "indent",
  "selected",
  "amplified",
  "thumb",
  "notification",
  "integration-normal",
  "integration-flat",
  "integration-selected",
  "integration-indent",
];
const STATES: readonly string[] = ["enabled", "hover", "pressed", "activated", "focused", "disabled"];

export default function Example(): ReactElement {
  const [family, setFamily]: [string, Dispatch<SetStateAction<string>>] = useState<string>("raised");
  const [theme, setTheme]: [Theme | "inherit", Dispatch<SetStateAction<Theme | "inherit">>] = useState<
    Theme | "inherit"
  >("inherit");
  const foreground: string = family.endsWith("selected")
    ? "bk-control-selected-text"
    : family === "notification"
      ? "bk-control-notification-text"
      : family.startsWith("integration-")
        ? `integration-on-${family.slice(12)}-active-color`
        : `on-${family}-active-color`;
  return (
    <Stack space="lg">
      <div className="foundation-control-options">
        <Select.Root value={family} onValueChange={(next: string | null): void => { (setFamily)(next ?? ""); }}>
          <Select.Trigger aria-label="Control style">
            <Select.Value />
          </Select.Trigger>
          <Select.Portal><Select.Positioner alignItemWithTrigger={true} ><Select.Popup ><Select.List>
            {STYLES.map((name: string): ReactElement => (
              <Select.Item key={name} value={name}>
                {name}
              </Select.Item>
            ))}
          </Select.List></Select.Popup></Select.Positioner></Select.Portal>
        </Select.Root>
        <Select.Root
          value={theme}
          onValueChange={(next: string | null): void => { ((value: string): void => {
            if (value === "inherit" || THEMES.includes(value as Theme)) setTheme(value as Theme | "inherit");
          })(next ?? ""); }}
        >
          <Select.Trigger aria-label="Specimen theme">
            <Select.Value />
          </Select.Trigger>
          <Select.Portal><Select.Positioner alignItemWithTrigger={true} ><Select.Popup ><Select.List>
            <Select.Item value="inherit">Page theme</Select.Item>
            {THEMES.map((name: Theme): ReactElement => (
              <Select.Item key={name} value={name}>
                {name}
              </Select.Item>
            ))}
          </Select.List></Select.Popup></Select.Positioner></Select.Portal>
        </Select.Root>
      </div>
      <PresentationScope theme={theme === "inherit" ? undefined : theme}>
        <div className="foundation-control-grid" aria-label="Control token specimens">
          {STATES.map((state: string): ReactElement => {
            const ink: string = state === "disabled" ? "text-dim" : foreground;
            const style: CSSProperties = {
              background: `var(--${family}-${state}-background-color)`,
              borderColor: `var(--${family}-${state}-border-color)`,
              color: `var(--${ink})`,
              outline: state === "focused" ? "var(--bk-focus-width) solid var(--bk-focus-ring)" : undefined,
            };
            return (
              <figure key={state} className="foundation-control-cell">
                <div className="foundation-control-swatch" style={style}>
                  Aa
                </div>
                <figcaption>
                  {state}
                  <code>
                    --{family}-{state}-…
                  </code>
                </figcaption>
              </figure>
            );
          })}
        </div>
      </PresentationScope>
      <Text variant="caption" color="text-dim">
        These are token swatches, not disabled or focusable controls. Try real controls on the Interaction states page.
      </Text>
    </Stack>
  );
}
01Choose the role firstA normal action, selected option and notification have different purposes. Choose the role before choosing its fill, outline or text.
02One visual languageStyles share the spacing, radius and typography foundations. A raised control has stronger surface emphasis; only overlays cast shadows.
03Keep alert colors reservedSelection, destructive actions, validation and notifications do not borrow alarm, warning or caution colors. Labels and icons explain their meaning.

Style and state are separate

A style identifies the control’s visual role. A state describes what is happening to that control. A selected option can also be hovered, pressed, focused or disabled. Its selection remains visible through each state. OpenBridge styles and states provides the vocabulary; Bosun maps it to the elevation, color, spacing and typography foundations.

StyleUseBosun treatment
normalOrdinary actionsNeutral surface, readable label and a hairline.
flatQuiet toolbar actions and navigationTransparent at rest; interaction adds a surface or outline.
raisedA control that needs stronger emphasisSurface and outline contrast, without a resting shadow.
indentRecessed input or control wellInset surface; the label remains separate from placeholder text.
selectedA persistent choiceAccent treatment plus a check, indicator or explicit selected semantics.
amplifiedStrong neutral emphasisBrighter text and stronger outline, without an alert hue.
thumbA slider or switch gripNeutral grip with a distinct focus and disabled treatment.
notificationInformation outside alert managementNotification palette and readable foreground, never a priority color.
integration-*Navigation between systems or workspacesThe matching normal, flat, selected or inset palette; no competing brand color.

These are palette roles, not extra variants on every component. Use the component’s documented API; do not restyle a Button into an unsupported variant at the call site. The Button, Navigation list and Brilliance menu pages show their supported combinations.

Token anatomy

Background and outline tokens use --<style>-<state>-background-color and --<style>-<state>-border-color. The states are enabled, hover, pressed, activated, focused and disabled. activated names a persistent activation in the OpenBridge vocabulary; pressed lasts only for the input gesture. Integration controls add integration- before the role.

Text and icons are a separate layer. --on-<style>-active-color, -neutral-color and -disabled-color describe the foreground. For labels on strong fills, use the dedicated text pair:

ForegroundBackgroundPurpose
--bk-control-selected-text--selected-enabled-background-colorText on a selected fill.
--bk-control-notification-text--notification-enabled-background-colorText on a notification fill.
--bk-action-textPage or neutral control surfaceAccent-colored text using the theme’s readable accent step.
--bk-focus-ringThe control’s surrounding surfaceKeyboard focus, drawn independently of selection.

The selected and notification text pairs are checked at the text contrast floor in all four themes. Icon colors and fill colors are not automatically safe for small text. Check the actual background, especially when a fill is transparent or mixed with another surface.

TokenNightDuskDayBright
--raised-enabled-background-color
Raised control
#1A140B
#1A1B20
#F6F7F9
#E2E5E9
--raised-pressed-border-color
Pressed outline
#8F6130
#8B8F98
#5B6068
#4B5058
--selected-enabled-background-color
Selected fill
#48341F
#14B8A6
#0D9488
#0D9488
--bk-control-selected-text
Text on selected fill
#FFC878
#0B0C0F
#0B0C0F
#0B0C0F
--notification-enabled-background-color
Notification fill
#286550
#84B3EF
#2D548B
#1D3C67
--bk-control-notification-text
Text on notification fill
#FFCA7A
#0B0C0F
#FFFFFF
#FFFFFF
--bk-focus-ring
Keyboard focus
#FFC878
#14B8A6
#0D645E
#0D645E

Applying the palette

Bosun buttons use --bk-control-* roles for their raised surface, outline and foreground. Inputs and select triggers use --bk-field-*. Primary actions and selected navigation labels use --bk-action-text; their actual mixed backgrounds are included in contrast checks. Focus uses --bk-focus-ring, --bk-focus-width and --bk-focus-offset, independently of an invalid border or selected indicator. A chosen segment or row takes --bk-selected-background and is marked by --bk-selected-indicator; an invalid field's dashed border is --bk-field-invalid-border, the text color. These house roles follow the amber Night interface palette, in which red marks only the alarm; instruments retain their own presentation roles.

Do
background: var(--normal-pressed-background-color);
Keep purpose and state in the token name. A pressed normal control uses its pressed palette.
Don't
background: var(--alert-alarm-color);
Do not turn a pressed control into an alarm. Alert colors carry priority, not emphasis.

Use the shared text roles for labels, the spacing scale for padding, and the target rules for touch. A palette does not change a target’s size, imply a shadow, or determine whether an action is allowed. The component owns those behaviors.

Coverage

The kit defines all six background and border states for the roles above. Integration foregrounds and surfaces alias the same foundations, and nested PresentationScope themes resolve the aliases locally. The source vocabulary is pinned to OpenBridge 2.0.0-next.128; this is a palette contract for Bosunkit’s controls, not a claim that every upstream web component is available. Automation, augmented-reality overlays and additional alert-control families need their own semantic review before adoption.

  • Interaction states: behavior, state precedence and working controls.
  • Themes: palettes, scopes, automatic selection and brightness.
  • Elevation: surface emphasis and shadows.
  • Color: meaning and contrast limits.