Lab — Contributor Reference

Styleguide

Reference

The shared visual language used by the Sorter UI, the Hive community platform, and this documentation site. This page is the source of truth — both apps render an in-app `/styleguide` route that mirrors these patterns as a live smoke test.

Design Principles

Five rules that apply across Sorter, Hive, and these docs. Every new screen or component must honor them unless you have a documented reason not to.

1. No rounded corners

Sharp 0px edges everywhere. The LEGO family is intentionally industrial — rounded corners belong to consumer surfaces, not to our tools. The only sanctioned exception is the circular check icon used in hero celebration panels.

2. No colored left-accent borders

Notifications and callouts use a flat 1px border at 40% opacity on all four sides — never border-left: 4px solid stripes. That style is forbidden by the style guide.

3. One unified notification template

Info, success, warning, and error notifications all share the same shape and rhythm; only the brand color tone changes. Never invent a fifth variant. See Notifications below for the canonical markup.

4. 11px uppercase labels for micro-headings

Section labels, notification kickers, and stat cells use 11px / font-semibold / uppercase / tracking-wider. Body copy stays at 12px (text-xs) with relaxed leading. Resist the urge to grow labels back to text-sm.

5. Darker tones on tinted backgrounds

The standard LEGO palette is too light against the ~6% tinted notification backgrounds. Use the darker contrast tones listed in Colors for any text layered on a tinted surface.

Colors

Brand colors — user-selectable primary

Each operator can choose their preferred LEGO primary in the app settings. The four primary options below are equally valid; every screen that uses a primary color must pull it from the --color-primary CSS variable rather than hard-coding one of the four values.

LEGO Red
#D01012
Default primary for Hive and docs. Destructive actions, active nav, errors.
LEGO Blue
#0055BF
Default primary for the Sorter UI. Info, focus rings, selection.
LEGO Green
#00852B
Success, completion, connected state, confirm buttons.
LEGO Yellow
#FFD500
Warnings, calibration-weak, highlight. The canonical warning tone.

See Primary Color for the mechanics of how the pick is wired through the CSS variable.

Dark contrast tones

Use these darker shades for any label text that sits on a tinted notification background. They are not replacements for the brand colors — they are strictly reserved for legibility on top of 6%-opacity surfaces.

Red (dark)
#5C0708 / #7A0A0B
Notification labels on tinted red background.
Blue (dark)
#003A8C
Notification labels on tinted blue background.
Green (dark)
#003D14
Notification labels on tinted green background.
Yellow (dark)
#4A3300
Notification labels on tinted yellow background.

Neutral tokens

The app-agnostic neutral palette. Every LEGO-family surface draws from these.

bg
#F7F6F3
Page background.
surface
#FFFFFF
Card and panel background.
border
#E2E0DB
Divider lines, card outlines.
text
#1A1A1A
Primary copy.
text-muted
#7A7770
Secondary copy, labels, descriptions.

Typography

Both apps and these docs use IBM Plex Sans for copy and IBM Plex Mono for identifiers, URLs, hex values, and numeric read-outs.

Type scale

Role Size Weight Classes
Page hero headline 24px Bold text-2xl font-bold text-text
Section card title 16px Semibold text-base font-semibold text-text
Section description 14px Regular text-sm text-text-muted
Body / notification copy 12px Regular text-xs leading-relaxed text-text
Micro-heading / stat label 11px Semibold text-[11px] font-semibold tracking-wider uppercase
Identifier / URL 14px Regular font-mono text-sm text-text

Rules of thumb

  • Never grow a micro-heading back to text-sm. The 11px uppercase label is a deliberate rhythm — inflating it to the body scale dissolves the hierarchy we rely on inside notification blocks and stat cells.
  • Body copy is 12px (text-xs), not 14px. Sorter and Hive both default to the tighter scale; text-sm is reserved for hero descriptions and form labels that carry a specific hint.
  • Mono is for things you copy. Hashes, URLs, hex codes, machine identifiers. Prose and UI chrome stay in IBM Plex Sans.

Notifications

Every info, success, warning, and error surface must use the same template. Four variants, one shape.

Shape contract

All four variants share the exact same layout:

  • 1px border at 40% opacity on all four sides
  • Background tint at ~6% opacity of the same brand color
  • 11px uppercase kicker in the darker contrast tone
  • 12px body text in the default text-text color
  • No left-colored stripe, no rounded corners, no drop shadow

Info (blue)

<div class="border border-[#0055BF]/40 bg-[#0055BF]/[0.06] px-3 py-2
            dark:border-sky-500/40 dark:bg-sky-500/[0.08]">
  <div class="text-[11px] font-semibold tracking-wider text-[#003A8C]
              uppercase dark:text-sky-200">
    Calibration hint
  </div>
  <div class="mt-1 text-xs leading-relaxed text-text">
    Hold a flat reference card under the camera and click Capture.
  </div>
</div>

Success (green)

<div class="border border-[#00852B]/40 bg-[#00852B]/[0.06] px-3 py-2
            dark:border-emerald-500/40 dark:bg-emerald-500/[0.08]">
  <div class="text-[11px] font-semibold tracking-wider text-[#003D14]
              uppercase dark:text-emerald-200">
    Calibration is usable
  </div>
  <div class="mt-1 text-xs leading-relaxed text-text">
    White balance and exposure are within tolerance.
  </div>
</div>

Warning (yellow)

The canonical warning tone is #FFD500 — the real LEGO yellow. The yellow tint uses 10% opacity (not 6%) because yellow reads too faintly at the lower level.

<div class="border border-[#FFD500]/50 bg-[#FFD500]/[0.10] px-3 py-2
            dark:border-amber-500/40 dark:bg-amber-500/[0.08]">
  <div class="text-[11px] font-semibold tracking-wider text-[#4A3300]
              uppercase dark:text-amber-200">
    Calibration weak
  </div>
  <div class="mt-1 text-xs leading-relaxed text-text">
    Reference patches drifted by 8.3 ΔE. Re-shoot the calibration card.
  </div>
</div>

Error (red)

<div class="border border-[#D01012]/40 bg-[#D01012]/[0.06] px-3 py-2
            dark:border-rose-500/40 dark:bg-rose-500/[0.08]">
  <div class="text-[11px] font-semibold tracking-wider text-[#5C0708]
              uppercase dark:text-rose-200">
    Connection failed
  </div>
  <div class="mt-1 text-xs leading-relaxed text-text">
    Could not reach the Hive server. Check your credentials.
  </div>
</div>

Why the uniform shape

Notification style drift was the single biggest source of visual noise before this rule existed. Keeping one template means:

  • the eye instantly recognizes “this is a status message”, regardless of tone;
  • severity reads from color, not from layout;
  • new contributors don’t need to invent a new banner shape for every feature.

If you need a fifth visual affordance, you probably want a panel or card instead of a notification.

Components

Panels, buttons, form controls, and loading states — the building blocks that show up on almost every screen.

Panels and cards

The base surface is a flat 1px bordered rectangle with the bg-surface background — called .setup-panel in the Sorter UI. Use it for grouped content, stat cells, and inline forms.

<div class="setup-panel px-4 py-3">
  ... content ...
</div>

A hero panel (Setup Complete, first-run celebration, empty state) uses a gradient tinted background in the chosen accent color and may contain the single sanctioned rounded shape — the circular success check.

Buttons

Three button roles:

  • Primary — uses --color-primary (user-selected). One primary per screen, maximum.
  • Secondary — flat bordered button with the neutral surface background.
  • Brand confirm — inline affirmative actions that carry a fixed brand color regardless of the user’s primary (for example, the LEGO-green “Connect to Hive” confirm in the setup wizard).
<!-- primary -->
<button class="setup-button-primary inline-flex items-center gap-2
               px-4 py-2 text-sm font-medium">
  Continue
</button>

<!-- secondary -->
<button class="setup-button-secondary inline-flex items-center gap-2
               px-3 py-2 text-sm text-text">
  Rescan
</button>

<!-- brand confirm -->
<button class="inline-flex items-center gap-2 border border-[#00852B]
               bg-[#00852B] px-4 py-2 text-sm font-medium text-white
               hover:bg-[#00852B]/90">
  Connect to Hive
</button>

Form controls

The .setup-control class gives every input the same focus ring and surface. Focus always draws from --color-primary, not a hard-coded brand color.

<input type="text"
       class="setup-control w-full px-3 py-2 text-sm text-text"
       placeholder="e.g. Sorting Bench A">

Loading states

The standard spinner row used when fetching async data inside a step or panel:

<div class="setup-panel flex items-center gap-2 px-4 py-3
            text-sm text-text-muted">
  <Loader2 size={14} class="animate-spin" />
  Checking current Hive configuration…
</div>

Primary Color

How the user-selectable primary is wired through CSS custom properties, and which semantic slots stay fixed regardless of the user’s pick.

Mechanics

  • Each app reads --color-primary from CSS custom properties at the :root level.
  • Defaults are:
    • Sorter UI#0055BF (LEGO Blue)
    • Hive#D01012 (LEGO Red)
    • Docs site#D01012 (LEGO Red)
  • In both apps the user can override the default via Settings → Appearance → Primary color. The choice is persisted per-machine (Sorter) or per-user (Hive).
  • Components must consume --color-primary via the utility classes .text-primary, .bg-primary, .border-primary, or raw var(--color-primary)never via hard-coded hex values of the four LEGO options.

Semantic slots are fixed

Semantic slots are not affected by the primary picker:

  • Success stays green (#00852B)
  • Warning stays yellow (#FFD500)
  • Error stays red (#D01012)
  • Info stays blue (#0055BF)

This holds regardless of what the user picks as their primary.

Collision handling

If a user picks a primary that collides with a semantic slot (for example, Green-as-primary on a success screen), the semantic slot wins on its own component and the primary slot uses its natural color everywhere else. The two rarely sit side by side, but when they do the semantic meaning takes precedence over the personalization.

Checklist when adding a primary-coloured element

  1. Use var(--color-primary) or one of the .text-primary / .bg-primary / .border-primary utility classes.
  2. Don’t hard-code #0055BF, #D01012, #00852B, or #FFD500 for a primary slot.
  3. Make sure the background and text pair still reach AA contrast when the user picks any of the four options — Yellow primary in particular needs dark text.
  4. If the component has a focus ring, derive it from --color-primary too.

Where this guide lives

  • Canonical prose and rules: this page.
  • Live smoke test: /styleguide in the Sorter UI and /styleguide in the Hive frontend. Both routes render the same patterns with real components so designers can spot drift at a glance.
  • Design tokens: --color-bg, --color-surface, --color-border, --color-text, --color-text-muted, --color-primary (CSS custom properties, same names across all three surfaces).

When you add a new pattern, update the matching section above first, then mirror it into both in-app styleguides.