Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Volodymyr Vreshch

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
170 changes: 142 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,178 @@
# @agentage/design-system

The Agentage design system — OKLCH design tokens + a React component library shared across the Agentage web dashboard, landing site, and showcase.
![Agentage Design System - OKLCH tokens + 60+ React components, ready to install](https://github.com/agentage/design-system/raw/master/docs/banner.svg)

Previously vendored as a package copy inside both the `web` and `landing` monorepos; this repo is the single source of truth (the copies had silently diverged on the same version number).
OKLCH design tokens and 60+ accessible React 19 components in one package. Install it, import
the theme, and you have a coherent dark/light UI kit - buttons through data tables, stat cards,
command palettes and docs layouts.

## Install
Browse every component live at **[ds.agentage.io](https://ds.agentage.io)**.

## What is this?

The design system behind the Agentage dashboard, admin console and public sites, published as a
plain npm package so anything can use it.

It is deliberately unopinionated about your framework. It ships **standard ESM with one file per
component**, so tree-shaking works, and every interactive component already carries `'use client'`,
so it drops into a Next.js App Router server tree without wrappers or `dynamic()` tricks. Colors
are defined in [OKLCH](https://oklch.com), so the light and dark themes are perceptually matched
rather than hand-tuned, and contrast holds up under WCAG AA.

## Get started

### 1. Install

```bash
npm install @agentage/design-system
```

Peer dependencies: `react` / `react-dom` >= 19, and `tailwindcss` >= 4 (optional — only needed if you consume the token layer).
Peers: `react` and `react-dom` >= 19. `tailwindcss` >= 4 is an optional peer - the components
carry Tailwind utility classes, so you need it unless you ship your own compiled CSS.

### 2. Load the theme

```tsx
// app/layout.tsx (or your root entry)
import '@agentage/design-system/theme.css';
```

### 3. Point Tailwind at the shipped bundle

## Usage
```css
/* globals.css - Tailwind v4, CSS-first config */
@import 'tailwindcss';
@import '@agentage/design-system/theme.css';

@source "../../node_modules/@agentage/design-system/dist/**/*.js";
```

Components (tree-shakeable ES module):
> Tailwind must scan the **shipped JS**, not the source, or every design-system class is
> tree-shaken out of your build. The glob has to resolve to wherever npm actually hoisted the
> package - in a workspace that is the repo-root `node_modules`, not the package-local one. This
> is the single most common cause of "the components render unstyled".

### 4. Use it

```tsx
import { Button, Card, Alert } from '@agentage/design-system';
import { Button, Card, CardHeader, CardTitle, StatCard } from '@agentage/design-system';

export const Panel = () => (
<Card>
<CardHeader>
<CardTitle>Memories</CardTitle>
</CardHeader>
<StatCard title="Stored" value="12,480" progress={64} />
<Button>Open</Button>
</Card>
);
```

### React Server Components
Full walkthrough: [ds.agentage.io/install](https://ds.agentage.io/install).

## What's in it

62 documented component pages across eight areas, each with props generated from the source:

| Area | What's there |
| ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| [Foundations](https://ds.agentage.io/components/foundations) (6) | Button, Badge, Card, Avatar, loading states, Separator |
| [Data Display](https://ds.agentage.io/components/data-display) (10) | Table, DataTable, EntityList, StatCard, Pagination, EmptyState, StatusDot, CodeBlock, Progress, UsageMeter |
| [Cards](https://ds.agentage.io/components/cards) (7) | Gauge, donut, score, funnel, heatmap, multi-stat and ranked-list cards |
| [Forms](https://ds.agentage.io/components/forms) (8) | Input, Label, Checkbox, RadioGroup, Slider, Combobox, DatePicker, ToggleGroup |
| [Feedback](https://ds.agentage.io/components/feedback) (13) | Alert, Toast, Tooltip, Modal, AlertDialog, Sheet, DropdownMenu, ContextMenu, Popover, HoverCard, Command palette, DangerZone |
| [Layout](https://ds.agentage.io/components/layout) (5) | PageHeader, Section, Sidebar, Resizable panels, ScrollArea |
| [Navigation](https://ds.agentage.io/components/navigation) (7) | Breadcrumb, Tabs, Heading, Prose, Kbd, Collapsible, Accordion |
| [Site & Docs](https://ds.agentage.io/components/site-docs) (6) | Markdown, CopyButton, Chip, Footer, TopBar, DocSidebar |

Every interactive component ships `'use client'` as the first line of its emitted module, and the build
preserves one output file per source module — so the barrel above is safe to import from a React Server
Component: only the components you actually use that need the client runtime cross the boundary.
Those 62 pages cover 152 exported components once composable parts (`CardHeader`, `TableRow`,
`CommandItem` and friends) are counted, plus focus-trap, scroll-lock and anchor-positioning hooks. Every one
has a per-component subpath in the [exports map](./package.json).

Per-component subpaths are also exported, for consumers that want to keep the RSC graph explicit:
## Server components

Every interactive component ships `'use client'` as line 1 of its emitted module, and the build
preserves one output file per source module. So the barrel is safe to import from a React Server
Component - only the components you actually use that need the client runtime cross the boundary.

```tsx
import { Card } from '@agentage/design-system/card'; // server component
import { Tabs } from '@agentage/design-system/tabs'; // 'use client'
import { Card } from '@agentage/design-system/card'; // server-safe
import { Modal } from '@agentage/design-system/modal'; // 'use client', already declared
```

Design tokens (OKLCH color scales + semantic tokens) as CSS — import once at your app root:
No server-safe module transitively imports a client module, so importing `Card` never drags a
client boundary into your server tree. Per-component subpaths exist for consumers who want to keep
that graph explicit; the barrel works too.

```css
@import '@agentage/design-system/theme.css';
## Theming

Three tiers, composed by `theme.css`:

| Layer | Import | What it holds |
| ---------------- | ---------------------------------------- | --------------------------------------------------- |
| `primitives.css` | `@agentage/design-system/primitives.css` | Raw OKLCH scales - 11 stops each, theme-independent |
| `tokens.css` | via `theme.css` | Semantic tokens, typography, shadows |
| `base.css` | via `theme.css` | Element resets |

Dark is the default. Switch with one attribute on `<html>`:

```tsx
document.documentElement.setAttribute('data-theme', 'light'); // dark | light | system
```

`theme.css` pulls in `primitives.css` (raw OKLCH scales), `tokens.css` (semantic tokens, typography, shadows), and `base.css` (element resets). `primitives.css` is also exported standalone.
`system` follows `prefers-color-scheme`. Override any semantic token in your own CSS to rebrand
without forking - see [ds.agentage.io/theming](https://ds.agentage.io/theming).

## Requirements

| Requirement | Version | Note |
| ----------- | ------- | --------------------------------------------------- |
| React | >= 19 | Peer, required |
| React DOM | >= 19 | Peer, required |
| Tailwind | >= 4 | Optional peer - needed unless you ship your own CSS |
| Node | >= 22 | Build/dev only; the package itself is browser code |

Ships ESM only, with a `.d.ts` beside every module.

## Develop

```bash
npm install
npm run dev # component showcase (Vite) on :5174
npm run verify # type-check + lint + format + test + build
npm run dev # component showcase (Vite) on :5174
npm run verify # type-check + lint + format + exports:check + test + build
npm run test:e2e # Playwright: axe WCAG A/AA scan + screenshot baselines
```

- `src/components` — the components (barrel-exported from `src/index.ts`; add `'use client'` as line 1 to any component that uses client-only React — state, effects, refs, context, DOM APIs)
- `src/styles` — the OKLCH token + base-style CSS
- `src/lib` — `cn` and helpers
- `dev/` — the Vite showcase playground
- `src/components` - the components, barrel-exported from `src/index.ts`. Add `'use client'` as
line 1 to anything using state, effects, refs, context, portals, DOM APIs or event handlers.
- `src/styles` - the OKLCH token and base-style CSS.
- `src/lib` - `cn` and helpers.
- `dev/` - the showcase deployed to ds.agentage.io.

Run `npm run exports:generate` after adding or renaming a component; `exports:check` fails
`verify` when the exports map drifts.

Regenerate Playwright screenshot baselines inside the CI image, never on the host - font metrics
must match:

```bash
docker run --rm --ipc=host -v "$PWD":/work -w /work -u "$(id -u):$(id -g)" -e HOME=/tmp \
mcr.microsoft.com/playwright:v1.62.1-noble npx playwright test --update-snapshots
```

## Release

Publishing to npm is release-gated (`.github/workflows/publish.yml`):
Publishing is release-gated (`.github/workflows/publish.yml`): bump `version` in `package.json`
with a `chore(release): vX.Y.Z` commit (or run the workflow via **workflow_dispatch**). On merge
to `master` the workflow runs `verify`, then `npm publish --access public --provenance`, and tags
the release.

## Contributing

Issues and PRs welcome at
[github.com/agentage/design-system](https://github.com/agentage/design-system). Branch off
`master`, keep `npm run verify` green, and add a test when you change interactive behavior.

1. Bump `version` in `package.json` with a commit like `chore(release): v0.3.1` (or run the workflow via **workflow_dispatch**).
2. On merge to `master` the workflow runs `verify`, then `npm publish --access public --provenance`, and tags the release.
## License

Requires an `NPM_TOKEN` repo secret with publish rights to the `@agentage` scope.
MIT - see [LICENSE](./LICENSE).
38 changes: 35 additions & 3 deletions dev/app.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use client';
import { useState } from 'react';
import { EmptyState, Heading, ToastProvider, ToggleGroup } from '../src';
import { CopyButton, EmptyState, Heading, ToastProvider, ToggleGroup } from '../src';
import { DocsNav } from './components/docs-nav';
import { Search } from './components/search';
import { BrandedLogo } from './lib/icons';
import { BrandedLogo, GitHubIcon, NpmIcon } from './lib/icons';
import { Link, RouterProvider, useRoute } from './lib/router';
import { CategoryPage } from './pages/category-page';
import { ComponentPage } from './pages/component-page';
Expand All @@ -18,6 +18,13 @@ const TOP_NAV = [
{ to: '/theming', label: 'Theming' },
];

const INSTALL_CMD = 'npm i @agentage/design-system';

const EXTERNAL_LINKS = [
{ href: 'https://www.npmjs.com/package/@agentage/design-system', label: 'npm', Icon: NpmIcon },
{ href: 'https://github.com/agentage/design-system', label: 'GitHub', Icon: GitHubIcon },
];

const AllCategories = () => (
<div className="space-y-6">
<Heading as="h1" description="Every component, grouped by what it is for.">
Expand Down Expand Up @@ -80,7 +87,7 @@ const Shell = () => {
<Link
key={item.to}
to={item.to}
className={`rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${
className={`whitespace-nowrap rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${
path.startsWith(item.to)
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-foreground'
Expand All @@ -92,6 +99,31 @@ const Shell = () => {
</nav>
</div>
<div className="flex items-center gap-3">
<div className="hidden items-center gap-1 rounded-md border border-border bg-sidebar py-1 pl-3 pr-1 xl:flex">
<code className="whitespace-nowrap font-mono text-xs text-muted-foreground">
{INSTALL_CMD}
</code>
<CopyButton
text={INSTALL_CMD}
iconOnly
size="icon-sm"
variant="ghost"
label="Copy install command"
/>
</div>
{EXTERNAL_LINKS.map(({ href, label, Icon }) => (
<a
key={href}
href={href}
target="_blank"
rel="noreferrer"
aria-label={label}
title={label}
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
>
<Icon />
</a>
))}
<Search />
<ToggleGroup
value={theme}
Expand Down
10 changes: 10 additions & 0 deletions dev/lib/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,13 @@ export const MailIcon = () => (
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
);
export const GitHubIcon = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 .5A11.5 11.5 0 0 0 .5 12a11.5 11.5 0 0 0 7.86 10.92c.58.1.79-.25.79-.56v-2c-3.2.7-3.88-1.37-3.88-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.2 1.77 1.2 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.55-.29-5.23-1.28-5.23-5.7 0-1.26.45-2.29 1.19-3.1-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.18 1.18a11 11 0 0 1 5.79 0c2.2-1.49 3.17-1.18 3.17-1.18.63 1.59.24 2.76.12 3.05.74.81 1.18 1.84 1.18 3.1 0 4.43-2.69 5.4-5.25 5.69.41.36.78 1.07.78 2.16v3.2c0 .31.21.67.8.56A11.5 11.5 0 0 0 23.5 12 11.5 11.5 0 0 0 12 .5Z" />
</svg>
);
export const NpmIcon = () => (
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z" />
</svg>
);
Loading
Loading