The family’s one colourless stoneA design system that is generated
Every design system asks you to assemble it: pick the palette, hand-tune dark mode, hope the contrast holds, then write the docs twice. UI-Kit takes one brand colour and emits the rest — a contrast-verified theme in both modes, fifty components speaking one vocabulary, and the machine-readable contracts a coding agent needs so it stops guessing at your UI from whatever library dominated its training data.
$ npx @quxkit/ui-kit-cli init --brand "#44403D"
accent 11 steps, anchored exactly at 900
neutral hue borrowed from the brand
status subtle harmony — info +10.9°, success -9.9°
contrast 13 roles moved, 0 failures
wrote src/ui-kit/theme.css
src/ui-kit/tones.ts
src/ui-kit/ui-kit.d.ts
$ npx @quxkit/ui-kit-cli add button field cardThis page is wearing its own output.
The crystal in the mark above averages #44403D. Feed that to the engine and the eleven swatches below come back — perceptual lightness fixed per step, chroma pushed to the sRGB boundary and no further, the stone landing exactly on 900. It is the palette this page is painted in, and it was not chosen; it was derived.
Success, warning, danger and info are tinted toward the brand rather than left at their canonical hues — far enough to belong to the same system, never far enough that a warning stops reading as a warning. This is the hardest case for that: a stone with almost no chroma has almost no hue to lend, so the shifts stay under eleven degrees and every status survives intact. Four levels, from none to harmonic, where each status is placed on a named interval from the brand.
Aimed at the parts that make a design system fail.
Existing kits are good at the parts a design system makes easy, and quiet about the rest. These six are the rest.
Ramps that survive the gamut
Palettes generated from a brand colour go muddy because HSL ramps clip and hue-rotate unevenly. These are built in Oklch on a fixed perceptual lightness ladder, with chroma clamped per step to the real sRGB boundary found by binary search — so the eleventh step is still the colour you asked for, only darker.
Contrast is an obligation, not a check
Every role that carries text declares a contrast target in APCA Lc and is moved until it clears it. The build reports what it measured and what it corrected: for the stone this page is painted in, thirteen roles were moved and nothing failed.
Both modes, from one spec
Dark mode is not a second palette hand-tuned into quiet divergence. Roles resolve at arbitrary lightness per mode from a single declaration, and both are generated and verified together.
One vocabulary, fifty components
No per-component prop dialect — no colour, colorScheme, contained, primary. Every component takes tone, variant, size and radius. An unknown value throws with the legal set instead of quietly rendering an unstyled control.
The theme declares the vocabulary
Adding a secondary tone widens the Tone type, the runtime validator, the linter, the agent contract and the stylesheet from one place. A theme cannot express a colour the components refuse to accept.
Owned components, upgradable engine
Behaviour and tokens install as versioned packages you upgrade. Styled compositions are copied into your repo by UI-Kit add and are yours to edit outright — the usual trade between the two, refused.
Contracts, not vibes.
Docs and prop tables drift from the code. Here the contracts are generated from the recipes and extracted from the TypeScript source, so a renamed prop is renamed in the published contract in the same commit — and an MCP server serves them live.
ui_kit_vocabularyui_kit_list_componentsui_kit_get_componentui_kit_check_usageui_kit_resolve_tokenFive packages, one engine.
@quxkit/ui-kit-tokens
The colour engine. Oklab/Oklch conversion, gamut boundary search, APCA measurement and repair, ramp and role generation, four emitters. Zero dependencies.
@quxkit/ui-kit-core
The facet vocabulary, the tone contract, and the recipe engine that compiles style data to CSS. Zero dependencies, framework-agnostic.
@quxkit/ui-kit-registry
Fifty React components as recipe plus implementation, and the generator that publishes registry.json and the per-component agent contracts.
@quxkit/ui-kit-cli
init · theme · add · rules · check — generate the theme, copy components in, lint what was written.
@quxkit/ui-kit-mcp
Five MCP tools serving the registry and the theme engine to a coding agent.
Not the same thing as the billing components. billing-kit-components ships finished billing screens — pricing, usage, ledger, checkout — for an app that already has a design system. UI-Kit is the design system: the layer underneath, for the rest of the product.
Try it
Press a colour. The whole page is rebuilt.
Not a preview pane — the page. Every component, both modes, all eleven ramp steps and every contrast pair are regenerated in the browser, because the generator has no dependencies and no build step to hide behind.
success+6.4° from canonicalwarning-10° from canonicaldanger-4° from canonicalinfo+2.9° from canonical
| Role | Light | Dark | Lc | Target |
|---|---|---|---|---|
fg-default | #17191e | #f2f3f6 | 104.4 | 90 |
fg-muted | #55585e | #ccced2 | 84.8 | 75 |
fg-subtle | #7d8086 | #b2b4b9 | 66.9 | 60 |
fg-on-accent | #fdfdff | #fdfdff | 93.9 | 75 |
fg-accent | #2e50b8 | #b7cdff | 84.1 | 75 |
fg-on-danger | #fffdfd | #fffdfd | 75.6 | 75 |
fg-on-warning | #fffdfb | #fffdfb | 75.2 | 75 |
border-strong | #a6a9b0 | #95989f | 46.3 | 45 |
The catalogue
50 components. Here are eight.
Everything HeroUI ships, plus the contracts. Each specimen below is live and each Code tab shows the file that rendered it — read from disk at build time, so the sample and the demo cannot disagree.
Button
A control that performs an action when activated.
import { Button } from '@quxkit/ui-kit-registry'
export default function ButtonDemo() {
return (
<div className="demo-row">
<Button>Save changes</Button>
<Button variant="soft">Duplicate</Button>
<Button variant="outline">Export</Button>
<Button variant="ghost">Cancel</Button>
<Button tone="danger">Delete</Button>
<Button loading>Publishing</Button>
<Button disabled>Unavailable</Button>
</div>
)
}
Field, Select, Switch, Checkbox
Every control that takes a label routes its label, description and error through one shared shell, so none of them can be accessible while another is not.
We only use this for billing.
import { Checkbox, Field, Select, Switch } from '@quxkit/ui-kit-registry'
export default function FieldDemo() {
return (
<div className="demo-stack">
<Field
label="Work email"
type="email"
description="We only use this for billing."
defaultValue="[email protected]"
/>
<Select
label="Plan"
placeholder="Choose a plan"
items={[
{ value: 'free', label: 'Free' },
{ value: 'pro', label: 'Pro' },
{ value: 'scale', label: 'Scale' },
]}
/>
<Switch label="Two-factor authentication" defaultChecked />
<Checkbox label="Email me about outages" defaultChecked />
</div>
)
}
Alert
A message about the state of the system or the result of an action.
import { Alert, Button } from '@quxkit/ui-kit-registry'
export default function AlertDemo() {
return (
<div className="demo-stack">
<Alert
tone="danger"
title="Upload failed"
actions={
<Button size="sm" tone="danger" variant="outline">
Retry
</Button>
}
>
The file is larger than the 25 MB limit.
</Alert>
<Alert tone="success" title="Deployment complete">
Version 4.2.0 is live on production.
</Alert>
<Alert tone="info" variant="outline" title="Scheduled maintenance">
The API will be read-only on Sunday from 02:00 to 04:00 UTC.
</Alert>
</div>
)
}
Table
Rows of records with columns that can be sorted and selected.
ScopeSorting is reported and requested, not performed — you sort the data. No virtualisation, column resizing, or pinned columns.
| Customer | |||
|---|---|---|---|
| INV-0041 | Acme Corp | $4,210.00 | |
| INV-0040 | Globex | $980.00 | |
| INV-0039 | Initech | $12,400.00 |
import { Table, type SortState } from '@quxkit/ui-kit-registry'
import { useState } from 'react'
const INVOICES = [
{ id: '1', number: 'INV-0041', customer: 'Acme Corp', amount: '$4,210.00' },
{ id: '2', number: 'INV-0040', customer: 'Globex', amount: '$980.00' },
{ id: '3', number: 'INV-0039', customer: 'Initech', amount: '$12,400.00' },
]
export default function TableDemo() {
const [selected, setSelected] = useState<string[]>(['2'])
const [sort, setSort] = useState<SortState>({ column: 'amount', direction: 'descending' })
return (
<Table
caption="Invoices from the last 30 days"
columns={[
{ key: 'number', header: 'Invoice', sortable: true },
{ key: 'customer', header: 'Customer' },
{ key: 'amount', header: 'Amount', align: 'end', sortable: true },
]}
rows={INVOICES}
getRowId={(row) => row.id}
sort={sort}
onSortChange={setSort}
selected={selected}
onSelectionChange={setSelected}
/>
)
}
Modal, Dropdown, Toast
Overlays are native <dialog> elements, so the focus trap, the inert background and Escape come from the platform rather than from a hook.
import { Button, Dropdown, Modal, useToast } from '@quxkit/ui-kit-registry'
import { useState } from 'react'
export default function OverlayDemo() {
const toast = useToast()
const [open, setOpen] = useState(false)
return (
<div className="demo-row">
<Button onClick={() => setOpen(true)}>Delete project</Button>
<Dropdown
trigger={
<Button variant="outline" tone="neutral">
Actions
</Button>
}
items={[
{ label: 'Rename', onSelect: () => toast.show({ tone: 'success', title: 'Renamed' }), shortcut: 'R' },
{ label: 'Duplicate', onSelect: () => toast.show({ tone: 'success', title: 'Duplicated' }) },
{ separator: true },
{ label: 'Delete project', onSelect: () => setOpen(true), tone: 'danger' },
]}
/>
<Modal
open={open}
onClose={() => setOpen(false)}
title="Delete this project?"
description="Its 1,204 documents will be permanently removed."
footer={
<>
<Button variant="ghost" onClick={() => setOpen(false)}>
Keep project
</Button>
<Button tone="danger" onClick={() => setOpen(false)}>
Delete project
</Button>
</>
}
>
<p style={{ margin: 0 }}>This cannot be undone.</p>
</Modal>
</div>
)
}
Tabs
Alternative views of the same region, one shown at a time.
4,210 of 10,000 credits used this month.
import { Tabs } from '@quxkit/ui-kit-registry'
export default function TabsDemo() {
return (
<Tabs
items={[
{
value: 'overview',
label: 'Overview',
content: <p className="demo-body">4,210 of 10,000 credits used this month.</p>,
},
{
value: 'usage',
label: 'Usage',
content: <p className="demo-body">Peak was Tuesday, at 612 credits.</p>,
},
{
value: 'billing',
label: 'Billing',
content: <p className="demo-body">Next invoice on 1 September.</p>,
},
]}
/>
)
}
Chip, Badge, Avatar, User
The small pieces that carry status and identity.
import { Avatar, AvatarGroup, Badge, Button, Chip, User } from '@quxkit/ui-kit-registry'
export default function DisplayDemo() {
return (
<div className="demo-stack demo-stack--center">
<div className="demo-row">
<Chip tone="success">Active</Chip>
<Chip tone="warning">Expiring</Chip>
<Chip tone="danger">Failed</Chip>
<Chip>Draft</Chip>
<Chip tone="accent" variant="solid">
Pro
</Chip>
</div>
<div className="demo-row">
<Badge content={3} tone="danger" label="3 unread">
<Button variant="outline" tone="neutral" size="sm">
Inbox
</Button>
</Badge>
<AvatarGroup max={3} size="sm">
<Avatar name="Ada Lovelace" />
<Avatar name="Grace Hopper" />
<Avatar name="Katherine Johnson" />
<Avatar name="Annie Easley" />
</AvatarGroup>
<User name="Ada Lovelace" description="Owner" />
</div>
</div>
)
}
Calendar
A month grid for choosing a date.
ScopeGregorian calendar only, with locale-aware month and weekday names. No week numbers and no multi-month view.
August 2026
| Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday |
|---|---|---|---|---|---|---|
import { Calendar } from '@quxkit/ui-kit-registry'
import { useState } from 'react'
export default function CalendarDemo() {
const [date, setDate] = useState('2026-08-19')
return <Calendar label="Delivery date" value={date} onChange={setDate} />
}
Pricing
The system is free. The shortcuts are not.
Everything that makes the components work is open source. What you can pay for is the time you would otherwise spend building the surrounding material.
FreeMIT, forever
- All 50 components
- The theme engine
- Agent contracts
- MCP server
$180per seat, once
- Everything in Open
- Figma library generated from the theme
- Twelve application templates
- Brand kit export for stakeholders
Talk to usorg-wide
- Everything in Studio
- Private component registry
- Theme review and migration
- Priority support
Give it one colour.
A verified theme, fifty components and the agent contracts come back — and the whole system is MIT, free to install and free forever. What you can pay for is the Figma library, the templates and the private registry.