Skip to main content

Brandbook

PortQueue's dashboard and docs share one calm, muted visual system. The goal is a content-first surface: a dominant neutral, a single desaturated green accent, one text colour, and low-contrast semantic colours used only where they carry meaning.

All colours come from one set of design tokens in @portqueue/ui (src/theme/tokens.ts). The dashboard reads them as CSS variables (src/theme/theme.css, switched between light and dark by its ThemeProvider); this docs site mirrors them onto Docusaurus's Infima variables. A token change is made in tokens.ts and mirrored into both stylesheets.

Core palette​

#F2F3F1
--bg

Dominant. Page backgrounds, cards, panels, nav.

#A9D8B8
--primary

Primary accent — buttons, active states, links, success. Never a large fill.

#6FA184
--primary-dark

Hover / pressed for primary. Interactive text on light bg.

#DDE3DE
--secondary

Borders, dividers, table separators, inputs, code blocks, disabled.

#1B1F1D
--text

The only text colour. Full opacity for primary, ~68% for muted.

Semantic palette​

Muted and desaturated to match the core palette — never saturated/pure red, orange or blue. Use them only when the context genuinely means it; never decoratively. Always pair with --text (dark) for on-colour text, never white.

#D98A8A
--danger

Negative / error / destructive: failed jobs, delete actions, negative values, validation errors.

#B5645F
--danger-dark

Hover / active for danger.

#E0AE72
--warning

Warning / caution / pending: unsaved changes, approaching limits, deprecated docs, in-progress.

#C68A4E
--warning-dark

Hover / active for warning.

#8FB8D8
--info

Optional neutral informational tone: tooltips, new-feature badges, neutral notices.

Iconography​

Icons are real SVGs from @ant-design/icons (v6) — never emoji. Emoji render as a different picture on every platform, carry colours that aren't in the palette, and can't inherit the theme, so they are not used anywhere in the dashboard or these docs.

  • One curated set. @portqueue/ui re-exports the icons the design system uses from src/icons.ts under intent-named aliases — RetryIcon, PauseIcon, RemoveIcon, SearchIcon — and components import from there, never from @ant-design/icons directly. Adding an icon means adding a line to that file, which keeps the bundle to what's actually used and stops the same idea being drawn two ways. The docs site is the one exception: it has no dependency on @portqueue/ui and imports the icon package directly.
  • One icon per action. The dashboard's action icons live in one map (constants/labels.tsx), so Retry is the same glyph in a row, a bulk menu and a detail header.
  • Icons inherit. Every icon is sized in em and filled with currentColor, so it takes the size and colour of the text beside it. Colour an icon only where the colour carries the same meaning the palette gives it — semantic tones on alerts and toasts, --danger on a destructive action.
  • Decorative by default. An icon beside a text label is aria-hidden; an icon-only control carries the name in aria-label (and usually a tooltip).

Feedback & affordances​

A control that changes nothing visible has not told the user anything. Two patterns cover most of it, and both live in @portqueue/ui so every surface behaves the same way.

  • An action confirms itself. CopyButton swaps its icon for a green tick for one second after a successful copy, and says Couldn't copy — press ⌘C when the clipboard API is missing (any insecure origin) or refuses. Never let a copy look identical before and after.
  • The confirmation is announced, not just drawn. The state also lands in a role="status" region — a colour change is no feedback at all to a screen-reader user.
  • Clipped content shows it's clipped. A table wider than its card sits in ScrollShadow, which fades whichever edge has more content behind it: right-only at the start, both mid-scroll, left-only at the end, and neither when everything fits. A bare overflow-x: auto reads as a truncated table rather than a scrollable one.

Usage rules​

  • Neutral dominates. --bg covers the majority of every screen. --primary is an accent — buttons, active nav/tabs, links, positive indicators — never a big background.
  • One text colour. --text (#1B1F1D) for everything; drop to ~60–75% opacity for secondary/muted text. No warm greys, no pure black, no white body text.
  • Contrast first. Because the accents are light and muted, on-colour text is always the dark --text, not white — this keeps every combination at WCAG AA. Light green is used for fills; the darker --primary-dark is used for green text/links on light surfaces.
  • Semantics carry meaning. --danger/--warning/--info appear only where they mean something — never for decoration.

On the dashboard​

--primary for KPIs, active filters, primary CTAs. --danger for negative deltas, failed jobs, destructive actions. --warning for threshold alerts, pending states, stale-data indicators. Tables and charts stay on --bg/--surface with --secondary gridlines so semantic colours stand out only where they're meaningful.

In these docs​

--bg for content, --secondary for code blocks and dividers, --primary for links. Admonitions always sit on a plain white (--surface) banner — no coloured fill — and only the title takes a semantic colour: --info blue for :::info, --warning orange for :::warning/:::caution, --danger red for :::danger. :::note stays neutral. Green is never used for a banner.

Info

Informational callouts use a blue title on a white banner.

Note

Plain asides use a neutral title — no accent colour.

Warning

Caution/deprecation admonitions use the muted --warning orange title.

Breaking change

Critical/breaking admonitions use the muted --danger red title.