@fireworks-ai/design-system
Shared React components and design tokens for Fireworks AI products.
Component previews and docs (Storybook): https://storybook.preview.fireworks.ai
npm i @fireworks-ai/design-system
Requirements
| Dependency | Requirement |
|---|---|
react / react-dom | ^19.0.0, peer, required |
next | ^16.0.0, peer, required |
react-hook-form | ^7.62.0, peer, optional — only needed for ./components/ui/form |
tailwindcss | ^4.0.0, required |
Usage
Components
Import each component from its own subpath.
import { Action } from '@fireworks-ai/design-system/Action';
import { DataTable } from '@fireworks-ai/design-system/DataTable';
Then point Tailwind at the compiled components. Tailwind only generates CSS for classes it finds
while scanning, and it never scans node_modules — so skip this and every component renders
unstyled, with nothing to tell you why.
@source "../node_modules/@fireworks-ai/design-system/dist";
Adjust that path so it resolves to node_modules from wherever your CSS file sits. For the full
list of subpaths, run npm view @fireworks-ai/design-system exports.
Theme and cn
Tailwind v4 is required. The stylesheet uses v4-only syntax (@theme, @utility, @source),
so v3 cannot parse it, and there is no tailwind.config.js route to the tokens. Run
npx @tailwindcss/upgrade first if you are still on v3.
Import theme.css after tailwindcss, and import it as a whole. It is the only stylesheet the
package exports, and it pulls in four files whose resets only work in that order.
@import 'tailwindcss';
@import '@fireworks-ai/design-system/styles/theme.css';
That import replaces Tailwind's palette, type scale, and font weights — it does not extend them.
It also styles bare h1–h6 and ships a scrollbar class for scroll containers. Anything it
drops stops compiling, silently:
- Colors.
grayruns50–900(plus a750), the other families offer50 / 100 / 300 / 500 / 700, and there iswhite.slate-500and the rest of Tailwind's palette are gone. - Type scale.
xs sm base lg 2xl 3xl 4xl 5xl— note there is notext-xl. Every step carries its own line-height and letter-spacing, so a size class is already a finished type style; do not addleading-*ortracking-*on top. - Font weights. Only
font-normal,font-medium, andfont-semiboldsurvive.font-boldproduces nothing at all.
Use cn from this package to merge a className, rather than building your own twMerge. This
instance is configured with the package's @utility classes; a stock one mistakes text-eyebrow
for a color, so cn('text-eyebrow', 'text-gray-900') quietly drops it.
import { cn } from '@fireworks-ai/design-system/lib/utils';
Color tokens
The CSS variables hold RGB triples instead of hex, which lets hand-written CSS pick its own alpha:
rgb(var(--gray-900) / 0.4).
The same values also ship as plain TypeScript constants, with no CSS side effects. A test keeps the two in sync.
import { colors } from '@fireworks-ai/design-system/tokens/colors';
colors.gray[900]; // '#18181B'
Use the constants only where a CSS variable cannot reach — chart configs, canvas, SVG. If the value ends up in a stylesheet, use the utility class instead.
Token guard
check-design-tokens catches colors and font sizes that bypass the palette and the type scale:
Tailwind arbitrary values (bg-[#fff], text-[13px]), and raw values anywhere else — #6720FF in
a constant, rgb(24 24 27) in CSS, fontSize: 12 in an inline style.
bun run lint:tokens # this repo, all packages
You hit a violation. Now what?
Almost always the fix is to reach for the token. Which form depends on where the value lives:
<div className="bg-gray-900" /> // markup: a utility class
<div style={{ fontSize: 'var(--text-xs)' }} /> // inline style: the CSS variable
canvasContext.fillStyle = colors.green[500]; // canvas, SVG props: the TS constant
Reach for colors (from @fireworks-ai/design-system/tokens/colors) only in the last case — an API
that wants a real color string and cannot resolve var().
If the value genuinely cannot be a token — a third-party widget's colors, artwork that reproduces some other product's UI — say so on the line above it:
{/* design-system-exempt: macOS traffic light */}
<span className="bg-[#ff5f57]" />
The reason is required; design-system-exempt: with nothing after it is itself an error. A marker
covers exactly one line — the line below it when it stands alone, its own line when it trails code
— so two hardcoded values need two markers. Prefer the line above: a formatter will move a trailing
JSX comment onto its own line and break the pairing.
Setting the guard up in your own repo
Install the package, then point the CLI at your source and your config:
"lint:tokens": "check-design-tokens ./src --config ./design-tokens.config.mjs"
Exit code 0 means clean, 1 means at least one error, 2 means the command itself was wrong — a bad path or an unloadable config. Warnings never fail the run.
The package only ships rules that hold everywhere. Your own chart directories and generated assets are yours to declare — it cannot guess where they live:
/** @type {import('@fireworks-ai/design-system/guards').GuardConfig} */
export default {
pathExemptions: [
{ pattern: /\/(?:analytic|vis)\//, reason: 'Deferred: chart and data-viz colors' },
{ pattern: /\/components\/assets\/icons\.tsx$/, reason: 'Brand artwork, not a theme choice' },
],
};
rules takes the same shape if you need one of your own; run check-design-tokens --list-rules to
see everything currently in effect. A full example lives in apps/web/design-tokens.config.mjs.
Contributing
Development
Run the dev script to start a watching build. From the repo root,
bun run dev --filter=@fireworks/web... runs both the web app and the design system.
Building
bun run build in this package, or bun run build --filter=@fireworks-ai/design-system from
the repo root.
Adding new components
[!IMPORTANT] All components need to be defined within the
exportsfield of thepackage.jsonfile!
{
"exports": {
"./NewComponent": {
"types": "./dist/NewComponent.d.ts",
"import": "./dist/NewComponent.js"
}
}
}
Import other files in this package with relative paths. Self-referencing subpath imports such as
@fireworks-ai/design-system/Action survive into dist/, which breaks consumers whenever the
package is renamed, and the ~/ alias is not resolvable at all outside this repo.
Adding components from shadcn/ui
Run npx shadcn add <component>, then fix the output before committing:
- Colors:
slate-*→gray-*(slate-950→gray-900) - Radius:
rounded-md→rounded-8,rounded-lg→rounded-12,rounded-xl→rounded-16 - Focus ring:
ring-ring→ring-purple-500,ring-offset-background→ring-offset-white
The CLI is configured to inline concrete colors (cssVariables: false) and skip CSS files, so
the generated component is self-contained — just wrong palette.
Releasing
Run publish-design-system.yml manually from
the Actions tab, with bump set to patch, minor, or major. The workflow bumps the version
itself, so do not edit version by hand. It validates first — build, typecheck, tests, a packaging dry run, and a check that the version is not already on npm — then opens a release PR carrying the bumped package.json and bun.lock, publishes, and finally tags the commit it was dispatched from as design-system-v<version>. Merge that PR once the run is green: while main still points at the published version, the next release fails its preflight check.