The design system shared by Adea and Cortana.
One themed component library, one token set, one shell layout — so two separate desktop applications read as one product. Neither application implements its own components.
bun install
bun run build # the library and declarations
bun run storybook:build # the full workshop bundle
bun run storybook # the workshop: every component, every variant, both themesbuild produces the publishable library. CI builds the workshop once in its
dedicated gate and shares that artifact across the ten browser shards. Successful
main pushes that update Storybook or the public registry payload publish that build at
adea-ai.github.io/ui; registry-only pushes
build the site without allocating story-browser shards.
bun run verify includes both builds and the package checks. Workshop prop
documentation is extracted from local components; dependency TSX is excluded
from extraction while its imported types remain available.
Draft pull requests allocate no Registry or Workshop runners. Mark a prepared
pull request ready to run the complete gates; later ready-PR updates rerun them.
Returning to draft cancels the active gate run. PR and main push gates follow
the changed paths: documentation outside the published package skips the heavy
lanes, distribution metadata selects Registry, and browser harness changes
select their affected lanes. Component, token, dependency, workflow and unknown
paths retain full coverage. PRs use the immutable merge-base diff; pushes use
the immutable before/after tree diff, including every commit in a batched push.
Deleted and renamed source paths count. Missing, zero or unavailable refs retain
all gates. The required Workshop aggregate rejects failed or cancelled lanes;
it accepts a skip only when change classification explicitly excluded that lane.
A custom themed and designed component library built on top of shadcn/ui, on SolidJS and Kobalte. It is not a fork of fifty shadcn primitives, and it is not a set of hand-rolled controls: shadcn supplies the token vocabulary and the variant convention, Kobalte and corvu supply the accessible behaviour, and every visual decision lives in one place.
- 90 registry entries — 88 shared UI modules across primitives, window layout, composites and feature modules, plus the theme stylesheet and shared library utilities.
- One token file. Semantic OKLCH colours, an eight-rung type scale, the standard control ladder plus a stable 48px touch target, radius, elevation, motion and a z-index stack. Contrast is measured in tests, not reviewed by eye.
- Four user-facing axes — appearance, theme, accent and typeface — plus density,
each a value on
ThemeProviderand a set of tokens. Thirty-four themes ship in the catalogue, every one validated against WCAG AA floors; the two defaults are held to AAA. - Dark-first, with light as an equal — not a lesser inversion.
- The shell is a component. Side rail, sidebar, top bar, status bar, panels and their geometry are tokens, so the two applications cannot drift apart by pixels.
- Tree-shaking is measured, not claimed. Importing one component costs a small fraction of the library, the chart splits from itself so one chart type is cheaper than seven, and the gate fails if either stops being true. The per-component figures are written by that gate, so they cannot go stale.
- Storybook 10 with per-story accessibility checks, MDX documentation and token galleries. The published Storybook is the review surface; if a component is not in it, it is not done.
- A shadcn registry of 90 items, so a consumer can take one component without adopting the package — or install the whole thing from npm.
# The package
bun add @adea-ai/ui
# One component, copied into your project
bunx shadcn@latest add https://adea-ai.github.io/ui/r/button.jsonThen, once, in your app's stylesheet:
@import 'tailwindcss';
@import '@adea-ai/ui/theme.css';
@import '@adea-ai/ui/base.css';
@import '@adea-ai/ui/fonts.css'; /* optional: the self-hosted typefaces */Or, if your app does not already import Tailwind:
@import '@adea-ai/ui/globals.css';
@import '@adea-ai/ui/fonts.css';Then wrap the app in the provider:
import { ThemeProvider } from '@adea-ai/ui'
export function App() {
return (
<ThemeProvider>
<YourApp />
</ThemeProvider>
)
}ThemeProvider is what applies a theme, an accent and a typeface, and what persists
the user's choice. Toggling dark on <html> by hand still works, and gets you the
two default variants — but no palette beyond them, no accent, no typeface.
If the app is server-rendered, inline themeScript
in <head> so the first paint is already in the right appearance.
See docs/consumption.md for the registry, the four axes, the versioning contract, and how to re-hue the palette.
packages/ui/
src/styles/theme.css every token. The single source of truth.
src/styles/base.css state variants, keyframes, named utilities, base layer.
src/lib/tokens.ts the token manifest: names, meanings, which vary by theme.
src/lib/variants.ts the shared size ladder and the interactive recipe.
src/components/ui/ primitives, one folder each: component, stories, index.
src/components/layout/ app shell, side rail, sidebar, top bar, status bar, panel, page.
src/components/composites/ list row, settings, stat.
registry.json the shadcn registry catalogue.
public/r/ one installable payload per item.
apps/storybook/
.storybook/ Storybook configuration and the theme toolbar.
styleguide/foundations/ the token galleries.
Stories live next to their component, so documentation and implementation
cannot drift apart. The styleguide folder holds only what needs more room than a
component folder: the token galleries, and the full-window shell compositions.
Five rules explain most of the decisions in this codebase.
1. A component never restyles another component. Appearance comes from the
component's own variants and size props. This is enforced by @shadcn/lint at
the class level, not by review — see docs/conventions.md.
2. Every decision is a token. No literal colour, size, radius or shadow appears
in a component. packages/ui/src/styles/theme.css is the only stylesheet with a
palette value in it — and even that file is generated from @adea-ai/themes, so
the authoritative copy of the colours lives in that package, not here.
src/lib/tokens.ts is complete against the generated file as a test.
3. The accessible behaviour is the primitive's job. Focus trapping, arrow keys,
typeahead, aria-activedescendant, roving tabindex — Kobalte and corvu already do
these correctly and are maintained. A hand-rolled version is a regression with
better styling.
4. Describe the "why", not the "what". Comments in this repository explain decisions a reader could otherwise reverse: why a dialog has no corner close button, why the rail's tooltip trigger is the rail row. The rule of thumb is that a comment restating what the code does is noise; a comment recording a constraint the code cannot show is the point.
5. If it is not in Storybook it is not finished. Every story is checked for
accessibility violations, and the design system's own lint rules run over the
stories too — a demo that restyles a Button is caught the same way application
code would be.
Both applications consume @adea-ai/ui and import components from the package
root. Neither keeps a local component library. The component-by-component mapping
from adea's current packages is in docs/consumption.md.
import {
AppShell,
AppShellBody,
AppShellMain,
SideRail,
SideRailContent,
SideRailHeader,
SideRailItem,
SidebarNav,
SidebarNavContent,
SidebarNavItem,
SidebarNavSection,
TopBar,
TopBarSearch,
TopBarSection,
TopBarTitle,
StatusBar,
StatusBarItem,
StatusBarSpacer,
Panel,
PanelBody,
PanelHeader,
PanelTitle,
Button,
} from '@adea-ai/ui'The full-window composition is documented in Storybook under Layout → App shell, and the exact arrangement each application uses is one of its stories.
The rule the shared components follow: the library owns the shape, the
application owns the model. Board takes the caller's column ids and a canDrop
predicate rather than knowing what a task is; AccountMenu takes the caller's item
list; EntityIcon takes a name and an optional glyph. A component that decided its
own list, its own icons or its own column names could only serve one product.
| Document | What it covers |
|---|---|
| docs/design-language.md | The visual decisions: palette, type, the control ladder, the rail |
| docs/conventions.md | How components are written here — variants, tokens, comments |
| docs/consumption.md | Installing, the registry, re-hueing, density, and the migration map |
| docs/design-system.md | The published design-system page: what builds it, how to refresh it |
See CONTRIBUTING.md. In short: branch from main, use
Conventional Commits, open a draft pull request, and make sure
bun run fmt && bun run lint && bun run typecheck && bun run test && bun run registry:validate is clean before marking it ready.
Apache-2.0. See LICENSE.
The visual language — the surface ladder, the accent role, the density, the radius scale, the focus treatment — is substantially translated and modified from KiroCrew, which is Apache-2.0. The translation is total: KiroCrew's React components and bespoke CSS custom properties are re-expressed here as Solid components on Kobalte and corvu, using shadcn's semantic token vocabulary. No KiroCrew source file is reproduced verbatim. See NOTICE for the full attribution and for the upstream licences of the libraries this system is built on.
SplitLayout renders the accepted binary model with stable opaque leaf owners.
Pass an accessor-aware renderLeaf, labelled panes, a controlled resize callback
and optional close callback returning the surviving focus ID. Content remains
mounted across split/resize/move; the host retains runtime/session/editor identity,
authorization and persisted layout scope. Close destroys only the removed owner.
Separators use Corvu pointer/keyboard behavior with physical ARIA orientation,
10–90 percent limits and references to the visible regions. Focus restoration is
instance scoped, cancels on disposal and respects newer external focus.
Three stories cover two panes, nested directions and the eight-pane limit. Run
bun run test:layout for isolated headless Chromium/WebKit interaction, light/dark
automated accessibility, CSP geometry and native Node SSR evidence. This is a
renderer foundation. Optional onMove enables pane-title dragging with typed
closest-edge feedback. Only a live drag from this instance can invoke the host;
foreign/plaintext/stale drops are rejected, and disposal/cancellation clears feedback.
renderPaneActions supplies stable accessor-aware host controls. The stories show
keyboard-accessible movement through those controls, plus toolbar split and undo.
Application shortcuts and persisted transitions remain host owned. Manual assistive
technology acceptance and production Adea/Cortana adoption remain pending. Actual
packed-renderer measurements belong to the dependency integration checkpoint;
source-only changes do not refresh that evidence. The existing packed-model probe remains a pure subpath
check and does not certify the renderer or library root.
Core controls may be imported from @adea-ai/ui without chart/carousel peers.
Charts, chart helpers/types, carousels and carousel helpers/types are public only
through @adea-ai/ui/components/ui/chart and
@adea-ai/ui/components/ui/carousel. Install their corresponding optional peers
when using those entries. Migrate existing root imports to those subpaths; this
intentional breaking change is declared in the commit and release notes.
Run bun run check:packed-layout-renderer after the library build. It installs
the actual tarball with lifecycle scripts disabled and optional chart/carousel
engines absent, then runs the same 36 interaction/SSR cases per compiled/Solid
browser condition in headless Chromium/WebKit. Required server rendering compiles
installed Solid source and executes the result in native Node; browser-compiled
output is not treated as server code. bun run check:packed-layout independently
checks the explicit pure-model subpath and full packed attribution.
CSS discovery is explicit (source(none)): the host fixture and renderer sources,
plus the close button's complete classes returned by the installed public
buttonVariants({ variant: 'ghost', size: 'icon-xs' }). This includes the base,
tactile, focus, disabled, ghost and square-icon styles. The fixture uses exactly
that shared Button; host header actions are native buttons. Unused Button variants
and unrelated automatically discovered CSS are excluded. Positive browser checks
require the close button's actual token-sized square and keyboard focus ring.
The current compiled measurement is 31,797 gzip JS bytes and 33,632 raw CSS bytes, within the unchanged 32 KiB/34 KiB caps. Gates require one Solid runtime/chunk, unmixed browser exports, external Solid/Corvu imports, full Apache LICENSE and donor MIT NOTICE, and no chart/carousel, terminal/editor/highlighter, conversation, theme-engine or font assets. Earlier partial CSS measurements are historical, not complete-control acceptance evidence. The full current run remains blocked by the separately queued UI #20 square-control token prerequisite; it has not passed all 72 cases or been released.
These checks are enforced by local verification, PR and publish workflows.
Install headless Chromium/WebKit with bunx playwright install chromium webkit
before local packed/browser verification. Actual Adea/Cortana production mounts,
full shell/persistence, hydration, native editor/terminal and manual AT remain
separate acceptance gates.