Helm Design System
Helm is the operating platform for a fire-protection contractor’s back office: jobs, liens and collections, vendors, material and training on one Tower foundation. The system is warm paper and navy ink in light, near-black and gold in dark: calm, dense, record-first. Every value lives in one token store; nothing on a screen is spelled by hand.
Page mockups: browser, tablet and phone mockups of whole screens, built with this system, are on their own page: Page mockups.
Page map: every page, tab, panel and modal in Helm, in menu order, with links to each one’s mockup and issues: Page map.
Content fundamentals
- Say what the record is, in the business’s words. “Lien Register”, “Vendor Holds”, “Schedule of Values”, “Confirm Paid”. Not “Items”, not “Manage”.
- Sentence case everywhere except
text-overline/text-overline-smeyebrows, which upper-case themselves. Never type capitals by hand. - Second person, plain verbs. Buttons name the action: “Start filing”, “Log contact”, “Mark filed”. No “Submit”, no “Click here”.
- Numbers are the content. Amounts, counts and day-ages carry
text-numericand right-align in tables; an exceptional figure (an overdue total) takestext-body-emphasisplus a status colour. - Empty is a state, not a blank. Every list ships an
EmptyStatewith what is missing and the one action that fixes it: “No projects yet. Create your first project to get started.” - No emoji, no exclamation marks, no mascots. The brand speaks as a competent colleague.
Colour
- Ground and ink. Pages sit on
background; raised surfaces usecard, thenhelm-s2andhelm-s3for nested wells. Text isforeground; secondary text ishelm-text-dim; placeholders and meta aremuted-foreground. - Brand pair.
primaryis navy (#233153) in light and gold (#d5a944) in dark: primary buttons, selected state, focusring.accentis terracotta (#b95b46) in light and green (#4b8b5f) in dark: hover fills, the accent marker, the active sub-nav underline. - Chrome. The top bar and footer are
sidebarnavy withsidebar-foregroundtext;sidebar-foreground-dimfor inactive labels. - Status is meaning, not decoration. Use the
StatusBadgetones:helm-greensuccess,helm-orangewarning,helm-reddanger,helm-blueinfo,helm-purplespecial. A status surface is its hue at 12% (*-dim) with a 35% border (*-border). The word always travels with the colour. - Aging. Receivables age current →
helm-green, tier 1helm-yellow, tier 2helm-orange, tier 3helm-red, tier 4helm-red-deep. - Messages. Contextual strips (process, operational, onboarding, other) use their own
color-message-*surface, border and deep same-hue text. - Charts take
chart-1…chart-5in order, throughChartContainer. - Contrast.
foregroundholds 15:1 onbackgroundin both themes. Three source pairs miss 4.5:1 and are kept exact:muted-foregroundonbackgroundin light (3.6:1),accent-foregroundonaccent(3.6 / 4.0:1) anddestructive-foregroundondestructive(3.8:1). Keep those to 14px+ semibold labels or non-essential meta.
Type
- Outfit for all UI and headings (
--font-family-ui,--font-family-heading); JetBrains Mono for codes, IDs and aligned figures (text-mono). Both ship with the system as variable font files covering weights 400 to 700 (SIL Open Font License). - Use a role, never a size. Page title
text-title; section and dialog titlestext-heading; bodytext-body; dense UItext-body-sm; control labelstext-label; eyebrowstext-overline; metatext-caption. - Emphasis is a ladder. At base, sm and caption sizes step medium → strong → emphasis (500 / 600 / 700):
text-body-mediumfor a row’s identifying cell,text-body-strongfor a block’s lead line,text-body-emphasisonly for a figure that must read as exceptional. - Modifiers compose.
text-numeric(tabular figures) andtext-selected(weight only) sit on top of a size role. - A raw Tailwind size beside a role wins over it: remove every utility a role covers.
Space, shape and depth
- Spacing steps
space-xs4 ·space-sm8 ·space-md16 ·space-lg24 ·space-xl32 ·space-2xl48, plusspace-3xs(5px) for icon-to-label gaps. Page padding ispage-padding-y28px bypage-padding-x32px. - Radius is one base,
radius(10px), with offsets:radius-sm6px for chips and checkboxes,radius-lg12px for cards,radius-xl16px for dialogs,radius-fullfor pills and avatars.radius-noneonly where square is the meaning. - Borders over shadows. Surfaces separate with a 1px
borderhairline (border-width-thin). Shadows are for things that float:shadow-smresting controls,shadow-lgmenus and popovers,shadow-xldialogs. Dark mode deepens every shadow. - Layering follows
z-*: dropdown 100 < sticky 200 < overlay 300 < modal 400 < toast 500 < tooltip 600.
Layout
- Every signed-in screen is
AppShell→Page, which takes one typedPageContractand rendersPageHeader+ContentLayout+ twoTogglePanels. Do not assemble those by hand. - The top bar is
top-bar-height(66px); the module sub-nav issecondary-nav-height(40px). Navigation is the MENU button at every width (ED-161-1628); the vertical rail (sidebar-width228px) is switched off, not deleted. - Left/Right Panels open to
panel-width(280px), never more thanpanel-max-width-share(30%) each, and collapse to apanel-rail-width(32px) rail whose only signal is colour-coded dots. Fill them withPanelCard. - Below
breakpoint-tablet-max(1023px) panels stack above and below the page.
Motion
Motion tokens are CSS variables in the bundle (this format has no motion family): --duration-fast 120ms, --duration-base 200ms (the panel and nav glide), --duration-slow 300ms, --duration-bounce 600ms (still in the store; panels no longer use it), --duration-dwell 1000ms (how long a confirmed state holds before it exits), easing --easing-ease cubic-bezier(0.4, 0, 0.2, 1). Panels and the nav glide at --duration-base; nothing snaps and nothing bounces. prefers-reduced-motion collapses every CSS transition to near-instant.
Iconography
- Lucide, at
icon-size-sm(16px) in controls andicon-size-md(24px) in empty and stat states, stroke inherited from the text colour. - Icons are role tokens. Each role maps a slot to a Lucide name (
nav.home→ House,lien.filing→ Landmark,action.mark-filed→ Stamp). Render withRoleIcon role="…"orActionIconButton; never import a Lucide icon at a call site. A new slot gets a new role with a description of where it sits. The Icon roles page shows every assignment live, and the Icons group holds each role as an SVG with its slot.
Marks
- Helm is a ship’s wheel beside a heavy geometric wordmark. The shell uses the transparent horizontal lockup directly on either surface; the collapsed rail uses the wheel alone. See the Logos group for each file’s ground.
- LiensEasy is the one-module product shell for
lien-collections: a white wordmark with a green (#8fd360) mark, so it sits only onsidebarnavy or darker. - The mark colours (the wheel’s gold and teal) are artwork, not tokens; never sample them into UI.
Printed documents
The four legal instruments (statutory notice, filing affidavit, lien waiver, lien release) print from the print family in PDF points: US Letter, 1in margins, Helvetica at 14 / 13 / 11 / 10 / 9pt, 15pt leading, pure black print-ink. They never borrow UI colour or type.
Not synced
- Motion (
duration-*,easing-*) has no family in this format; the values ship as CSS variables incomponents/bundle.css. - Icon roles are not a token family in this format; they ship as the Icon roles page, the Icons asset group, and in the bundle as
Helm.ICON_ROLESandHelm.ICON_ROLE_DESCRIPTIONS. theme-name, a deploy diagnostic string, is omitted.- Uncarded exports:
GuideSpotlight(a full-viewport tour overlay anchored to live page elements) and the hooks and helpers (useShellNav,useAnchorBox,useIsNarrowLayout,cn,cx,C) are in the bundle without cards. - Route: built.
components/bundle.jsispackages/design-system/srccompiled with Vite and Tailwind v4, with React 19 inside it (React 19 ships no UMD build). Each preview is that component’s Storybook stories compiled against the bundle;ActionIconButton,DataSourceBadge,GuideNoteandPanelCardhave no stories and their previews are hand-written.