Pick a color, drag the roundness, retype the whole system, and watch a real product surface re-skin under your hands: an app bar, stat cards, a form with fifteen controls, a sortable table, overlays, alerts, a calendar. When it looks right, take it with you as CSS, as a prompt for your coding agent, as JSON, or as a link.
| Group | Knobs |
|---|---|
| Presets | Six starting points (Manti, Violet, Ocean, Forest, Rose, Graphite), every value still editable afterwards |
| Color | A base color per variant (primary, secondary, tertiary, success, info, danger), each expanded into the full --variant-* role vocabulary |
| Neutral | One hue drives every gray, surface, border and text role; light/dark switch beside it |
| Shape | One radius factor rescaling the whole xs to 2xl ramp |
| Typography | Sans font stack and the base text size every other size derives from |
| Density | Spacing unit and control heights, plus focus-ring width |
Not sure where to start? Surprise me rolls a coherent random theme.
The Copy theme dialog hands the result over four ways:
- CSS tokens. A stylesheet you drop into your app, with the import order spelled out in its header.
- AI prompt. A brief for Claude Code, Cursor or any agent working in your repo: where to put the file, why it must stay unlayered, and how Manti's token tiers relate.
- JSON. The raw studio state, for storing themes in your own tooling.
- Share link. The whole theme base64url-encoded into
?t=, so a URL reopens the studio exactly as you left it. No account, no server.
Every knob writes a public token override, the same escape hatch a consumer
uses. The studio collects them into one unlayered <style> element in
<head>, which beats Manti's layered @layer manti.tokens defaults without a
single !important.
That is why the CSS in the export dialog is byte-for-byte the CSS the page you are looking at is running: one builder feeds both. Nothing here is studio-only. If the preview looks right, pasting the exported CSS into your app gives you the same result.
/* what a themed app ends up importing */
@import '@manti-ui/styles/index.css';
@import './manti-theme.css'; /* the studio's output, unlayered, last */Node >=22.12.0, pnpm 10.
pnpm install
pnpm dev # http://localhost:5173
pnpm verify # lint + typecheck + production build
pnpm preview # serve the built dist/ on :4173| Path | What lives there |
|---|---|
src/theme/customTheme.ts |
The theme store: state, token-CSS generation, persistence, URL sharing |
src/theme/useTheme.ts |
Light/dark, driven by data-theme on <html> |
src/studio/ThemeControls.tsx |
The control rail: presets, colors, hue, shape, type, density |
src/studio/ThemePreview.tsx |
The preview canvas, a product-shaped tour of the component set |
src/studio/ExportDialog.tsx |
CSS / AI prompt / JSON / share-link hand-off |
src/styles/studio.css |
App chrome, in its own @layer studio |
Adding a knob touches two places: ThemeConfig plus buildThemeCss() in
customTheme.ts, which is also what gets exported, and a control in
ThemeControls.tsx. The preview needs no change; it reads tokens like every
other Manti consumer.
The app depends on the published @manti-ui/* packages rather than a
workspace link, so it exercises the design system the way a third party does. To
try an unreleased library change, pnpm link the package from the
manti-ui/ui checkout.
Upstream backport:
src/styles/studio.cssends with a small block that repairs Slidermarkspositioning in@manti-ui/styles0.8.0. It is fixed upstream, so delete the block once this app depends on the release carrying it.
Netlify, from netlify.toml: pnpm build into dist/, a www to apex 301,
and a CSP that allows the preview's avatar portraits (i.pravatar.cc).
There is deliberately no SPA catch-all. The app has one route and a shared
theme rides in the query string, so /?t=... already resolves index.html as a
static file. A /* rewrite would answer every typo with a 200 and the full
studio, which reads to a crawler as unlimited URLs serving identical content;
unmatched paths get 404.html and a real 404 instead. Bring the rewrite back the
day this app grows a router.
MIT, same as Manti UI.