@sableui/react
v0.21.0
Published
Sable: themeable React components, layout patterns, and tools for coding agents.
Readme
Sable
Sable — a themeable React component system with layout patterns, semantic tokens and tools for coding agents. Default theme: Sable. On npm as @sableui/react; docs and the live catalog at sableui.com.
For a clickable iOS prototype (demos and presentations, not production), ask the
Sable skill to build with the iOS target. Every screen is Sable components in your
preset; the optional @sableui/react/ios adapter adds only the iOS system chrome
(status bar, navigation bar, tab bar, sheet presentation), drawn by the optional
konsta peer that the agent installs for that target. The prototype's document
imports Tailwind, Sable's styles, a preset, then @sableui/react/ios/styles.css.
Frame it with PhoneFrame from @sableui/react/ios/preview. See
building an iOS prototype.
Original Sable code is MIT licensed.
The featured presets are Sable (neutral), Meridian (square), Prism (compact technical),
Verdant (serif), Ember (warm), and Poppy (playful). Every preset supports light and
dark modes. Load its declared fonts and use ThemeSelector to offer preset choices.
Third-party notices list the software and fonts used
in Sable.
- Behavior: Base UI (
@base-ui/react) — accessible primitives, isolated behind our own component APIs. - Styling: Tailwind CSS v4 + semantic CSS-variable tokens; variants via
class-variance-authority. - Icons: opt-in through
IconProvider— Reicon's set ships ready to mount, or plug in any icon package. Apps pay only for the set they choose. - Motion: a token-driven, zero-dependency motion layer (recipes, policy, browser-native primitives).
- Themeable: components reference semantic tokens only; a theme is a CSS variable set.
A broad product component catalog, a fluid layout foundation, and a pluggable icon adapter — see registry.json.
Colour
Components paint roles (bg-card, bg-primary, text-muted-foreground), so a
theme restyles everything at once. For colour beyond the roles, every theme also
generates a palette: neutral, red, orange, yellow, green, teal,
blue, purple and pink (50–950), plus a scale under every tone. The
families lean toward the theme's brand, neutral is the theme's own page-to-ink
ramp, and steps follow the mode, so bg-blue-100 text-blue-800 reads in light and
dark. Use it for tags, avatars, labels and illustration; Badge, Pill and StatusDot
take a family as their tone (<Badge tone="teal">). Tailwind's other colour
families are switched off. See DESIGN.md → Generated palette.
Taste, style, and variety
A project can keep its own visual atlas at .sable/atlas/atlas.json: annotated
desktop, mobile and interaction captures with conditional transfer principles and
cautions. sable atlas --search retrieves contextual examples from it and --show
returns verified local image paths, which an agent must actually view. The package
ships no atlas; without one, work from the brief and the user's references.
When alternatives materially affect the brief, keep content and behavior fixed while
rendering bounded directions with meaningful differences in composition, typography,
density, imagery, story, or purposeful motion. sable explore --check design/directions.json
checks the specification and capture hashes; --compare creates an offline
comparison with anonymous labels and an explicit choice or tie. It exports a
review record for sable prefer --record. Preferences live in the consuming
project's .sable/preferences/, are scoped to a project, brand, person, or task,
and distinguish an actual human judgment from an agent observation. Human-only
retrieval is the default. These tools do not update model weights. See
sable docs developing-taste for the complete workflow.
Install & setup
Agents: the machine-readable version of these steps is the
consumeblock inregistry.json.
Quick start — install, then let sable init do steps 2–5:
pnpm add @sableui/react
pnpm exec sable init # or: npx sable init — once the package is installedinit reads your package manager from the lockfile and your framework from the project. It installs what is missing (React, Tailwind and its build plugin), puts the CSS imports below in order, adds React dedupe and the Tailwind plugin to a Vite config, loads the fonts, mounts IconProvider in a Vite starter's entry, installs the agent skill and its block in AGENTS.md / CLAUDE.md (sable setup --agent), offers the Claude Code Stop hook when you run it in a terminal, then runs sable doctor. Anything it cannot change safely — an unfamiliar config, a Next.js layout — it prints as the exact snippet and the file it belongs in. Vite and Next.js (App Router) get every step; other setups get the packages and the stylesheet. --dry-run shows every change first; --no-agent skips the skill. Already set up? sable doctor --fix applies the same fixes to whatever doctor flags.
The steps below are what init does, for doing it by hand or checking its work.
1. Install:
pnpm add @sableui/react2. Dedupe React, or Base UI resolves a second copy → "invalid hook call". Vite:
// vite.config.ts
export default defineConfig({ resolve: { dedupe: ["react", "react-dom"] } })3. CSS (order matters), in your root stylesheet:
@import "tailwindcss";
@import "@sableui/react/styles.css";
/* let Tailwind scan the package for the classes its components use */
@source "../node_modules/@sableui/react/dist/**/*.{js,mjs}";That @source generates the classes of every Sable component, about 32 KB of CSS gzipped. Once the app is built, npx sable sources --write src/sable-sources.css writes @source lines for just the modules it imports (12 KB for an app using Card, Button and DataTable); @import "./sable-sources.css"; in place of the line above. Re-run it after importing something new: sable check fails while the file is stale, since an unlisted component renders unstyled.
4. Fonts — the default theme expects Figtree (sans) + DM Mono (mono). Each alternate preset declares its own families in its manifest (registry.json → themes.available[].fonts). Load whichever stacks you plan to use via <link> or @font-face; the theme manifests declare the stacks but ship no font files.
5. Providers + color mode / theme selection:
import {
IconProvider,
MotionProvider,
SableThemeProvider,
ThemeSelector,
ThemeSwitcher,
useSableTheme,
} from "@sableui/react"
import { reiconIconSet } from "@sableui/react/icons/reicon"
function ThemeControls() {
const { preference, setPreference, themeId, themes, setThemeId, mode } = useSableTheme()
return (
<>
<ThemeSelector value={themeId} onValueChange={setThemeId} themes={themes} mode={mode} />
<ThemeSwitcher value={preference} onValueChange={setPreference} />
</>
)
}
<IconProvider set={reiconIconSet}> {/* required — Reicon, or pass another icon set */}
<MotionProvider> {/* optional — enables the motion on/off toggle */}
<SableThemeProvider> {/* optional, recommended when users can switch themes */}
<App />
</SableThemeProvider>
</MotionProvider>
</IconProvider>Icons are opt-in: mount <IconProvider set={reiconIconSet}> once at the app root, with reiconIconSet from @sableui/react/icons/reicon (its only import path). Without a provider every <Icon> renders nothing, and a development build warns once. An app that passes its own set ships none of Reicon's artwork. To add domain icons, use composeIconSets(appExtras, reiconIconSet); composeIconSets and the IconSet type are exported from the root. A set returns null for names it does not handle. List the names your extras add in .sable/icons.json ({ "names": [...] }) so sable grade counts them as icons the app supplies, not off-contract ones. fallbackIconSet remains a deprecated alias of Reicon, exported from @sableui/react/icons/reicon. SableThemeProvider is the preferred route for user-facing theme switching: it stores the user's color preference (light / dark / system), toggles the .dark class, stores the active design theme, and writes data-sable-theme for non-default themes. If your app already owns this state, use applySableColorMode() / applySableTheme() or the exported SABLE_THEME_ATTRIBUTE constant directly. You do not need a Tailwind @custom-variant dark for Sable itself — add one (@custom-variant dark (&:where(.dark, .dark *));) only if your own app code uses dark: utilities (Sable ships none).
6. Use it:
import { Button, Card, Carousel, Dialog } from "@sableui/react"The CLI
Two binaries ship with the package, both zero-dependency and both reading the contract from the version you actually installed:
npx sable pattern --list # the eight canonical page shells
npx sable pattern DashboardPage --skeleton # its composition tree — 127 lines of source, 45 of structure
npx sable block PricingCardBlock --code # a section-sized block's source, imports included
npx sable component Button --props
npx sable search "date range" # one ranked set across components, examples, patterns, layout, tokens, docs
npx sable component Calendar --example Range # just the example a search hit names
npx sable init # wire a new app: packages, CSS, fonts, IconProvider, agent skill, then doctor
npx sable doctor # is this project wired correctly? exits 1 if not
npx sable doctor --fix # apply the fixes doctor can make safely; print the rest
npx sable docs designing-a-surface # brief, direction, composition, interaction, adaptation
npx sable docs developing-taste # references, rendered directions, scoped judgment
npx sable atlas --search "editorial culture" --contrast --limit 3 --json # a project's own atlas, if it has one
npx sable explore --template # specification for three rendered alternatives
npx sable prefer --search "reading" --scope project:my-app --json
npx sable learn --search "editorial hierarchy" --json
npx sable grade src --json # code conformance; pattern similarity is descriptive
npx sable grade --template # evidence record for a rendered design review
npx sable-lint # run the design contract over ./srcregistry.json is about 1 MB and llms-full.txt about 800 KB — complete, and larger than any working context. sable serves the same generated content a piece at a time: --detail brief|compact|full picks a size, --dense drops prose, and --json wraps everything in a { type, data } envelope with a stable discriminator. Errors carry a stable code plus concrete suggestions, so a misspelled name costs one call instead of a guess. npx sable manifest --json describes the whole surface, generated from the same table that drives --help.
Design quality needs a rendered review. Start from the surface brief, reuse a
canonical composition when it fits, or build an original one from layout
primitives. Inspect desktop/mobile, the primary flow, keyboard behavior, and
reduced motion; sable docs reviewing-a-design explains the evidence format.
sable grade src --review design/review.json --strict validates conformance and
recorded review completeness. It does not independently judge the images.
sable learn reads packaged precedents and project-owned .sable/lessons/ without
a plugin or network. Each lesson states when to apply it, when to avoid it, and
where its evidence came from. Use sable learn --template, fill observed
before/after evidence, and sable learn --record design/lesson.json to preserve a
provisional local lesson. Package upgrades never overwrite project memory. See
sable docs learning-from-builds for the full loop.
Documentation
- AGENTS.md — how to build with Sable: wiring, the rules for code you write, customizing (read first).
- CONTRIBUTING.md — changing the package: hard rules, adding a component, theme/icon/motion contracts.
- DESIGN.md — the design-system spec (tokens, type, grid, motion).
- registry.json / llms.txt — machine-readable catalog + the
consumerecipe. Component entries include props, variants, tokens, a11y notes, copy-pasteable examples, and AI-readable usage guidance with do/don'ts, anatomy, keywords, and related components.
Build & publish
In the workspace the package is consumed as source (exports point at ./src) for instant HMR — no build needed for local dev. To produce the publishable artifact:
pnpm --filter @sableui/react build # regenerates registry.json from the metas → tsup → dist/ (ESM + .d.ts) → copies the token CSSRequires Node 22+ (the registry generator imports the
.meta.tsfiles via Node's built-in TypeScript type-stripping).registry.jsonis generated from each component's*.meta.ts; CI'spnpm registry:checkfails the build if it drifts.
publishConfig.exports repoints consumers at ./dist on publish (workspace dev keeps ./src). Publishing is automated — push a v* tag and the repo's Release GitHub Action publishes to npm with provenance (see the repo-root README).
Requirements
- React 19 (peer), React DOM 19 (peer), Tailwind CSS v4 in the consuming app.
Build with an agent
After installing Sable, run pnpm exec sable setup --agent in the target
project. It installs discoverable entry files in .agents/skills/sable for
Codex/Cursor and .claude/skills/sable for Claude Code; --target selects a host.
In a monorepo where Sable belongs to one workspace package, run setup in that
package with --root naming the repository root (for example
pnpm --filter web exec sable setup --agent --root ../..); the entries go
there and resolve Sable through the package.
Setup also adds a short managed block to the project's AGENTS.md and
CLAUDE.md, the instructions an agent reads on every task, so a UI request
goes through the skill even when it doesn't mention Sable. Only the text between
its <!-- sable:begin --> and <!-- sable:end --> markers is Sable's;
--no-instructions skips it.
Each entry resolves skills/sable/SKILL.md from the installed package, so package
upgrades also update the workflow. Other agents can read that file directly.
Use $sable build <idea>, inspire <references>, audit <target>, or
polish <target> in Codex; use /sable with the same modes in Claude Code or
Cursor. create is a build alias. These are agent modes, not shell commands.
To look Sable up before installing it, or if you prefer MCP, add the hosted
server: claude mcp add --transport http sable https://www.sableui.com/api/mcp
(Cursor, VS Code and Codex take the same URL). Its three tools, search, get
and docs, give the CLI's answers from the published package, at the version
the agent passes or the latest. Once Sable is installed, the CLI is the default.
Upgrade with pnpm exec sable upgrade: it updates the package, then runs the
new version's init, which refreshes the skill entries and the instructions
block and checks the wiring. From 0.17 or earlier, which has no upgrade, run
pnpm update @sableui/react --latest and then pnpm exec sable init once.
Setup only replaces managed files that have not been locally edited; --dry-run checks without writing. Keep
project preferences in project instructions. Package installation alone does not
change agent settings. See building-with-sable.md,
creating-a-preset.md, and
using-presets.md.
