@crisp-ui-kit/crisp
v0.51.0
Published
A lean React design system, measured pixel-for-pixel from Attio.
Maintainers
Readme
crisp
A lean React design system, measured pixel-for-pixel from Attio. Every component's spacing, radius, shadow, and motion is read off the live app and reproduced verbatim — then guarded by a spec check so it can't drift.
- 60+ components — primitives (Button, Input, Select, Menu, Dialog, Table…) and product surfaces (RecordsTable, Board, Command, ListPage, RichText editor…).
- Design tokens — a small, semantic scale (
space,radius,text, colors) with automatic light/dark theming. - Typed — full TypeScript types for every component and variant.
- Lucide icons, CSS-variable theming, zero runtime CSS-in-JS.
Install
npm install @crisp-ui-kit/crisp
# or: bun add @crisp-ui-kit/crispReact 18+ is a peer dependency:
npm install react react-domcrisp ships ESM and CommonJS, so both import and require work. Every
component also has its own subpath, which is what keeps a one-component import
small (a lone Badge bundles to ~8 KB):
import { Badge } from "@crisp-ui-kit/crisp"; // barrel — tree-shakes
import { Menu } from "@crisp-ui-kit/crisp/menu"; // or import it directlyUsage
Import the stylesheet once at your app root, then use components anywhere:
// app entry (main.tsx / layout.tsx)
import "@crisp-ui-kit/crisp/base.css"; // optional app baseline
import "@crisp-ui-kit/crisp/styles.css";If your own styles need to beat crisp's, import @crisp-ui-kit/crisp/styles.layered.css
instead of styles.css. It is the same CSS wrapped in @layer crisp-components, and anything
you write outside a layer wins over anything inside one regardless of specificity or order.
base.css is optional: it sets body's font, background, and text color from
crisp's own tokens (plus box-sizing and color-scheme), so a fresh app doesn't
render Times New Roman on a transparent background. Import it first, before
styles.css.
styles.css already contains the design tokens, so that one import is enough.
@crisp-ui-kit/crisp/tokens.css is available separately for the rare case of
wanting the tokens without the component styles — theming another library
from crisp's palette, for instance.
import { Button, Badge, EmptyState } from "@crisp-ui-kit/crisp";
export function Example() {
return (
<div>
<Button intent="primary">Save changes</Button>
<Badge tone="success" dot>
Live
</Badge>
<EmptyState
variant="page"
title="No records yet"
description="Create your first record to get started."
action={<Button intent="primary" size="sm">New record</Button>}
/>
</div>
);
}Theming
Colors and tokens are CSS variables. Toggle dark mode by setting data-theme="dark"
(or class="dark") on a parent element; every component follows. Access the raw tokens
in JS via the exported contract:
import { contract as c } from "@crisp-ui-kit/crisp";
const styles = { padding: c.space["4"], borderRadius: c.radius.md };Building your own component
The same primitives the kit is built on are exported (defineRecipe, resolveRecipe,
Primitive) so you can author components in the same style.
For new components, prefer defineSpec: it takes the same styling as data — named slots
with typed declarations — rather than as a CSS string, and specToRecipe compiles it to the
recipe everything else already understands.
import { defineSpec, resolveRecipe, specToRecipe, contract as c } from "@crisp-ui-kit/crisp";
const spec = defineSpec({
name: "callout-lite",
base: { display: "flex", gap: c.space["2"], borderRadius: c.radius.md },
parts: [{ slot: "icon", selector: "& .icon", decls: { flexShrink: 0 } }],
variants: {
tone: {
neutral: { background: c.bgRaised, color: c.fgMuted },
danger: { background: c.bgDanger, color: c.fgOnTone },
},
},
defaultVariants: { tone: "neutral" },
});
export const calloutLite = resolveRecipe(specToRecipe(spec));The variant keys survive into the type, so VariantProps<typeof calloutLite>["tone"] is
"neutral" | "danger" and not string. A string recipe can only be rewritten and hoped over;
a spec can be read, which is what lets a non-CSS target refuse a property it cannot express
instead of silently dropping it. crisp's own primitives are moving to it component by component.
Documentation
The docs site renders a live, measured demo for every component. Run it locally:
cd docs && bun run dev # http://localhost:3000Credits
crisp stands on other people's work.
SEED Design by 주식회사 당근마켓 (Karrot),
Apache-2.0. Seven @seed-design/* packages drive crisp's DatePicker, TimePicker, Slider,
QuantityPicker, FileUpload and MiddleTruncate, and the geometry of those components — plus the
WheelPicker, the field focus rings and several neutral token values — is read from
@seed-design/css and remapped onto crisp's own token roles. The layering that crisp's
multi-platform work follows is theirs too: see ADR 0002.
Each derived file records what was taken and what changed; the full attribution, including
SEED Design's own notice, is in NOTICE.
Radix Slot and compose-refs (MIT) for asChild,
Zag (MIT) for the combobox and dialog state machines,
Lucide (ISC) for every icon, and clsx (MIT).
crisp is not affiliated with, sponsored by or endorsed by any of them.
License
MIT © Munkherdene
crisp's own code is MIT. It includes work under the Apache License 2.0 — see NOTICE and licenses/Apache-2.0.txt.
