Design System Reference

Living reference for pure HTML/CSS prototyping (WCAG 2.2 AA on light backgrounds). Last updated 14 June 2026. Stored in BeameryHQ/design-artefacts, aligned to BeameryHQ/lib-design-system.

The design system ships as three files in this folder — styles.css (tokens & components), sprite.svg (logos & icons) and scripts.js (behaviours). Use starter.html — a shell page which already links all the right files — to get started.

Download design system

Table of contents

Colours

Components must consume semantic tokens (e.g. text.*, borders.*, backgrounds.*, controls.*), never raw primitive shades.

Primary band colours

Click a row to copy the hex.

Hex Primitive Token
#150E3Fbrand-navy
#F15852brand-orange
#7E4BBCbrand-deep-lavender

Key colours

Click a row to copy the hex. These shades are most often referenced in semantic tokens and components.

Hex Primitive Token
#1F1F23neutral-900 text.default, forms.label, borders.formActive, backgrounds.dark
#6C6C7Aneutral-700 text.secondary, forms.placeholder, borders.form
#492FF4indigo-500 text.primary, borders.primary, outlines.focus, controls.primary
#EBEBFEindigo-100 backgrounds.accent
#BF0338red-500 text.error, borders.error, controls.destructive
#FFE6E9red-100 backgrounds.error
#FFBF00yellow-500
#FFF1C7yellow-100 backgrounds.warning
#06742Bgreen-500 text.success, controls.activation
#D1FFD9green-100 backgrounds.success
Primitives – Full colour scales: neutral, indigo, red, yellow, green (50 → 950)

Click to copy hex values. Solid swatches are in-use theme colours.

neutral

Greyscale

Hex Primitive Token
not used
#0B0B0Cneutral-950
#1F1F23neutral-900 text.default, forms.label, borders.formActive, backgrounds.dark
not used
#34343Aneutral-850
#484851neutral-800
#5B5B67neutral-750 text.secondaryOnGray, forms.placeholderOnGray
#6C6C7Aneutral-700 text.secondary, forms.placeholder, borders.form
not used
#858593neutral-650
#A0A0ABneutral-600
not used
#AEAEB7neutral-550
#BCBCC3neutral-500 controls.secondaryActive
not used
#CCCCD1neutral-450
#DBDBE0neutral-400 borders.default, controls.secondaryHover
not used
#E1E1E5neutral-350
#E7E7E9neutral-300 backgrounds.secondaryOnGray
not used
#EBEBEDneutral-250
#F1F1F3neutral-200 backgrounds.secondary, controls.secondary
not used
#F3F3F4neutral-150
#F6F6F7neutral-100 backgrounds.default
not used
#FBFBFBneutral-50
#FFFFFFneutral-0 text.onDark, backgrounds.onGray

indigo

Primary accent

Hex Primitive Token
not used
#252FB1indigo-950
#2222C3indigo-900 text.primaryActive, borders.primaryActive, controls.primaryActive
not used
#2B1FD6indigo-850
#391EE6indigo-800 controls.primaryHover
not used
#3B21E9indigo-750
not used
#3E23EBindigo-700
not used
#4126EDindigo-650
not used
#4329F0indigo-600
not used
#462CF2indigo-550
#492FF4indigo-500 text.primary, borders.primary, outlines.focus, controls.primary
not used
#5D4AF6indigo-450
not used
#7366F8indigo-400
not used
#8A82FAindigo-350
not used
#A39EFBindigo-300
not used
#BCBBFDindigo-250
#D7D7FEindigo-200 backgrounds.accentHover
not used
#E1E1FEindigo-150
#EBEBFEindigo-100 backgrounds.accent
not used
#F5F5FFindigo-50

red

Semantic error

Hex Primitive Token
not used
#7B0526red-950
#860329red-900 controls.destructiveActive
not used
#92022Bred-850
#9E002Dred-800 controls.destructiveHover
not used
#A4002Fred-750
not used
#A90131red-700
not used
#AF0132red-650
not used
#B40234red-600
not used
#BA0236red-550
#BF0338red-500 text.error, borders.error, controls.destructive
not used
#D2043Ered-450
#E70545red-400
not used
#FC275Ared-350
not used
#FD5D7Dred-300
not used
#FE94A5red-250
#FFCCD2red-200 borders.errorSubtle
not used
#FFD9DDred-150
#FFE6E9red-100 backgrounds.error
#FFF2F4red-50 backgrounds.errorSubtle

yellow

Semantic warning

Hex Primitive Token
not used
#291B00yellow-950
not used
#412B00yellow-900
not used
#583B00yellow-850
#704D00yellow-800 text.warning
#885E00yellow-750 text.warningSubtle, borders.warningSubtleHover
not used
#A07100yellow-700
not used
#B88400yellow-650
not used
#CF9700yellow-600
not used
#E7AB00yellow-550
#FFBF00yellow-500
not used
#FFC518yellow-450
not used
#FFCB30yellow-400
not used
#FFD147yellow-350
not used
#FFD75Fyellow-300
not used
#FFDD77yellow-250
#FFE38Dyellow-200
#FFEAAByellow-150 borders.warningSubtle
#FFF1C7yellow-100 backgrounds.warning
#FFF8E3yellow-50 backgrounds.warningSubtle

green

Semantic success

Hex Primitive Token
not used
#054319green-950
#064C1Dgreen-900 controls.activationActive
not used
#085421green-850
#095D25green-800 controls.activationHover
not used
#096126green-750
not used
#086427green-700
not used
#086828green-650
not used
#076C29green-600
not used
#07702Agreen-550
#06742Bgreen-500 text.success, controls.activation
not used
#0AA239green-450
not used
#0FD044green-400
not used
#24ED56green-350
not used
#54EF76green-300
not used
#83F298green-250
#B1F6BDgreen-200
not used
#C0FCCAgreen-150
#D1FFD9green-100 backgrounds.success
not used
#E5FFEAgreen-50

Typography

Single family (Inter / system stack). Hierarchy by size and weight. Default: 14px / 400 / neutral-900.

H1 – Page (24px / 800 / 36px)

H2 – Section (20px / 700 / 32px)

H3 – Section or Modal (16px / 700 / 24px)

H4 – Small Header (14px / 700 / 24px)

H5 – Group Title (12px / 700 / 20px / uppercase)

Body – default product copy at 14px.

Body Emphasised – 600 weight for inline emphasis.

Small – 12px for captions and metadata.

Secondary – neutral-700 for descriptions and hints.

Link – brand primary, darkens on hover.

Error text – validation messages.

Success text – confirmations.

Table

Every column is sortable – click toggles ascending/descending (aria-sort); the unsorted indicator shows bidirectional arrows in text.secondary, the active column's arrow turns controls.primary. Headers: 12px / 400, text.secondaryOnGray on backgrounds.secondaryOnGray.

Name Role Status
Jane SmithEngineeringActive
Tom BrownDesignPending
Alex ChenProductInactive

Spacing, Sizing & Shape

4px grid. Gap classes .gap-1.gap-8; most common gap is 16px (--space-4).

Control height: 32px (lib-design-system sizes.x8) for buttons, inputs and selects; chips are 28px. Every interactive target clears the 24×24 minimum (SC 2.5.8).

Radii: 4px (sm – connector chips, small controls), 6px (md – buttons, inputs, selects: the workhorse), 8px (lg – cards), 9999px (pill – value chips, status pills, toggle tracks).

Logo & Icons

All assets ship as SVG sprite symbols. Click an icon to copy its SVG source.

Logo

Full wordmark, 118:32. The bexa mark is always brand-orange; the wording defaults to neutral-900, brand-navy on marketing surfaces.

Bexa

The logo mark without the wording, brand-orange. Scales freely.

Icon library (151 icons)

Most icons are stroke-only – outline icons using stroke="currentColor" – set color on the parent to tint. Partner marks and special cases appear at the end.

Buttons

Base class .btn + variant. One filled primary action per surface. The secondary family carries a visible resting border (borders.form, rendered as an inset box-shadow). Add a leading sprite with .btn-icon; use .btn-icon-only for icon-only controls (include aria-label). Action menus attach a floating .list-panel to a secondary button — labelled or overflow (three dots).

Variants

Disabled = 0.5 opacity + cursor: not-allowed.

With icons

Icon-only

Small (28px)

.btn-sm matches chip height — for inline filter rows and compact toolbars.

Chip (28px)

Action menus

Overflow commands in a .list-panel (role="menu"). Wire with DS.initActionMenu. Use a labelled btn-secondary trigger or an icon-only overflow button (ds-icon-more). Icons only on high-signal items; secondary actions stay text-only.

States (secondary family)
StateFillBorder (inset)
Restcontrols.secondary (neutral-200)borders.form (neutral-700)
Hover / Focuscontrols.secondaryHover (neutral-400)borders.formActive (neutral-900)
Activecontrols.secondaryActive (neutral-500)borders.formActive
Pressed (toggled)borders.primary

Inputs

Grey-fill control with a visible resting border. Placeholders never replace a visible <label>.

We'll never share your email.
Readable/copyable, not editable — distinct from disabled.
Please enter a valid email address.
Uses .typeahead-results as alias of .list-panel.
List-panel picker via DS.initSelectPicker
Labeled select — Empty / default state shows Any … (from data-select-any or the empty <option> text). Prefix inside trigger when a value is chosen (Sort by: Match score). No external label. Inline auto-width variant — .select-picker-labeled without .select-full.
To match 👉 Chip
28px chip trigger for inline chip-pair rows — .select-picker-chip or .qualifier-select inside .keyword-group.
States

Text inputs

StateFillBorderNotes
Restcontrols.secondary (neutral-200)borders.form (neutral-700)Placeholder forms.placeholder (neutral-700; 750 on grey surfaces)
Hovercontrols.secondaryHover (neutral-400)borders.formActive (neutral-900)
Focuswhiteborders.primary (indigo-500)+ focus ring
Errorneutral-200borders.error (red-500)Label text.error; aria-invalid + aria-describedby
Disabledneutral-200borders.form0.5 opacity

Select picker

btn-secondary trigger + list-panel listbox — same chrome as the Actions menu.

StateTriggerPanelNotes
Restcontrols.secondary + carethiddenSelected value on trigger
Openaria-expanded="true"white surface + shadowClick-outside / Escape closes
Option hoverbackgrounds.secondary
Selected optionaria-selected="true"Medium weight + primary text
Focusfocus ringitem focus ring

Select picker (labeled)

Prefix label inside the trigger — .select-picker-labeled + data-select-label. Selected: Sort by: value. Empty: Any … via data-select-any or empty <option> text. Full width with .select-full; inline auto-width without.

StateTriggerPanelNotes
SelectedLabel: + value + carethiddenPrefix bold; value regular weight
Empty / defaultAny … + carethiddenPrefix hidden; full “Any X” phrase bold
Openaria-expanded="true"white surface + shadowSame list-panel behaviour

Select picker (chip)

28px pill trigger matching .chip — for chip-pair keyword rows (location radius, language level). Add .select-picker-chip or use .qualifier-select inside .keyword-group (excludes .threshold-value-select, filter-level .status-select).

StateTriggerPanelNotes
Restwhite fill + borders.form + 12px carethiddenSame geometry as value chip
Hoverbackgrounds.secondary + borders.formActive
Openaria-expanded="true"white surface + shadowClick-outside / Escape closes
Focusfocus ringitem focus ring

Chips and toggles

Status pills, boolean connectors, interactive chips, single checkboxes, and on/off switches – shared affordances for filters, selections, and settings.

Pills

Compact status labels – 12px / 500, full pill radius. *-100 fill with matching dark text token. Typically not interactive; used to communicate state (e.g. Active, Pending).

Neutral Accent Success Warning Error

Boolean connectors

Boolean-logic connectors for the filter system. 11px / 700, radius 4px. All pairings ≥ 4.5:1 (AA). Full state spec in Filter System/connector-chip-spec.md.

IS IS NOT AND OR OR👈 locked

Chips

28px pills. Resting border borders.form; hover darkens the border, not just the fill.

Chip Chip selected Chip removable

Toggles

Off track is neutral-700 (≥3:1 vs page and thumb); on is brand primary. Three non-colour cues: thumb position, track colour, icon (SC 1.4.1). Use for immediate on/off settings with no separate save step. Default track is 44×24 (18px knob + 3px padding). When a label is used, it must declare the active state and stay the same when off — never swap to an inactive phrasing (that confuses which side is on).

Default – 24px track

Compact exploration – 16px track

Smaller visuals for dense panels (e.g. filter rows). Hit target kept at ≥24×24 via padding on the label (.toggle--compact-hit), not the track – otherwise the thumb looks undersized inside a padded pill.

Checkbox

Binary choices that apply on Save / Apply / Submit, or as part of a batch action. Single option: 16×16 box, 4px radius, label at 14px / 400. Resting border borders.form; hover border borders.primary; checked fill controls.primaryActive with white check. Matches the lib-design-system Checkbox component.

Single option

When to use toggle vs checkbox

toggle = “turn this on now”; checkbox = “include this”.

Use a toggle when flipping the control should take effect immediately — a live on/off for a single setting. Examples: enable/disable an active filter without clearing it, show/hide a panel section in place.

Use a checkbox when the choice is part of a form, list, or batch action — especially if several options can be selected, or the change only applies after Save / Apply / Submit. Examples: row selection, “Select all”, optional columns, consent, sub-filters in a filter panel.

Multiple options

When several choices need configuring together, wrap chips around native checkbox or radio inputs – use .chip-check; selected styles apply when the nested input is checked. Every option stays visible as a pill.

Radio – choose one day
Checkbox – choose all toppings that apply
States — chips
StateFillBorderText
Restwhiteborders.form (neutral-700)text.default; close icon text.secondary
Hoverbackgrounds.secondary (neutral-200)borders.formActive (neutral-900)close icon darkens to text.default
Activebackgrounds.secondaryOnGray (neutral-300)borders.formActive
Selectedbackgrounds.accent (indigo-100); hover accentHover (indigo-200)borders.primaryActive (indigo-900)text.primaryActive
Remove preview (prototype pattern)whitetext.error (red-500)text.error – hovering a remove-only chip previews the destructive outcome
FocusFocus ring (--focus-ring) on the chip; remove buttons get their own ring
States — checkbox
StateFillBorderCheck
Restwhiteborders.form (neutral-700)hidden
Hoverwhiteborders.primary (indigo-500)hidden
Checkedcontrols.primaryActive (indigo-900)borders.primaryActivewhite check icon
Disabled50% opacity; cursor: not-allowed
FocusFocus ring (--focus-ring) on the box
States — boolean connectors
ConnectorFillBorderTextHover
OR · IS (neutral)backgrounds.secondaryborders.defaulttext.secondary (4.6:1)Whole fill darkens one neutral step; border borders.form
AND (amber)backgrounds.warningSubtle (yellow-50)borders.warningSubtle (yellow-150)text.warningSubtle (yellow-750, 5.5:1)Border only → warningSubtleHover (yellow-750)
IS NOT (red)backgrounds.errorSubtle (red-50)borders.errorSubtle (red-200)text.error (red-500, 5.9:1)Border only → borders.error
LockedIdentical to rest; cursor: not-allowed; not focusable (tabindex="-1"); no hover response
Focusoutline: 2px solid outlines.focus, offset 2px

Alerts

Alert text uses text.warning (yellow-800) / text.error / text.success on the matching 100-tier fill. Error alerts carry role="alert"; others role="status".

Success

Account created successfully.

Warning

Your trial expires in 3 days.

Tooltip

Brief, plain-text hint that explains an element. CSS-only: wrap a focusable trigger in .tooltip with a .tooltip__bubble child. It reveals on hover and keyboard focus after a short delay, and only the hovered/focused one ever shows. Set data-position to top (default), right, bottom or left — choose the side with room (this lightweight version doesn't auto-flip like the React component). Set data-delay="instant" on .tooltip to skip the default hover-intent delay (side nav uses instant tooltips by convention).

Tooltip on top Tooltip on the right Tooltip on the bottom Tooltip on the left No hover delay

Markup

<span class="tooltip">
  <button aria-describedby="tip-save">Save</button>
  <span class="tooltip__bubble" id="tip-save" role="tooltip" data-position="top">Saves your changes</span>
</span>

Two markup patterns: a trigger with aria-describedby pointing at a role="tooltip" bubble when the tip adds information; or, for icon-only controls whose tip just repeats the aria-label (the side nav and the gear above), mark the bubble aria-hidden="true" to avoid a double announcement.

Accessibility (WCAG 2.2 AA)

  • Form-control boundaries are visible at rest. borders.form (neutral-700) gives 5.2:1 on white and 4.6:1 on the neutral-200 fill – comfortably above the 3:1 non-text minimum (SC 1.4.11). Hover state darkens to borders.formActive (neutral-900).
  • Placeholders: forms.placeholder (neutral-700, 4.6:1 on neutral-200) and placeholderOnGray (neutral-750, 5.9:1). Placeholders never substitute for a visible <label for>.
  • Focus ring (0 0 0 2px white, 0 0 0 4px indigo-500) on every interactive element; connector chips use a 2px offset outline. Never remove :focus-visible styles.
  • Hover is supplementary – every interactive element has a resting affordance (border, fill, or underline). Hover/colour change is never the only signal.
  • Text contrast: text.default 16.7:1, text.secondary 5.2:1 on white; connector chips 4.6–5.9:1; white on controls.primary 7.0:1.
  • Targets: 32px controls, 28px chips – all clear the 24×24 minimum (SC 2.5.8).
  • Error inputs carry aria-invalid="true" + aria-describedby; required fields aria-required="true" plus the visual *.
  • Icon-only buttons carry aria-label. Locked connectors are unfocusable (tabindex="-1") with cursor: not-allowed.
  • Disabled controls are 0.5 opacity (exempt from contrast requirements) but keep their border so the field shape remains discernible.

TODO

Tracked here so the reference and the live system stay honest about what exists versus what's planned. Grouped by type; not in priority order.

Components to build

  • Toasts / notifications — formalise the existing copy-confirmation toast into a documented component.
  • Modals / dialogs — focus trap, aria-modal, escape to close.
  • Tabsrole="tablist", arrow-key navigation.
  • Pagination and breadcrumbs.
  • Empty states and loading / skeleton states.
  • Progress indicators.

Foundations & tokens

  • Motion tokens — standard durations and easings (currently hardcoded 0.15s).
  • Elevation / shadow tokens — documented scale (shadows exist but are undocumented).
  • Breakpoint scale + responsive guidance (only ad-hoc media queries today).
  • i18n considerations.

Governance & docs

  • Decision guidance — which button variant when; an anti-patterns gallery; microcopy / voice.
  • Accessibility testing checklist (axe + manual screen-reader) and per-component keyboard patterns.