Changelog

What changed in Helix — release-level highlights below, the full commit-level history further down.

Recent releases

  1. fixed

    AppShell no longer nests two main landmarks

    2026-08-19v0.8.0

    SidebarInset rendered a <main> and AppShell rendered another <main> (the app-shell-main slot) inside it. HTML permits one main landmark per document and forbids nesting one in another, so screen-reader landmark navigation listed both and the inner region was not reliably exposed as the document's main. SidebarInset is now a plain <div> — every class and its data-slot are unchanged, so the fix is DOM-structural and pixel-identical (the inset still computes display:flex, flex:1 1 0%, and the same 1192px width beside a 248px rail). The landmark stays on the content region rather than the inset because that is the part a "skip to content" jump should land in. That also let the explicit role="banner" on AppShellTopBar go: the workaround existed only because a <header> inside <main> gets no implicit banner role, and the top bar now sits outside main, so <header> is an implicit banner on its own — the accessibility tree shows one banner and one main as siblings. Callers using the Sidebar primitives directly should note SidebarInset no longer supplies the landmark: wrap the page content in a <main> inside it, which is what every skill code sample already showed.

  2. new

    Badge gains sky and sand tones

    2026-08-19v0.8.0

    defaultSky / outlineSky and defaultSand / outlineSand round out the primitive colour set. Both ramps already existed in tokens.css, so no new tokens were added. The filled pair joins the LIGHT recipe (light fill + black text, as defaultOrange does) rather than the dark one: sky-600 would have been the tightest pair in the family against the navy fill (#16538E) by more than double at ΔE76 26.8, while sitting only ΔE 19.1 from sky-400 — nearly as close to a different tone as to another step of its own ramp. sky-400 sits ΔE 43.0 from navy. defaultSky measures 7.96:1 and defaultSand 11.05:1 against black. The outline pair takes the -700 text step, and that step is forced rather than chosen: the house recipe is -50 fill / -200 border / the first text step clearing 4.5:1, and sky-600 on sky-50 measures 4.49:1 (misses by 0.01) while light-sand-600 on light-sand-50 measures 4.02:1. Badge text is text-xs (12px), so the WCAG large-text allowance does not apply — the same call already made for outlineBerry. outlineSky lands at 6.23:1 and outlineSand at 5.46:1. The variants are named Sand, not LightSand: the variant name is the public API and "sand" is the spoken name, with the tc-light-sand token binding recorded in metadata.

  3. new

    Badge takes success, warning, and info

    2026-08-19v0.8.0

    Badge now answers to success / warning / info and outlineSuccess / outlineWarning / outlineInfo. Zero pixels change: each is an alias over an existing look — defaultSage, defaultOrange, defaultNavy and their outline counterparts — and both keys reference one shared class string in badge.tsx so an alias cannot drift from what it aliases. The gap was real: tokens.css defines --success and --warning, but those tones reached Badge only through the tc-* primitives, and destructive was the only tone with a same-named variant, so a status badge had to be spelled as a COLOUR — which invites hand-rolling. Alert already ships success/warning/info, so this was an inconsistency between two components carrying the same semantics rather than a missing feature. The tc-* colour names stay permanently and are NOT deprecated: a defaultNavy badge used as a decorative category chip is genuinely not `info`, and collapsing the two would force a false semantic onto non-status badges. Badge's filled warning is a lighter ramp step than Alert's on purpose — Alert uses its tone as a RAIL (3:1, WCAG 1.4.11) while Badge uses it as a TEXT BACKGROUND (4.5:1, WCAG 1.4.3); tc-orange-300 + black is 14.08:1, whereas matching Alert's tc-orange-700 with white would be 4.20:1, an AA failure at Badge's 12px text.

  4. new

    Dialog caps its height, and DialogBody pins the header and footer

    2026-08-19v0.8.0

    DialogContent had no max-height and no overflow handling. Because the panel is centred with translate-y(-50%), a dialog taller than the viewport bled off BOTH edges at once with nothing scrollable — measured at 1252px tall in a 907px viewport, losing 173px at each end and taking the title and the action buttons with it. DialogContent now sets max-h-[calc(100dvh-4rem)] and becomes a flex column. dvh, not vh: on mobile Safari vh resolves against the largest viewport, so a vh cap still clips while the URL bar shows, and the usual dvh jitter objection does not apply because a modal locks body scroll. Scrolling is opt-in-shaped so existing dialogs are fixed without touching them: with no DialogBody present the whole panel scrolls (not-has-[[data-slot=dialog-body]]:overflow-y-auto), which is what 81 of 86 admin call sites get for free. Wrap the middle in the new DialogBody and only it scrolls, with DialogHeader and DialogFooter pinned — prefer that for any dialog whose actions must stay reachable. Dialogs that already fit are unchanged, and the five call sites that invented their own max-h (80vh, 85vh, 90vh) keep their value, since a caller's class still wins through twMerge; those can now drop it.

  5. new

    Table can pin its header row

    2026-08-19v0.8.0

    Table now takes stickyHeader + maxHeight, e.g. <Table stickyHeader maxHeight="24rem">. Consumers could not build this themselves: className goes to the inner <table>, not to the scroll container, and the container had no bound — overflow-x-auto already makes overflow-y compute to auto, so it was a scroll container, but with unconstrained height nothing scrolled inside it and a hand-rolled `sticky top-0` on TableHeader had no scrollport to pin against. maxHeight supplies the bound as an inline style (a caller-chosen length must not become an arbitrary utility); the header gets sticky top-0 z-10 plus a bg-card fill so rows do not show through it, and a card-variant container drops its vertical padding, because a scrollbox's padding gutters sit inside the scrollport and a clipped row was visible above the pinned header. stickyHeader without maxHeight is a no-op with a dev-only warning rather than a header that silently does not stick. This is an inner scroll panel: the page does not move, and parking the header under an app bar is a different UX that is not supported.

  6. fixed

    Table warns when a card-variant table is nested in a Card

    2026-08-19v0.8.0

    variant="card" stays the default — no visual change — but the wrong pairing is now loud. A card-variant table inside a Card's CardContent applies the card treatment twice: the container's p-6 stacks with CardContent's px-6, doubling the intended inset, and its border/rounded-xl/shadow-sm draws a second surface inside the Card's. Six admin surfaces shipped it, because it renders almost right and nothing in the type system or the test suite can see it. Table now detects the arrangement in the DOM and logs a dev-only console warning naming the stacked classes; use variant="flush" inside a Card. Flipping the default to flush was rejected: flush's failure mode is a standalone table with no border, radius or shadow at all, which relocates the risk instead of removing it — and a nested card is the harder of the two to spot.

  7. breaking

    AlertAction anchors to the title row instead of centring

    2026-08-19v0.8.0

    AlertAction spans both of Alert's grid rows, so self-center centred it across the title AND the description — a disclosure chevron sat beside the title while collapsed and then slid down by half the description's height when opened (32px in a three-line specimen, more as the description grows). A control you click must not move when you click it. The default is now self-start. Alerts with a title only are unaffected, since one row renders identically either way. Where centring is genuinely wanted, pass className="self-center" — it merges cleanly, so no prop was added.

  8. breaking

    CardTitle now sets its own size (18px)

    2026-08-19v0.8.0

    CardTitle was only `leading-none font-semibold`, so its size came from whatever ancestor happened to set one — 14px inside a text-sm ancestor, 16px with none. The same component rendered at different sizes on different pages, and replacing a 24px heading with a bare CardTitle silently dropped it to body size. It now defaults to text-lg (18px). Call sites that already pass an explicit size are unaffected — cn is twMerge, so the caller's class still wins. Every CardTitle without one grows.

  9. fixed

    Outline Button matches every other bordered control

    2026-08-19v0.8.0

    0.7.0 darkened --input for contrast but left --border alone, and outline Button read a bare `border` — so it kept the old #D3D4D4 stroke while Input, Select, Textarea, Checkbox, Radio, Combobox and InputOTP all moved to #646565. One toolbar rendered two different strokes. Outline Button now sets border-input explicitly. Light mode only; dark mode was already correct, and the variant's own dark:border-input override is now redundant and removed.

  10. new

    AlertDisclosure — Alert can collapse its body

    2026-08-19v0.8.0

    A long notice that leads with its summary and reveals the rest behind a “Read more” chevron had to be hand-built from an Alert plus a separate expand/collapse element, so every team got a slightly different result and the ARIA wiring was theirs to get wrong. AlertDisclosure supplies both halves already wired — aria-expanded and aria-controls on the trigger, and a revealed region that is `hidden` while collapsed so it leaves the accessibility tree rather than being merely invisible. Labels default to “Read more” / “Read less”, the house verbs for expanding truncated body text in place. It is its own module rather than a `collapsible` prop on Alert: disclosure needs state and "use client" is per-module, so the prop would have moved Alert across the client boundary for every consumer including the many rendering a plain static banner — and Alert is one of the 11 server-safe components. It renders a fragment rather than a wrapper because Alert is a grid and the revealed body and the trigger belong in different cells; a wrapper would be one grid item and would drop the trigger below the text. Pass role="status" on the Alert: the default role="alert" is an assertive live region, and revealing text inside one makes a screen reader interrupt itself to read what the user just asked for — a disclosure is not an event. Reuses alertActionClasses, so it inherits this release's self-start fix, which was made for exactly this control: expanding grows the alert 87px while the trigger moves 0px.

  11. fixed

    Deprecated variant values are no longer offered as choices

    2026-08-12v0.7.0

    Deprecated variant values are recorded in variants[].deprecatedValues and removed from the variants[].values list the docs and the MCP server serve, so they are no longer offered as valid options. Alert's info and error, and Button's secondary, still render but log a dev-only console warning. Asking for an "info banner" now points at variant="default", which is the informational style; the supporting action next to a primary button is outline.

  12. new

    TableSkeleton compound

    2026-08-12v0.7.0

    Loading placeholder shaped like a table — Skeleton bars inside real table rows and cells rather than a generic block, so the placeholder occupies the same grid as the data it replaces and swapping in the real rows does not reflow the page. Live-region wiring included.

  13. new

    TablePagination compound

    2026-08-12v0.7.0

    Table footer with a status line and Previous/Next. Pass page, pageSize and total; the row range and page count are derived and the range is announced, so callers do not compute or wire that themselves.

  14. new

    PasswordInput and PasswordRulesList compounds

    2026-08-12v0.7.0

    PasswordInput is a password field with a trailing show/hide toggle. PasswordRulesList shows live positive-assertion rule feedback as the user types and doubles as the error state, so a set-password form needs no separate error message. Rules are caller-supplied, because password policy is enforced by each product's own validation and server and the list has to mirror it.

  15. breaking

    Alert signals its variant with a leading rail, not a background tint

    2026-08-12v0.7.0

    Every Alert variant now renders on the card surface with a foreground title and muted description; the variant is carried by a 4px rounded rail on the leading edge plus the icon color. The soft tc-*-50/-200 tinted palettes are gone. No code change is required — the variant union is unchanged, and six names now resolve to four looks (default ≡ info navy, destructive ≡ error berry, warning orange, success sage) — but expect a visual diff anywhere Alert is used. Because color is now the only variant signal, always pass an icon. AlertAction also moved to its own grid track, so a long action label shrinks the content instead of overlapping it, and adding one no longer widens the title-to-description gap.

  16. breaking

    Table card/flush variants; default is now a padded card

    2026-07-24v0.6.0

    A single table on a page renders as its own padded, bordered card (variant="card", the new default) so rows are never flush to the edge, with its header/controls/footer around it. When several tables share a page, wrap each in a Card and use variant="flush" inside. The prior tinted bg-input-bg default is gone — an existing <Card><Table></Card> should adopt variant="flush" to avoid a double border.

  17. new

    Tabs spacing prop (compact / page)

    2026-07-24v0.6.0

    Tabs gains a spacing prop for the gap between the tab strip and its content — compact (8px, default) for inline tabs, page (24px) for page-level detail tabs.

  18. new

    text-link semantic color token

    2026-07-24v0.6.0

    A semantic text-link color (brand blue, tracking primary) for inline links, themeable in light and dark. Use it instead of raw text-blue-* utilities.

  19. new

    AppSidebar section-aware active routes + NavBreadcrumb

    2026-07-24v0.6.0

    AppSidebar accepts a pathname prop and resolves the active item/section itself, so nested and sibling detail routes (e.g. /clients/:id) highlight their owning section. Collapsible groups tidy on navigation — only the current section stays open. NavBreadcrumb auto-fills the PageHeader breadcrumb from the same nav model.

  20. new

    Five-intent information architecture

    2026-07-09v0.3.0

    Docs restructured into Guides / Foundations / Components / Patterns with a taxonomy-driven catalog, new top nav, section landing pages, and legacy-URL redirects.

  21. new

    Compound components join the catalog

    2026-07-09v0.3.0

    The 7 compounds (app-shell, app-sidebar, editable-section, filter-popover, list-page-template, metric-card, page-header) now have content pages and are covered by the verify:docs gate like every other component.

  22. fixed

    Relative content links no longer 404

    2026-07-09v0.3.0

    Links inside design-system markdown written relative to content/ (../dir/file.md, ./file.md, bare file.md) now resolve to their real routes instead of 404ing on the static export.

  23. new

    AlertAction added

    2026-06-29v0.3.0

    Alerts can now carry a call-to-action, positioned top-right; the container reserves space for it automatically.

  24. breaking

    Sidebar width 256px → 248px

    2026-06-26v0.3.0

    Default expanded sidebar width corrected from the shadcn stock 16rem to the spec's 248px; menu button height and submenu indentation were also aligned to spec.

  25. fixed

    Tooltip uses the primary token, not foreground

    2026-06-26v0.3.0

    Tooltip background and arrow switched from bg-foreground (rendered as a near-black tooltip) to the semantic bg-primary/text-primary-foreground pair.

  26. fixed

    Primitive spec-conformance sweep

    2026-06-26v0.3.0

    An audit against content/components/*.md found vendored-fork drift across button, empty, command, popover, item, switch, kbd, pagination, tabs, toggle, alert, select, and more — published classes now match the documented spec.

  27. new

    Monorepo consolidation

    2026-06-26v0.3.0

    Helix consolidated into one npm-workspaces monorepo: the @timelycare/helix-ui package, the docs/visualizer app, and a single canonical content/ source, with a verify:docs no-drift gate between structured metadata and prose.

Change Log

A record of changes made to Helix — both the design system specs and the workbench front-end.

DateBranchSummaryMerged IntoType
Jun 26, 2026consolidate-monorepoMonorepo consolidation: package + visualizer + canonical content
WorkbenchSpec
May 6, 2026stagingStaging → Main: Table Behavior pattern, accessibility, and interactive state improvementsmain
SpecWorkbench
Apr 29, 2026stagingStaging → Main: Semantic color tokens, K12 + TimelyPulse nav icons, sidebar previewsmain
SpecWorkbench
Apr 29, 2026stagingSemantic color tokens: zinc → tc-gray migration; K12 + TimelyPulse nav icons + sidebar previewsmain
SpecWorkbench
Apr 22, 2026stagingSpecs: rename magenta → berry across badge, table, and data-table docsmain
Spec
Apr 22, 2026stagingAlert: warning, info, success, error variants; preview polishmain
SpecWorkbench
Apr 15, 2026stagingStaging → Main: Brand color hierarchy, tc-gray tokens, berry badge variants, ScaledFramemain
SpecWorkbench
Apr 10, 2026stagingStaging → Main: PageTemplates, Avatar/Sidebar specs, PreviewAppShell, Sidebar height, Table overflowmain
SpecWorkbench
Apr 1, 2026stagingStaging → Main: Interactive charts, Table striped variant, outline badge mappingmain
SpecWorkbench
Mar 20, 2026stagingStaging → Main: Header cleanup, mobile logo, TimelyCare logos, proposal framework, color fixmain
SpecWorkbench
Mar 20, 2026stagingStaging → Main: TimelyCare logos, design system proposal framework, tc-light-sand token fixmain
SpecWorkbench
Mar 13, 2026stagingStaging → Main: Brand colors (tc- prefix), typography scale, badge color variants, K-12 removalmain
SpecWorkbench
Mar 13, 2026feat/k12-removalRemove K-12 and theme system — Helix is now single-brandstaging
SpecWorkbench
Mar 13, 2026feat/badges-updatesBadge color variants (primitive), semibold typography, full rounded shapestaging
SpecWorkbench
Mar 13, 2026feat/new-brand-colorsNew brand colors; removed outdated primitive and semantic tokensstaging
SpecWorkbench
Mar 9, 2026stagingPromote staging to main — full component audit, avatar & switch fixesmain
SpecWorkbench
Mar 9, 2026REV-2026-march-09Comprehensive audit aligning all 57 usage previews with Helix specsstaging
SpecWorkbench
Mar 3, 2026stagingData table spec & preview updated to use Helix Badge, Table, and Checkbox componentsmain
SpecWorkbench
Mar 3, 2026stagingPromote staging to main — button hierarchy, changelog feature, workflow fixesmain
SpecWorkbench
Mar 3, 2026feat-button-hierarchy-2026-mar-03Button action hierarchy and style updatesstaging
SpecWorkbench
Mar 2, 2026REV-2026-mar-02Filter preview updates and button outline style refinementstaging
SpecWorkbench
Feb 27, 2026REV-2026-02-27Filtering pattern, select behavior, surface colors, sidebar icons, component library reorgstaging
SpecWorkbench