@sableui/react 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/AGENTS.md +8 -4
  2. package/CHANGELOG.md +60 -0
  3. package/CONTRIBUTING.md +2 -2
  4. package/DESIGN.md +7 -7
  5. package/README.md +7 -3
  6. package/bin/sable-lint.mjs +0 -0
  7. package/bin/sable.mjs +10 -5
  8. package/dist/{_contract-o3tUc8Ig.d.ts → _contract-Cn3lUpih.d.ts} +10 -5
  9. package/dist/blocks/index.js +2 -2
  10. package/dist/{chunk-YXBOYWBN.js → chunk-C4Z3CF54.js} +1 -1
  11. package/dist/{chunk-CKNM7KSB.js → chunk-CCUDFLU3.js} +23 -6
  12. package/dist/{chunk-6LCIBMMG.js → chunk-D2ETQBHV.js} +1 -1
  13. package/dist/{chunk-MXJX3NZI.js → chunk-EFLC4NA3.js} +4 -4
  14. package/dist/{chunk-EP55KSYA.js → chunk-EHH4MHO6.js} +6 -6
  15. package/dist/{chunk-WCFLQVK5.js → chunk-FBSL2LZQ.js} +1 -1
  16. package/dist/{chunk-X276MF2W.js → chunk-OQACYT2D.js} +2 -0
  17. package/dist/index.d.ts +8 -8
  18. package/dist/index.js +13 -13
  19. package/dist/patterns/index.js +2 -2
  20. package/dist/presets/index.d.ts +1 -1
  21. package/dist/presets/index.js +2 -2
  22. package/dist/styles.css +16 -6
  23. package/dist/{theme-provider-BE-KqUG-.d.ts → theme-provider-CGeiusgu.d.ts} +3 -3
  24. package/dist/themes/atelier/index.d.ts +1 -1
  25. package/dist/themes/atelier/index.js +1 -1
  26. package/dist/themes/ember/ember.css +8 -5
  27. package/dist/themes/ember/index.d.ts +1 -1
  28. package/dist/themes/index.d.ts +3 -3
  29. package/dist/themes/index.js +3 -3
  30. package/dist/themes/meridian/index.d.ts +1 -1
  31. package/dist/themes/poppy/index.d.ts +1 -1
  32. package/dist/themes/poppy/poppy.css +1 -1
  33. package/dist/themes/prism/index.d.ts +1 -1
  34. package/dist/themes/sable/index.d.ts +9 -0
  35. package/dist/themes/sable/index.js +1 -0
  36. package/dist/themes/{base/base.css → sable/sable.css} +6 -6
  37. package/dist/themes/verdant/index.d.ts +1 -1
  38. package/dist/themes.css +3 -3
  39. package/llms/components/button.md +15 -3
  40. package/llms/components/input.md +1 -1
  41. package/llms/components/scrub-field.md +1 -1
  42. package/llms/components/select.md +1 -1
  43. package/llms/components/theme-selector.md +1 -1
  44. package/llms/reference/token-intent.md +5 -1
  45. package/llms/tasks/building-a-page.md +1 -1
  46. package/llms/tasks/creating-a-preset.md +1 -1
  47. package/llms/tasks/designing-a-surface.md +1 -1
  48. package/llms/tasks/getting-started.md +4 -0
  49. package/llms-full.txt +32 -12
  50. package/llms.txt +1 -1
  51. package/package.json +5 -5
  52. package/registry.json +2456 -18
  53. package/scripts/agent-setup.mjs +34 -7
  54. package/scripts/design-presets.mjs +1 -1
  55. package/scripts/grade.mjs +50 -11
  56. package/scripts/lint-rules.mjs +20 -1
  57. package/dist/themes/base/index.d.ts +0 -9
  58. package/dist/themes/base/index.js +0 -1
package/AGENTS.md CHANGED
@@ -4,7 +4,7 @@ How to **build with** Sable. This is the whole consumer contract an agent needs;
4
4
 
5
5
  ## What this is
6
6
 
7
- **Sable** is an owned, themeable React component system: about 100 components (`npx sable manifest` has the live count), 13 layout primitives, 8 page shells, on [Base UI](https://base-ui.com) behavior with Tailwind v4 + semantic CSS-variable tokens. The default theme is **Base**; the other featured presets are Meridian, Prism, Verdant, Ember, and Poppy.
7
+ **Sable** is an owned, themeable React component system: about 100 components (`npx sable manifest` has the live count), 13 layout primitives, 8 page shells, on [Base UI](https://base-ui.com) behavior with Tailwind v4 + semantic CSS-variable tokens. The default theme is **Sable**; the other featured presets are Meridian, Prism, Verdant, Ember, and Poppy.
8
8
 
9
9
  **Where things are**
10
10
 
@@ -59,12 +59,12 @@ The machine-readable version of this section is the `consume` block in `registry
59
59
  2. **Dedupe React** in the bundler (`resolve.dedupe: ["react","react-dom"]`) — Base UI must not resolve a second React, or you get an "invalid hook call" blank page.
60
60
  3. **CSS, in order:** `@import "tailwindcss";` → `@import "@sableui/react/styles.css";` → `@source "<node_modules>/@sableui/react/dist/**/*.{js,mjs}";` so Tailwind scans the package's classes.
61
61
 
62
- `styles.css` carries **Base only** — the default theme, and the layout floor every other theme inherits (`--page-inset`, `--region-*`, `--rhythm-unit` on a bare `:root`). The bundled **alternates are opt-in**, because they are `[data-sable-theme]`-scoped and an app that never sets the attribute ships CSS that can never match: 5.5 KB gzip of permanently-dead weight, which is exactly how a real app's build audit found this. Using one? `@import "@sableui/react/theme/prism.css";`. Offering the whole set through a `<ThemeSelector>`? `@import "@sableui/react/themes.css";`. **Symptom of forgetting:** `data-sable-theme` is set, nothing matches, and the page silently renders as Base — no error, just the wrong theme.
62
+ `styles.css` carries **Sable only** — the default theme, and the layout floor every other theme inherits (`--page-inset`, `--region-*`, `--rhythm-unit` on a bare `:root`). The bundled **alternates are opt-in**, because they are `[data-sable-theme]`-scoped and an app that never sets the attribute ships CSS that can never match: 5.5 KB gzip of permanently-dead weight, which is exactly how a real app's build audit found this. Using one? `@import "@sableui/react/theme/prism.css";`. Offering the whole set through a `<ThemeSelector>`? `@import "@sableui/react/themes.css";`. **Symptom of forgetting:** `data-sable-theme` is set, nothing matches, and the page silently renders as the Sable theme — no error, just the wrong theme.
63
63
  4. **Fonts:** load Figtree (sans) + DM Mono (mono) — the theme declares the stacks, the host provides the files. Apply `antialiased` (`-webkit-font-smoothing: antialiased` / `-moz-osx-font-smoothing: grayscale`) **once on the app root**: Sable's type scale was drawn against smoothed text, and without it macOS renders every weight heavier than designed. The package deliberately doesn't set it — a library has no business styling the host's whole page — so it has to happen here.
64
- 5. **Providers (all optional):** components render with **Reicon** by default, so `<IconProvider set={…}>` is only needed to replace it with another icon package or extend it using `composeIconSets(appExtras, reiconIconSet)`; `<MotionProvider>` is optional too. Use `<SableThemeProvider>` + `useSableTheme()` when the app should expose user-selectable themes; pair `themeId` / `setThemeId` with `<ThemeSelector>` and `preference` / `setPreference` with `<ThemeSwitcher>`. The provider stores `light` / `dark` / `system`, toggles the `dark` class on `<html>`, stores the active design theme, and writes `data-sable-theme` for non-default themes. Apps with their own state layer can call `applySableColorMode()` / `applySableTheme()` instead. You only need a Tailwind `@custom-variant dark` if **your** app code uses `dark:` utilities (Sable ships none).
64
+ 5. **Providers (all optional):** components render with **Reicon** by default, so `<IconProvider set={…}>` is only needed to replace it with another icon package or extend it using `composeIconSets(appExtras, reiconIconSet)` (list the added names in `.sable/icons.json` as `{ "names": [...] }` so `sable grade` accepts them); `<MotionProvider>` is optional too. Use `<SableThemeProvider>` + `useSableTheme()` when the app should expose user-selectable themes; pair `themeId` / `setThemeId` with `<ThemeSelector>` and `preference` / `setPreference` with `<ThemeSwitcher>`. The provider stores `light` / `dark` / `system`, toggles the `dark` class on `<html>`, stores the active design theme, and writes `data-sable-theme` for non-default themes. Apps with their own state layer can call `applySableColorMode()` / `applySableTheme()` instead. You only need a Tailwind `@custom-variant dark` if **your** app code uses `dark:` utilities (Sable ships none).
65
65
  6. Import from the root barrel: `import { Button } from "@sableui/react"`. Page shells live on the `patterns` subpath (`import { SettingsPage } from "@sableui/react/patterns"`) — preview one, then copy its source from `registry.json → patterns` when the route needs to diverge. Blocks — section-sized compositions such as a sign-in card or a pricing card — live on the `blocks` subpath (`import { SignInBlock } from "@sableui/react/blocks"`) and ship the same way (`registry.json → blocks`); copy one and edit its content constants. A block's props are only `className`, `framed` and its primary action's callback, never content.
66
66
 
67
- 7. **Check the wiring:** `npx sable doctor`. Every check it runs corresponds to a way Sable fails *silently* — a second React (blank page, "invalid hook call"), a missing `@source` (Tailwind emits none of the package's classes, everything renders unstyled), or `data-sable-theme` set with no theme CSS imported (every theme renders as Base and the switcher looks broken). Exits 1 on any failure; `--json` for the structured report.
67
+ 7. **Check the wiring:** `npx sable doctor`. Every check it runs corresponds to a way Sable fails *silently* — a second React (blank page, "invalid hook call"), a missing `@source` (Tailwind emits none of the package's classes, everything renders unstyled), or `data-sable-theme` set with no theme CSS imported (every theme renders as the Sable theme and the switcher looks broken). Exits 1 on any failure; `--json` for the structured report.
68
68
 
69
69
  8. **Read the catalog through the CLI, not the file.** `registry.json` is about 1 MB and `llms-full.txt` about 800 KB — complete and unreadable in a working context. `npx sable` serves the same generated contract in pieces, offline, at your installed version:
70
70
 
@@ -144,6 +144,10 @@ Base UI wires `aria-invalid`/`aria-describedby` when you use `Field`; the behavi
144
144
  After installing Sable, run `pnpm exec sable setup --agent` in the target
145
145
  project. It installs discoverable entry files in `.agents/skills/sable` for
146
146
  Codex/Cursor and `.claude/skills/sable` for Claude Code; `--target` selects a host.
147
+ In a monorepo where Sable belongs to one workspace package, run setup in that
148
+ package with `--root` naming the repository root (for example
149
+ `pnpm --filter web exec sable setup --agent --root ../..`); the entries go
150
+ there and resolve Sable through the package.
147
151
  Each entry resolves `skills/sable/SKILL.md` from the installed package, so package
148
152
  upgrades also update the workflow. Other agents can read that file directly.
149
153
  Use `$sable build <idea>`, `inspire <references>`, `audit <target>`, or
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # What's new
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### The default theme is now called Sable
6
+
7
+ The default theme was called Base, which read too close to Base UI, the
8
+ library Sable's components are built on. It is now **Sable**. It looks the same.
9
+
10
+ - Theme id `base` → `sable` (`data-sable-theme="sable"`, `setThemeId("sable")`).
11
+ - `@sableui/react/theme/base` and `theme/base.css` → `theme/sable` and
12
+ `theme/sable.css`. `styles.css` still includes it, so most apps import nothing new.
13
+ - `baseTheme` → `sableTheme`.
14
+ - A stored `base` choice is no longer a known id, so `SableThemeProvider`
15
+ falls back to the default, which is Sable.
16
+
17
+ ### Themes
18
+
19
+ - **Panel fields read in dark mode.** `panel-field` was the canvas tone, 3% ink
20
+ off the panel, which measured 1.04:1 on a dark panel. It is now mixed from the
21
+ panel at 9% ink (1.15:1 dark, 1.25:1 light), with hover a step further at 12%.
22
+ - **Ember's dark-mode primary button has white text.** The fill is now Ember's
23
+ light-mode orange-red. On the old, brighter fill the dark label passed WCAG
24
+ but read muddy (APCA Lc 42); white now clears both (4.8:1, Lc 76). The
25
+ sidebar's active item follows.
26
+
27
+ ### Agent tools
28
+
29
+ - `sable-lint` no longer reports `hand-derived-surface` on the value of a
30
+ `--theme-*` hook, such as `--theme-panel-field: color-mix(…)`. Tuning a
31
+ derived token is what the hooks are for.
32
+ - `sable grade --ignore <glob>` skips the same paths `sable-lint --ignore`
33
+ does, so a data layer of document colours is not counted as contract
34
+ violations. Ignored files are still fingerprinted.
35
+
36
+ ## 0.3.2
37
+
38
+ ### Workspace panels
39
+
40
+ - **`panel-field` and `panel-field-hover` tokens.** The fill of a compact
41
+ control docked in a panel, and its hover step. They default to the canvas
42
+ tone and the raised panel step, so nothing changes visually. The `xs` field
43
+ rung (Input, SelectTrigger, Combobox, ScrubField) now wears them, and a panel
44
+ control of your own can too, without a lint exception.
45
+ - **`<Button variant="field">`.** A button that sits among `xs` fields in a
46
+ panel row, such as a font picker or an add action: the inset field fill and
47
+ the field's corner instead of a pill. Use it with `size="xs"` or `"icon-xs"`.
48
+
49
+ ### Agent tools
50
+
51
+ - `sable grade` checks props against each component's real type, so props a
52
+ component forwards to Base UI (`Toggle`'s `pressed`, `Combobox`'s `filter`)
53
+ are no longer reported as hallucinated. `registry.json` carries the result as
54
+ `propSurface`.
55
+ - `sable grade` reads the icon names your app adds with `composeIconSets` from
56
+ `.sable/icons.json` (`{ "names": [...] }`), or from `--icons <file>`, and stops
57
+ counting them as off-contract.
58
+ - `sable setup --agent --root <dir>` writes the skill entries in an ancestor
59
+ directory. In a monorepo, run it in the workspace package that depends on
60
+ Sable with `--root` at the repository root. The entries resolve Sable through
61
+ that package.
62
+
3
63
  ## 0.3.1
4
64
 
5
65
  ### Charts
package/CONTRIBUTING.md CHANGED
@@ -117,7 +117,7 @@ deliberately reverses: `DESIGN.md → Registers`. What a contributor needs to kn
117
117
 
118
118
  ## Theme contract
119
119
 
120
- A theme is a CSS file that defines every token in `themes/_contract.ts` for `:root` (light) and `.dark` (dark), plus an optional TS manifest. Components depend on the **semantic** layer; each theme supplies its own palette underneath. Alternate themes activate through the root `data-sable-theme` attribute; consuming apps should usually let `<SableThemeProvider>` own that wiring and read/update it through `useSableTheme()`. To make a new theme: copy `themes/base/`, swap the palette + semantic values, keep the token names identical, add the manifest to `themes/registry.ts`, import its CSS from `src/themes.css`, add `./theme/<id>` + `./theme/<id>.css` to both `exports` and `publishConfig.exports`, and cover it in `themes.test.ts`. `build-registry` fails on a theme missing from any of those, and generates `registry.json → themes.available` from the manifests — never hand-edit that list. Re-skinning never touches component code.
120
+ A theme is a CSS file that defines every token in `themes/_contract.ts` for `:root` (light) and `.dark` (dark), plus an optional TS manifest. Components depend on the **semantic** layer; each theme supplies its own palette underneath. Alternate themes activate through the root `data-sable-theme` attribute; consuming apps should usually let `<SableThemeProvider>` own that wiring and read/update it through `useSableTheme()`. To make a new theme: copy `themes/sable/`, swap the palette + semantic values, keep the token names identical, add the manifest to `themes/registry.ts`, import its CSS from `src/themes.css`, add `./theme/<id>` + `./theme/<id>.css` to both `exports` and `publishConfig.exports`, and cover it in `themes.test.ts`. `build-registry` fails on a theme missing from any of those, and generates `registry.json → themes.available` from the manifests — never hand-edit that list. Re-skinning never touches component code.
121
121
 
122
122
  **Themes are presets, not the unit of customization.** Every token a theme sets can also be set by an app (or by the docs customizer) on `:root` after the theme loads. A theme is the starting point a consumer picks; the customizer exports the deltas on top of it. Keep that working: a theme must not set a token with `!important` or under a selector more specific than `[data-sable-theme="<id>"]`, or a consumer override cannot win.
123
123
 
@@ -130,7 +130,7 @@ while allowing a consumer to tune the whole type system, rather than making only
130
130
  headings reflect its art direction. Existing defaults remain the fallback; themes
131
131
  do not need to declare every optional hook.
132
132
 
133
- `SEMANTIC_TOKENS` includes `border-strong` alongside `border` — a visually-stronger structural divider (grid lines, table rules, section separators) distinct from `border`, which stays tuned for subtle chrome (badge/input outlines). Both need light + dark values, derived from the same neutral ramp as `border` but pulled from a higher-contrast step (e.g. Base: `border` = `--zinc-200` light / low-alpha `--zinc-700` dark → `border-strong` = `--zinc-300` light / higher-alpha `--zinc-500` dark). `styles.css`'s `@theme inline` block maps it to `--color-border-strong`, so components/consumers use it via the `border-strong` Tailwind color utility, same pattern as `border`.
133
+ `SEMANTIC_TOKENS` includes `border-strong` alongside `border` — a visually-stronger structural divider (grid lines, table rules, section separators) distinct from `border`, which stays tuned for subtle chrome (badge/input outlines). Both need light + dark values, derived from the same neutral ramp as `border` but pulled from a higher-contrast step (e.g. the Sable theme: `border` = `--zinc-200` light / low-alpha `--zinc-700` dark → `border-strong` = `--zinc-300` light / higher-alpha `--zinc-500` dark). `styles.css`'s `@theme inline` block maps it to `--color-border-strong`, so components/consumers use it via the `border-strong` Tailwind color utility, same pattern as `border`.
134
134
 
135
135
  `TYPE_OVERLINE_TOKENS` (`type-overline-size/weight/leading/tracking/transform`) back the `text-overline` utility — the eyebrow/badge-label/stat-caption text role with no home in `TYPE_SCALE_TOKENS`'s display/heading/title ramp. `type-overline-transform` is themeable rather than a hardcoded `uppercase`, so a quieter reference (e.g. Cove) can opt into sentence case. Like the other type tiers, every built-in theme declares explicit values rather than relying on styles.css's fallback defaults (`0.75rem` / weight `600` / leading `1.2` / tracking `0.08em` / `uppercase`).
136
136
 
package/DESIGN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Sable — Design System Spec
2
2
 
3
- The system-level design spec for **Sable**, an owned, themeable component system. This document is **theme-agnostic**: it describes the structure, tokens, and rules that every theme inherits. The **default theme is "Base"** (a dark-by-default, content-first, fluid product aesthetic); its concrete values live in `src/themes/base/`. Other themes supply their own values for the same token contract.
3
+ The system-level design spec for **Sable**, an owned, themeable component system. This document is **theme-agnostic**: it describes the structure, tokens, and rules that every theme inherits. The **default theme is "Sable"** (a dark-by-default, content-first, fluid product aesthetic); its concrete values live in `src/themes/sable/`. Other themes supply their own values for the same token contract.
4
4
 
5
5
  For building with Sable see `AGENTS.md`; for changing the package see `CONTRIBUTING.md`. For the machine-readable catalog see `registry.json` / `llms.txt`.
6
6
 
@@ -9,7 +9,7 @@ For building with Sable see `AGENTS.md`; for changing the package see `CONTRIBUT
9
9
  1. **Own the surface, theme the values.** Components reference semantic roles, not colors. Swapping a theme re-skins everything without touching a component.
10
10
  2. **Content over chrome.** The UI recedes so content advances. Restraint over decoration.
11
11
  3. **Constraint is generative.** A small set of tokens, tiers, and primitives yields a large space of good UIs. Reach for what exists before inventing.
12
- 4. **A deliberate visual world.** Base starts dark. A consuming product chooses its modes, typography, shape, and art direction from its brief; the token contract travels across those choices.
12
+ 4. **A deliberate visual world.** The Sable theme starts dark. A consuming product chooses its modes, typography, shape, and art direction from its brief; the token contract travels across those choices.
13
13
  5. **Accessible by construction.** Base UI carries behavior/ARIA; semantic token pairs are pre-checked to WCAG AA.
14
14
  6. **AI-native.** The system is documented for machines as well as people (`registry.json`, per-component `*.meta.ts`).
15
15
 
@@ -26,7 +26,7 @@ evidence contract. A reviewer still has to see and operate the result. Read
26
26
 
27
27
  The semantic contract protects meaning, behavior, and consistency. It does not
28
28
  choose the story, visual hierarchy, imagery, or composition for every product.
29
- Statements about Base's accent, shape, and dark-first identity describe the
29
+ Statements about the Sable theme's accent, shape, and dark-first identity describe the
30
30
  default theme; consumers can choose a different art direction while preserving
31
31
  semantic roles and accessibility. A palette change alone is not a new design.
32
32
 
@@ -57,7 +57,7 @@ Three layers, outermost is what components use:
57
57
  | **Semantic** (the contract) | `--primary`, `--card`, `--muted-foreground`, `--tone-edition`, `--ring` | **Components** (via `bg-*`/`text-*` utilities). |
58
58
  | **Component** (local) | a cva variant string | Inside one component. |
59
59
 
60
- `themes/_contract.ts` enumerates the semantic tokens every theme must define. The default (Base) palette is authored in **OKLCH** for perceptually-uniform ramps, locked hue, gamut-safe chroma, and dark-mode vibrancy.
60
+ `themes/_contract.ts` enumerates the semantic tokens every theme must define. The default (Sable) palette is authored in **OKLCH** for perceptually-uniform ramps, locked hue, gamut-safe chroma, and dark-mode vibrancy.
61
61
 
62
62
  ### Semantic tokens (the contract)
63
63
 
@@ -68,7 +68,7 @@ Tone ink (legible text/icon on a tone fill, theme-adaptive): `tone-standard-fore
68
68
  Overlay (fixed dark surface over content — scrims, media letterbox, pills on imagery): `overlay`, `overlay-foreground`.
69
69
  Sidebar set: `sidebar`, `sidebar-foreground`, `sidebar-primary(-foreground)`, `sidebar-accent(-foreground)`, `sidebar-border`, `sidebar-ring`.
70
70
 
71
- **Base inversion rule:** in dark mode `primary` is light (a near-white pill); in light mode it is near-black. Other themes may use a different action palette. Never call `primary` "black" or `primary-foreground` "white" — use the semantic pair, whose values belong to the theme.
71
+ **Sable theme inversion rule:** in dark mode `primary` is light (a near-white pill); in light mode it is near-black. Other themes may use a different action palette. Never call `primary` "black" or `primary-foreground` "white" — use the semantic pair, whose values belong to the theme.
72
72
 
73
73
  ### Tone system
74
74
 
@@ -442,7 +442,7 @@ Motion is a first-class token layer, not an afterthought. **Subtle by default, w
442
442
 
443
443
  **Three parts:**
444
444
 
445
- 1. **Tokens** (`themes/base/base.css` → Motion tokens; contract in `themes/_contract.ts` → `MOTION_TOKENS`).
445
+ 1. **Tokens** (`themes/sable/sable.css` → Motion tokens; contract in `themes/_contract.ts` → `MOTION_TOKENS`).
446
446
  - **Durations:** `instant` 75 · `fast` 150 · `base` 200 · `slow` 300 · `slower` 500, plus semantic `enter`/`exit`. Every duration is multiplied by `--motion-scale`.
447
447
  - **Easings:** `standard` (≈ease-in-out, default) · `decelerate` (≈ease-out, entrances) · `emphasized` (large surfaces) · `linear` (indeterminate loaders) · **`spring`** (gentle ~+4% single overshoot — the default for delightful pops) · **`bounce`** (~+10%, opt-in playful accents). Springs apply on **enter only**; exits stay overshoot-free.
448
448
  - Consume easings via the `ease-*` utilities; durations via `duration-(--motion-duration-*)` (so the kill-switch always applies).
@@ -526,7 +526,7 @@ Not every removal is a regression; check for an **equivalent replacement** befor
526
526
  - Inline `oklch()`/hex or raw palette steps in components. *(CI-enforced: `inline-color`, `palette-ref`.)*
527
527
  - Decomposing paired typography utilities. *(CI-enforced: `decomposed-type`.)*
528
528
  - Pure `#000`/`#fff` (use `zinc-950`/`zinc-50`, or `overlay`/`overlay-foreground` for scrims). *(CI-enforced: `raw-black-white`.)*
529
- - In Base, purple/brand for primary CTAs (brand = editions + selection only). A different theme chooses its action palette through the semantic pair.
529
+ - In the Sable theme, purple/brand for primary CTAs (brand = editions + selection only). A different theme chooses its action palette through the semantic pair.
530
530
  - Gradient text, side-stripe borders, nested cards, mixed radii.
531
531
  - Concrete icon-package imports scattered through components. Reicon supplies the default through the shared adapter; IconProvider allows replacement.
532
532
  - A global page max-width (use opt-in region caps).
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Sable
2
2
 
3
- **Sable** — a themeable React component system with layout patterns, semantic tokens and tools for coding agents. Default theme: **Base**. On npm as `@sableui/react`; docs and the live catalog at [sableui.com](https://www.sableui.com).
3
+ **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](https://www.sableui.com).
4
4
 
5
5
  For a clickable iOS prototype (demos and presentations, not production), ask the
6
6
  Sable skill to build with the iOS target. Every screen is Sable components in your
@@ -13,7 +13,7 @@ Frame it with `PhoneFrame` from `@sableui/react/ios/preview`. See
13
13
 
14
14
  Original Sable code is [MIT licensed](./LICENSE).
15
15
 
16
- The featured presets are Base (neutral), Meridian (square), Prism (compact technical),
16
+ The featured presets are Sable (neutral), Meridian (square), Prism (compact technical),
17
17
  Verdant (serif), Ember (warm), and Poppy (playful). Every preset supports light and
18
18
  dark modes. Load its declared fonts and use `ThemeSelector` to offer preset choices.
19
19
  [Third-party notices](./THIRD_PARTY_NOTICES.md) list the software and fonts used
@@ -107,7 +107,7 @@ function ThemeControls() {
107
107
  </IconProvider>
108
108
  ```
109
109
 
110
- Components render Reicon icons out of the box. To replace Reicon, mount `<IconProvider set={yourIconSet}>`; to add domain icons, use `composeIconSets(appExtras, reiconIconSet)`. Both helpers and the `IconSet` type are exported from Sable. A set returns `null` for names it does not handle. `fallbackIconSet` remains a deprecated alias of Reicon for compatibility. `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).
110
+ Components render Reicon icons out of the box. To replace Reicon, mount `<IconProvider set={yourIconSet}>`; to add domain icons, use `composeIconSets(appExtras, reiconIconSet)`. Both helpers and the `IconSet` type are exported from Sable. 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 for compatibility. `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).
111
111
 
112
112
  **6. Use it:**
113
113
 
@@ -181,6 +181,10 @@ pnpm --filter @sableui/react build # regenerates registry.json from the metas
181
181
  After installing Sable, run `pnpm exec sable setup --agent` in the target
182
182
  project. It installs discoverable entry files in `.agents/skills/sable` for
183
183
  Codex/Cursor and `.claude/skills/sable` for Claude Code; `--target` selects a host.
184
+ In a monorepo where Sable belongs to one workspace package, run setup in that
185
+ package with `--root` naming the repository root (for example
186
+ `pnpm --filter web exec sable setup --agent --root ../..`); the entries go
187
+ there and resolve Sable through the package.
184
188
  Each entry resolves `skills/sable/SKILL.md` from the installed package, so package
185
189
  upgrades also update the workflow. Other agents can read that file directly.
186
190
  Use `$sable build <idea>`, `inspire <references>`, `audit <target>`, or
Binary file
package/bin/sable.mjs CHANGED
@@ -83,7 +83,7 @@ const COMMANDS = [
83
83
  summary: "Create, validate, or compile a portable Sable preset used by Canvas and builds.",
84
84
  arg: null,
85
85
  flags: [
86
- { name: "--template", value: null, describe: "Print a complete editable Base preset JSON record." },
86
+ { name: "--template", value: null, describe: "Print a complete editable Sable preset JSON record." },
87
87
  { name: "--check", value: "file.json", describe: "Validate the schema and Sable token contract." },
88
88
  { name: "--css", value: "file.json", describe: "Generate scoped theme CSS from a validated preset." },
89
89
  ],
@@ -96,6 +96,7 @@ const COMMANDS = [
96
96
  flags: [
97
97
  { name: "--agent", value: null, describe: "Install skill entry files. Required." },
98
98
  { name: "--target", value: "all|codex|claude|cursor", describe: "Host discovery directories. Default all; Codex and Cursor share .agents." },
99
+ { name: "--root", value: "dir", describe: "Write the entries in this ancestor instead, e.g. the repository root when Sable belongs to one workspace package. Run setup in that package." },
99
100
  { name: "--update", value: null, describe: "Refresh unchanged managed entries after a package upgrade." },
100
101
  { name: "--dry-run", value: null, describe: "Check and report the destination without writing files." },
101
102
  ],
@@ -208,6 +209,8 @@ const COMMANDS = [
208
209
  { name: "--review", value: "review.json", describe: "Check captures, observations, iterations, and the source fingerprint." },
209
210
  { name: "--strict", value: null, describe: "Exit 1 for contract findings or missing/incomplete/unresolved review evidence." },
210
211
  { name: "--cli-log", value: "file.jsonl", describe: "Include retrieval usage from a SABLE_CLI_LOG file." },
212
+ { name: "--icons", value: "icons.json", describe: 'Icon names the app adds with composeIconSets, as { "names": [...] }. Default: .sable/icons.json when present.' },
213
+ { name: "--ignore", value: "glob", describe: "Skip matching paths in the checks, with the same globs as sable-lint --ignore. Repeatable." },
211
214
  ],
212
215
  returns: ["grade.score", "grade.template"],
213
216
  },
@@ -1076,7 +1079,7 @@ function cmdDoctor() {
1076
1079
  /* The theme trap: opting into theme switching without importing theme CSS. */
1077
1080
  const src = collectSources(/\.(tsx?|jsx?)$/)
1078
1081
  const srcText = src.map((f) => f.text).join("\n")
1079
- // A provider used only for light/dark mode needs Base, not alternate themes.
1082
+ // A provider used only for light/dark mode needs only the Sable theme, not alternate themes.
1080
1083
  const usesThemes = /ThemeSelector|data-sable-theme|applySableTheme|setThemeId|defaultThemeId/.test(srcText)
1081
1084
  if (usesThemes) {
1082
1085
  const themeCss = /@import\s+["'](?:@sableui\/react\/|[^"']*packages\/sable\/(?:src|dist)\/)(themes\.css|theme\/[a-z-]+\.css|themes\/[a-z-]+\/[a-z-]+\.css)["']/.test(cssText)
@@ -1086,7 +1089,7 @@ function cmdDoctor() {
1086
1089
  add(
1087
1090
  "fail",
1088
1091
  "themes",
1089
- "This app selects a design theme but has no alternate or app-owned scoped theme CSS — the selected theme may silently render as Base.",
1092
+ "This app selects a design theme but has no alternate or app-owned scoped theme CSS — the selected theme may silently render as the Sable theme.",
1090
1093
  'Import themes.css, one theme/<id>.css, or the scoped CSS generated by sable preset --css.',
1091
1094
  )
1092
1095
  }
@@ -1215,9 +1218,11 @@ const HANDLERS = {
1215
1218
  preset: () => cmdDesignTool("design-presets.mjs"),
1216
1219
  setup: () => {
1217
1220
  if (!flags.has("agent") || rest.length) fail("ERR_BAD_ARGUMENT", "Use sable setup --agent from your project root.", ["sable setup --agent --dry-run"])
1221
+ const root = flags.get("root")
1222
+ if (root === true) fail("ERR_BAD_ARGUMENT", "--root needs a directory.", ["pnpm --filter <package> exec sable setup --agent --root ../.."])
1218
1223
  try {
1219
- const data = installAgentSkill({ root: CWD, packageRoot: PKG_ROOT, target: flags.get("target") ?? "all", update: flags.has("update"), dryRun: flags.has("dry-run") })
1220
- ok("setup.agent", data, `${data.dryRun ? "Would install" : "Installed"} Sable skill entries at ${data.destinations.join(", ")}.\nNext: ${data.next}`)
1224
+ const data = installAgentSkill({ root: root ? resolve(CWD, root) : CWD, project: CWD, packageRoot: PKG_ROOT, target: flags.get("target") ?? "all", update: flags.has("update"), dryRun: flags.has("dry-run") })
1225
+ ok("setup.agent", data, `${data.dryRun ? "Would install" : "Installed"} Sable skill entries at ${data.destinations.map((d) => relative(CWD, join(data.root, d)).replaceAll("\\", "/")).join(", ")}.\nNext: ${data.next}`)
1221
1226
  } catch (error) { fail("ERR_AGENT_SETUP", error.message, ["sable setup --agent --dry-run"]) }
1222
1227
  },
1223
1228
  manifest: cmdManifest,
@@ -4,14 +4,14 @@
4
4
  * never a theme's internal palette steps — which is what makes the system
5
5
  * re-themeable without touching component code.
6
6
  *
7
- * To author a new theme: copy `themes/base/`, keep these token names, and
7
+ * To author a new theme: copy `themes/sable/`, keep these token names, and
8
8
  * supply your own palette + values. See CONTRIBUTING.md → "Theme contract".
9
9
  */
10
10
  declare const SEMANTIC_TOKENS: readonly ["background", "foreground", "card", "card-foreground", "popover", "popover-foreground", "muted", "muted-foreground", "border", "border-strong", "input", "control-border", "ring", "primary", "primary-foreground", "secondary", "secondary-foreground", "accent", "accent-foreground", "destructive", "destructive-foreground", "success", "success-foreground", "tone-standard", "tone-collectible", "tone-edition", "tone-info", "tone-warning", "highlight", "tone-standard-foreground", "tone-collectible-foreground", "tone-edition-foreground", "tone-info-foreground", "tone-warning-foreground", "highlight-foreground", "overlay", "overlay-foreground", "sidebar", "sidebar-foreground", "sidebar-primary", "sidebar-primary-foreground", "sidebar-accent", "sidebar-accent-foreground", "sidebar-border", "sidebar-ring"];
11
11
  type SemanticToken = (typeof SEMANTIC_TOKENS)[number];
12
12
  /**
13
13
  * Motion tokens (mode-independent). A theme MAY override these to retune its
14
- * motion feel; if omitted, the Base defaults apply. Components reference
14
+ * motion feel; if omitted, the Sable theme's defaults apply. Components reference
15
15
  * them via `duration-(--motion-duration-*)` + the `ease-*` utilities, or — far
16
16
  * more often — via the named motion recipes (`motion-pop`, `motion-press`, …)
17
17
  * defined in styles.css. Every duration is multiplied by `--motion-scale`, the
@@ -23,7 +23,7 @@ type MotionToken = (typeof MOTION_TOKENS)[number];
23
23
  /**
24
24
  * Radius tokens (mode-independent). A theme MAY override these to retune its
25
25
  * shape language (soft-square, rectilinear, full-pill, …); if omitted, the
26
- * Base defaults apply (see styles.css → `@theme inline`, which bridges
26
+ * Sable defaults apply (see styles.css → `@theme inline`, which bridges
27
27
  * each `--theme-radius-*` var to the `radius-*` Tailwind scale). Components
28
28
  * never reference these directly — they use the derived `rounded-*`
29
29
  * utilities.
@@ -33,7 +33,7 @@ type RadiusToken = (typeof RADIUS_TOKENS)[number];
33
33
  /**
34
34
  * Type-scale tokens (mode-independent). A theme MAY override these to retune
35
35
  * its typographic voice (weight, leading, tracking, and — for heading/display
36
- * tiers — size); if omitted, the Base defaults baked into styles.css's
36
+ * tiers — size); if omitted, the Sable theme's defaults baked into styles.css's
37
37
  * paired `text-*` utilities apply. Additional body/label/caption/mono and size
38
38
  * hooks are advertised separately in TYPE_REFINEMENT_TOKENS: they are optional,
39
39
  * so a theme can retune its reading voice without duplicating every default.
@@ -95,11 +95,16 @@ type DensityToken = (typeof DENSITY_TOKENS)[number];
95
95
  * COMPRESSED tonal band — Paper's whole ladder spans 249→242→238, about 3-7
96
96
  * RGB units, with a low-contrast hairline doing the structural work. `card` is
97
97
  * tuned for the opposite job (lifting content off a page).
98
+ * - `panel-field` / `panel-field-hover` are the inset fill of a compact control
99
+ * docked in a panel (the `size="xs"` field rung, a field-style button) and its
100
+ * hover step, mixed from the panel they sit in. Named so a consumer's own panel
101
+ * control can wear them without borrowing the work surface's token or a fixed
102
+ * hover the surface-hover rule rightly rejects.
98
103
  * - `hairline` / `hairline-strong` are that structural edge. In this register
99
104
  * hairlines carry structure INSTEAD of tonal steps — a deliberate reversal of
100
105
  * the page register's "prefer a tonal step or space over a divider line".
101
106
  */
102
- declare const WORKSPACE_SURFACE_TOKENS: readonly ["canvas", "canvas-foreground", "panel", "panel-foreground", "panel-raised", "hairline", "hairline-strong"];
107
+ declare const WORKSPACE_SURFACE_TOKENS: readonly ["canvas", "canvas-foreground", "panel", "panel-foreground", "panel-raised", "panel-field", "panel-field-hover", "hairline", "hairline-strong"];
103
108
  type WorkspaceSurfaceToken = (typeof WORKSPACE_SURFACE_TOKENS)[number];
104
109
  /**
105
110
  * UI type tiers (mode-independent) — the chrome typography register.
@@ -1,5 +1,5 @@
1
- import { Kbd, CommandMenuPanel, ToggleGroup, Toggle, BarChart, DateTile, Accordion, AccordionItem, AccordionTrigger, AccordionPanel, RadialChart, Sparkline, Slider, Stepper, StepperItem, SegmentedBar, Checkbox, PieChart, AreaChart, OtpField } from '../chunk-WCFLQVK5.js';
2
- import { Card, CardHeader, CardTitle, CardDescription, CardAction, Button, CardList, Entity, OverflowMenu, MenuItem, MenuSeparator, Code, CardContent, Message, Bubble, CardFooter, DestructiveActionModal, Badge, AvatarGroup, Avatar, Textarea, CodeBlock, Input, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, StatusDot, Switch, Form, Field, Select, SelectTrigger, SelectContent, SelectItem, Separator, Label, Attachment } from '../chunk-CKNM7KSB.js';
1
+ import { Kbd, CommandMenuPanel, ToggleGroup, Toggle, BarChart, DateTile, Accordion, AccordionItem, AccordionTrigger, AccordionPanel, RadialChart, Sparkline, Slider, Stepper, StepperItem, SegmentedBar, Checkbox, PieChart, AreaChart, OtpField } from '../chunk-FBSL2LZQ.js';
2
+ import { Card, CardHeader, CardTitle, CardDescription, CardAction, Button, CardList, Entity, OverflowMenu, MenuItem, MenuSeparator, Code, CardContent, Message, Bubble, CardFooter, DestructiveActionModal, Badge, AvatarGroup, Avatar, Textarea, CodeBlock, Input, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, StatusDot, Switch, Form, Field, Select, SelectTrigger, SelectContent, SelectItem, Separator, Label, Attachment } from '../chunk-CCUDFLU3.js';
3
3
  import { Region, Stack, Row, Grid } from '../chunk-3IWZYBGH.js';
4
4
  import { cn } from '../chunk-SRQWQUTW.js';
5
5
  import { Icon } from '../chunk-NGBDP3BM.js';
@@ -2,7 +2,7 @@
2
2
  var atelierTheme = {
3
3
  id: "atelier",
4
4
  label: "Atelier",
5
- deprecated: { replacement: "base" },
5
+ deprecated: { replacement: "sable" },
6
6
  modes: ["light", "dark"],
7
7
  defaultMode: "light",
8
8
  fonts: {
@@ -339,6 +339,15 @@ var buttonVariants = cva(
339
339
  * restyling secondary/ghost three different ways was the alternative.
340
340
  */
341
341
  floating: "border border-border-floating bg-popover/90 text-popover-foreground shadow-md backdrop-blur hover:bg-popover",
342
+ /*
343
+ * A button docked in a panel among compact fields — a picker that
344
+ * opens a popover, "Add fill", a swatch's trigger. It wears the xs
345
+ * field rung's shell (lib/field-shell → FIELD_SHELL_XS): the inset
346
+ * `panel-field` fill, no border, the field's corner rather than the
347
+ * button's pill, so it reads as one of the row's controls. Secondary
348
+ * or ghost there read as page buttons dropped into tool chrome.
349
+ */
350
+ field: "rounded-[var(--panel-field-radius,var(--radius-xs))] bg-panel-field text-panel-foreground hover:bg-panel-field-hover",
342
351
  /*
343
352
  * On-colour tiers — for a button sitting ON a coloured fill (a tone
344
353
  * band, a solid Banner, a brand panel, a dark section in a light
@@ -587,9 +596,9 @@ var buttonMeta = {
587
596
  props: [
588
597
  {
589
598
  name: "variant",
590
- type: '"primary" | "secondary" | "ghost" | "outline" | "destructive" | "link" | "floating" | "on-color" | "on-color-outline" | "on-color-ghost"',
599
+ type: '"primary" | "secondary" | "ghost" | "outline" | "destructive" | "link" | "floating" | "field" | "on-color" | "on-color-outline" | "on-color-ghost"',
591
600
  default: '"primary"',
592
- description: "Visual emphasis tier. One primary per screen. The on-color tiers are for buttons ON a coloured fill: they paint with the inherited text colour (the fill's ink) \u2014 on-color is solid (ink background, fill-coloured label via the optional --on-color-fill var, else black/white), on-color-outline and on-color-ghost are transparent with an ink label. Never use them on page or card surfaces. floating is for a control that sits OVER content (carousel arrows, a marquee pause, a jump-to-latest over a conversation): its own edge, lift and translucent popover fill, so it reads on arbitrary content beneath."
601
+ description: "Visual emphasis tier. One primary per screen. The on-color tiers are for buttons ON a coloured fill: they paint with the inherited text colour (the fill's ink) \u2014 on-color is solid (ink background, fill-coloured label via the optional --on-color-fill var, else black/white), on-color-outline and on-color-ghost are transparent with an ink label. Never use them on page or card surfaces. floating is for a control that sits OVER content (carousel arrows, a marquee pause, a jump-to-latest over a conversation): its own edge, lift and translucent popover fill, so it reads on arbitrary content beneath. field is for a button docked in a workspace panel among xs fields: the inset panel-field fill and the field's corner, so a picker trigger or an add action reads as one of the row's controls; pair it with size xs or icon-xs."
593
602
  },
594
603
  {
595
604
  name: "size",
@@ -638,6 +647,7 @@ var buttonMeta = {
638
647
  "destructive",
639
648
  "link",
640
649
  "floating",
650
+ "field",
641
651
  "on-color",
642
652
  "on-color-outline",
643
653
  "on-color-ghost"
@@ -654,7 +664,10 @@ var buttonMeta = {
654
664
  "input",
655
665
  "destructive",
656
666
  "destructive-foreground",
657
- "ring"
667
+ "ring",
668
+ "panel-field",
669
+ "panel-field-hover",
670
+ "panel-foreground"
658
671
  ],
659
672
  motion: [
660
673
  {
@@ -746,6 +759,10 @@ var buttonMeta = {
746
759
  label: "Icon button with an unread badge",
747
760
  code: '<Button size="icon" variant="ghost" aria-label="Notifications" badge={<NotificationBadge dot />}><Icon name="bell" /></Button>'
748
761
  },
762
+ {
763
+ label: "Field button in a panel row",
764
+ code: '<PanelSection title="Text">\n <PropertyRow label="Font">\n <Button variant="field" size="xs" className="w-full justify-between" iconEnd={<Icon name="chevron-down" />}>Figtree</Button>\n </PropertyRow>\n</PanelSection>'
765
+ },
749
766
  {
750
767
  label: "On a coloured band",
751
768
  code: '<div className="flex gap-3 bg-tone-info p-6 text-tone-info-foreground [--on-color-fill:var(--tone-info)]"><Button variant="on-color">Join the beta</Button><Button variant="on-color-outline">Read the notes</Button></div>'
@@ -2183,7 +2200,7 @@ var separatorMeta = {
2183
2200
  var FIELD_SHELL = "rounded-sm border border-input bg-card motion-interactive placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50";
2184
2201
  var MOBILE_FIELD_TIER = "max-sm:text-body-lg";
2185
2202
  var FIELD_INVALID = "aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive/30";
2186
- var FIELD_SHELL_XS = "rounded-[var(--panel-field-radius,var(--radius-xs))] border-transparent bg-canvas hover:bg-panel-raised";
2203
+ var FIELD_SHELL_XS = "rounded-[var(--panel-field-radius,var(--radius-xs))] border-transparent bg-panel-field hover:bg-panel-field-hover";
2187
2204
  function Input({ className, type, size = "md", ref, ...props }) {
2188
2205
  const tier = useTypeTier();
2189
2206
  const density = useDensity();
@@ -2289,7 +2306,7 @@ var inputMeta = {
2289
2306
  name: "size",
2290
2307
  type: '"md" | "xs"',
2291
2308
  default: '"md"',
2292
- description: "md is the form field. xs is the dense inset field of a tool panel (an inset canvas fill, no outline, tight padding) \u2014 the same ladder as Select and Combobox. Use it instead of overriding the height with className."
2309
+ description: "md is the form field. xs is the dense inset field of a tool panel (an inset panel-field fill, no outline, tight padding) \u2014 the same ladder as Select and Combobox. Use it instead of overriding the height with className."
2293
2310
  },
2294
2311
  {
2295
2312
  name: "type",
@@ -3969,7 +3986,7 @@ var selectMeta = {
3969
3986
  {
3970
3987
  name: "SelectTrigger",
3971
3988
  type: 'Base UI Trigger props + placeholder?: ReactNode + size?: "md" | "xs" + className',
3972
- description: 'The button. Composes Trigger + Value + a chevron Icon that flips on open (data-[popup-open]). `size="xs"` is the property-grid rung \u2014 height, padding, radius AND surface move together, which is why it is a prop and not a className: the xs trigger is an inset `bg-canvas` field with no border, matching ScrubField on the same row. A consumer overriding only the height gets an md-register 8px radius on a 24px box and a raised card sitting among inset fields; one inspector panel had eleven of those, and both tells are visible at a glance.'
3989
+ description: 'The button. Composes Trigger + Value + a chevron Icon that flips on open (data-[popup-open]). `size="xs"` is the property-grid rung \u2014 height, padding, radius AND surface move together, which is why it is a prop and not a className: the xs trigger is an inset `bg-panel-field` field with no border, matching ScrubField on the same row. A consumer overriding only the height gets an md-register 8px radius on a 24px box and a raised card sitting among inset fields; one inspector panel had eleven of those, and both tells are visible at a glance.'
3973
3990
  },
3974
3991
  {
3975
3992
  name: "SelectContent",
@@ -1,4 +1,4 @@
1
- import { useMotion, useIsomorphicLayoutEffect, mergeRefs, trimLabel, useTypeTier, buttonVariants, Input, Table, TableCaption, TableHeader, TableRow, TableHead, Button, TableBody, TableCell, Card, SimpleTooltip, useDensity, FIELD_SHELL, FIELD_INVALID, MOBILE_FIELD_TIER, Tooltip, TooltipTrigger, TooltipContent, useScrollActivity, setRef, Textarea, Menu, MenuTrigger, MenuContent, MenuSubmenu, MenuSubmenuTrigger, Separator } from './chunk-CKNM7KSB.js';
1
+ import { useMotion, useIsomorphicLayoutEffect, mergeRefs, trimLabel, useTypeTier, buttonVariants, Input, Table, TableCaption, TableHeader, TableRow, TableHead, Button, TableBody, TableCell, Card, SimpleTooltip, useDensity, FIELD_SHELL, FIELD_INVALID, MOBILE_FIELD_TIER, Tooltip, TooltipTrigger, TooltipContent, useScrollActivity, setRef, Textarea, Menu, MenuTrigger, MenuContent, MenuSubmenu, MenuSubmenuTrigger, Separator } from './chunk-CCUDFLU3.js';
2
2
  import { cn } from './chunk-SRQWQUTW.js';
3
3
  import { Icon } from './chunk-NGBDP3BM.js';
4
4
  import { createContext, useRef, useState, useEffect, isValidElement, cloneElement, useContext, useMemo, useCallback, useImperativeHandle } from 'react';
@@ -1,19 +1,19 @@
1
1
  import { prismTheme } from './chunk-7MG4SHYX.js';
2
2
  import { verdantTheme } from './chunk-KO3NAEBF.js';
3
3
  import { emberTheme } from './chunk-L4IEIQQA.js';
4
- import { baseTheme } from './chunk-EP55KSYA.js';
4
+ import { sableTheme } from './chunk-EHH4MHO6.js';
5
5
  import { poppyTheme } from './chunk-ADLLDWLW.js';
6
6
  import { meridianTheme } from './chunk-K7RUHUGC.js';
7
- import { atelierTheme } from './chunk-YXBOYWBN.js';
7
+ import { atelierTheme } from './chunk-C4Z3CF54.js';
8
8
  import { createContext, useMemo, useState, useEffect, useCallback, useContext } from 'react';
9
9
  import { jsx } from 'react/jsx-runtime';
10
10
 
11
11
  // src/themes/registry.ts
12
12
  var SABLE_THEME_ATTRIBUTE = "data-sable-theme";
13
- var DEFAULT_THEME_ID = baseTheme.id;
13
+ var DEFAULT_THEME_ID = sableTheme.id;
14
14
  var defaultThemeId = DEFAULT_THEME_ID;
15
15
  var themeManifests = [
16
- baseTheme,
16
+ sableTheme,
17
17
  meridianTheme,
18
18
  prismTheme,
19
19
  verdantTheme,
@@ -1,7 +1,7 @@
1
- // src/themes/base/index.ts
2
- var baseTheme = {
3
- id: "base",
4
- label: "Base",
1
+ // src/themes/sable/index.ts
2
+ var sableTheme = {
3
+ id: "sable",
4
+ label: "Sable",
5
5
  modes: ["dark", "light"],
6
6
  defaultMode: "dark",
7
7
  fonts: {
@@ -9,6 +9,6 @@ var baseTheme = {
9
9
  mono: '"DM Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Courier New", monospace'
10
10
  }
11
11
  };
12
- var base_default = baseTheme;
12
+ var sable_default = sableTheme;
13
13
 
14
- export { baseTheme, base_default };
14
+ export { sableTheme, sable_default };
@@ -1,4 +1,4 @@
1
- import { FIELD_SHELL, FIELD_INVALID, useMotion, useTypeTier, useScrollActivity, setRef, useAnchoredDensity, Dialog, DialogContent, DialogTitle, DialogDescription, useIsomorphicLayoutEffect } from './chunk-CKNM7KSB.js';
1
+ import { FIELD_SHELL, FIELD_INVALID, useMotion, useTypeTier, useScrollActivity, setRef, useAnchoredDensity, Dialog, DialogContent, DialogTitle, DialogDescription, useIsomorphicLayoutEffect } from './chunk-CCUDFLU3.js';
2
2
  import { cn } from './chunk-SRQWQUTW.js';
3
3
  import { Icon } from './chunk-NGBDP3BM.js';
4
4
  import * as OtpParts from '@base-ui/react/otp-field';
@@ -273,6 +273,8 @@ var WORKSPACE_SURFACE_TOKENS = [
273
273
  "panel",
274
274
  "panel-foreground",
275
275
  "panel-raised",
276
+ "panel-field",
277
+ "panel-field-hover",
276
278
  "hairline",
277
279
  "hairline-strong"
278
280
  ];