Assure One Design System
The single source of truth for visual design across all Assure products.
- Tokens — semantic CSS variables (color, type, spacing, motion) consumed by every product.
- Primitives — accessible React components built on Radix + Tailwind 4.
- Composites — common patterns (DataTable, PageHeader, Sidebar, Kanban).
Documentation
docs/components.md— the component catalogue: every public component by need and category, what it replaces, what not to use it for, deprecated aliases and their codemods. Generated from the API reports; ships in the package (node_modules/@assure-one/design-system/docs/components.md).docs/for-ai-agents.md— the short rulebook for AI agents and new contributors: never raw HTML, theFieldrecipe,classNamevsclassNames, theds:prefix, tokens, codemods, testing, what is experimental, how to propose a component.CHANGELOG.md— release-by-release log of what shipped.CONTRIBUTING.md— dev loop, release flow, versioning, pitfalls.CLAUDE.md— invariants and conventions for AI assistants working in this repo.docs/integration/css.md— how an application loads our CSS: layer order, import sequence, the compatibility preset, removing@source, runtime brand theming.docs/testing.md— rendering the real package in your app's Jest tests (@assure-one/design-system/testing, experimental).docs/forms.md— building a form:Field+ design-system controls + a Server Action + zod, reset andrequired, what each control submits, and testing it withfillField/selectOption/pickDate.docs/data-tables.md— building a data table: the headlessuseDataTablecontroller (sorting, pagination, search, selection, column visibility, filters — each controllable), servermanualmode, and the URL-state recipe.docs/design-system/icons.md— the icon catalogue and its server-safe entry (@assure-one/design-system/icons, experimental): import an icon in a Server Component without a client boundary.docs/adr/010-design-system-provider.md— the optionalDesignSystemProvider(messages, locale, link adapter) and the/nextentry; the quick start is below.claude-skills/— drop-in Claude Code skill for consuming projects.
Install
pnpm add @assure-one/design-system
Load the design system's CSS from your app's global stylesheet, as the split entries, under one layer statement (Tailwind 4 in your build):
/* src/app/globals.css — the layer statement is the first line of the file */
@layer theme, base, ds, components, utilities;
@import "tailwindcss";
@import "@assure-one/design-system/css/tokens.css";
@import "@assure-one/design-system/css/legacy-aliases.css"; /* optional */
@import "@assure-one/design-system/css/tailwind.css";
@import "@assure-one/design-system/css/shadcn.css"; /* optional */
@import "@assure-one/design-system/css/base.css"; /* optional */
@import "@assure-one/design-system/css/components.css";
@import "./ds-preset.css"; /* your app's preset, optional */
Then use components:
import { Button } from "@assure-one/design-system";
export default function Page() {
return <Button variant="primary">Click me</Button>;
}
The split CSS entries
The split entries are the documented default CSS integration since 1.40. Every
design-system class is ds:-prefixed and compiled into css/components.css
inside @layer ds, so your own utilities always win over component styles and
your Tailwind build never has to scan our package.
| Subpath | What it is |
|---|---|
./css/tokens.css | The --ds-* design tokens: :root, the colour scheme scope and the brand scopes. Usable without any component. |
./css/legacy-aliases.css | Read-aliases from today's token names to the namespaced ones (--color-surface: var(--ds-color-surface)), for as long as your own CSS reads design-system token names. |
./css/tailwind.css | A Tailwind @theme bridge, so bg-surface/40, hover:text-fg-3 and rounded-control compile in your build instead of silently producing nothing. |
./css/shadcn.css | The app-vocabulary bridge: background, foreground, primary, muted, destructive, the radius scale and the rest, each reading one --app-* input you can override. |
./css/base.css | Optional document defaults: body background, ink, font and color-scheme. Ships no preflight, no element rules and no font import. |
./css/components.css | The component utilities compiled with Tailwind's ds prefix (ds:flex, ds:bg-surface), inside @layer ds and bound to the --ds-* tokens. |
Import order is tokens.css → legacy-aliases.css (optional) → tailwind.css
→ shadcn.css (optional) → base.css (optional) → components.css, after your own
@import "tailwindcss" and under the layer statement
@layer theme, base, ds, components, utilities;.
docs/integration/css.md has the full
sequence, where the per-app preset goes, why no @source into our package is
needed, and the fixture run behind each of those claims. The reasoning lives
in ADR-004 and
ADR-005.
styles.css (deprecated)
@assure-one/design-system/styles.css — the all-in-one sheet with its own
preflight, a Google Fonts @import and the frozen unprefixed legacy utilities —
is deprecated since 1.40 (registry C-CSS-STYLES). It still ships, unchanged,
and is removed in 3.0; the built file opens with a deprecation
banner. Moving to the split entries is a deliberate visual change (document font
size, fonts, preflight): follow docs/integration/css.md
§6 and docs/migration/1.40.md.
Runtime brand theming (experimental)
For a tenant colour known only at runtime, @assure-one/design-system/tokens
exports createBrandTheme(): a validated seed becomes the custom properties a
built-in brand scope declares, ready to spread into a style attribute.
import { createBrandTheme } from "@assure-one/design-system/tokens";
<div style={{ ...createBrandTheme({ brand: "#2258d8", accent: "#f59e0b" }) }}>…</div>;
It is pure and SSR-deterministic, clamps a fill too pale to read against the
canvas, and guarantees WCAG AA for every foreground it emits. The seed shape is
gated by decision D7 and may change while that decision is open — see
docs/integration/css.md.
Brand artwork
The Assure wordmarks and marks Logo renders ship inside the package, under
public/brand/. They are served by your app, not imported, so copy them once
into your public folder:
cp -R node_modules/@assure-one/design-system/public/brand public/brand
An app served from a subpath calls setAssetBasePath("/my-base") once before
first render instead of (or as well as) moving the files. A Logo whose
artwork does not load falls back to the brand glyph + text lockup, so nothing
is required to get a readable lockup.
The provider and the /next entry (experimental)
Nothing requires a provider: every component renders English strings, formats
with en-US and (for LinkButton / Logo) renders a plain <a> / <img>.
On Next.js, render NextDesignSystemProvider to get next/link routing and
next/image: the root imports no next/* since 2.0. DesignSystemProvider is the one optional, thin, client-side provider
for what CSS cannot do — the generic strings, the locale, the text direction,
the portal container, the link and image adapters, shared tooltip defaults and
(opt-in) the toast region. It renders no DOM wrapper (ADR-010).
// app/layout.tsx (Next.js) — the Next adapters are filled in for you
import { NextDesignSystemProvider } from "@assure-one/design-system/next";
export default function RootLayout({ children }) {
return (
<html lang="de">
<body>
<NextDesignSystemProvider
locale="de-DE"
messages={{ select: { clear: "Auswahl löschen" }, combobox: { empty: "Keine Treffer" } }}
>
{children}
</NextDesignSystemProvider>
</body>
</html>
);
}
messages— any subset of the typed catalogueDsMessages, by component namespace (select,searchInput,combobox,field,fileUpload; more join as the overlays adopt it). Resolution is always component prop > provider > English default, per key. The full English list issrc/foundation/messages/en.json; parameterised messages are functions (combobox.create: (query) => string).locale— a BCP 47 tag, default"en-US"on the server and the client. Pass the locale your app already knows server-side; the design system never readsnavigator.languageduring render, so server and client HTML agree.linkComponent/imageComponent— whatLinkButtonandLogorender through. Without them the components render a plain<a>/<img>(full page navigation). A non-Next app passes its router's link; a Next.js app usesNextDesignSystemProvider.nextis an optional peer, needed only by the/nextentry.portalContainer,dir,tooltip,toasts— documented on the props.toastsmounts the toast region foruseToast(); leave it off if you already renderToastProvider.
Lint configs for consumers
Two shareable configs ship with the package (experimental, W7-18) so a product lints for the same things this repository ratchets on itself. Every rule is a warning: adopt it on the code you have, record the count, and ratchet it down.
// eslint.config.mjs
import ds from "@assure-one/design-system/eslint-config";
export default [...ds.configs.recommended];
// stylelint.config.mjs (stylelint is yours — an optional peer dependency here)
export { default } from "@assure-one/design-system/stylelint-config";
| Rule | Reports |
|---|---|
ds/no-raw-controls | raw <button>, <input>, <select>, <textarea> where a design-system control exists |
ds/no-radix-imports | a direct @radix-ui/* import instead of the primitive that wraps it |
ds/no-deep-imports | an import past the published entries (dist/…, src/… — ADR-011) |
ds/no-ds-token-redefinition | a --ds-* token set inline, outside a file under presets/ |
assure-ds/no-ds-token-redefinition | the same in CSS (stylelint; presets/ and *.preset.css are exempt) |
assure-ds/no-important-on-ds-selectors | !important on a ds:/data-slot selector — pass className/classNames instead |
Raise a rule to "error" in your own config once you are clean. Adoption per
app is tracked as W7-19.
Develop
pnpm install
pnpm storybook # http://localhost:6006
pnpm build # bundle for publishing
pnpm typecheck
pnpm lint
See CONTRIBUTING.md for the full dev loop, including iterating against a real consuming app.
Architecture
3-tier token system, authored as DTCG JSON in tokens/src
and generated into src/tokens/ and the ./css/* entries (ADR-003):
| Tier | Example (namespaced name → value) | Reads |
|---|---|---|
| reference | --ds-ref-brand-pro-fg: #6c42f8 | a literal; the raw ramps, one per brand |
| system (semantic) | --ds-color-action-brand-bg: var(--ds-ref-brand-pro-fg) | the reference tier; this is the public contract |
| component | --ds-menu-item-hover-bg: var(--ds-color-canvas-sunken) | the system tier; only where a component must be themable on its own |
Components consume system tokens, never reference values directly. Themes
(colour scheme, per-product brand, a runtime brand from createBrandTheme())
re-map system → reference without touching component code.
Every token has two published spellings: the namespaced --ds-* name above and
the legacy name it ships as today (--color-brand, --color-pro-fg,
--color-menu-item-hover-bg). css/legacy-aliases.css declares
<legacy>: var(<--ds-* name>) in the same scopes, so both resolve to the same
value — proved by pnpm tokens:ds-check.
Releases
Tag-driven publish to public npm. See CONTRIBUTING.md for the full flow; past releases in CHANGELOG.md.