@sableui/react 0.3.2 → 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 (46) hide show
  1. package/AGENTS.md +3 -3
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +2 -2
  4. package/DESIGN.md +7 -7
  5. package/README.md +2 -2
  6. package/bin/sable-lint.mjs +0 -0
  7. package/bin/sable.mjs +4 -3
  8. package/dist/{_contract-BlKMR4eX.d.ts → _contract-Cn3lUpih.d.ts} +7 -7
  9. package/dist/{chunk-YXBOYWBN.js → chunk-C4Z3CF54.js} +1 -1
  10. package/dist/{chunk-MXJX3NZI.js → chunk-EFLC4NA3.js} +4 -4
  11. package/dist/{chunk-EP55KSYA.js → chunk-EHH4MHO6.js} +6 -6
  12. package/dist/index.d.ts +6 -6
  13. package/dist/index.js +6 -6
  14. package/dist/presets/index.d.ts +1 -1
  15. package/dist/presets/index.js +1 -1
  16. package/dist/styles.css +12 -11
  17. package/dist/{theme-provider-CuYYxF0o.d.ts → theme-provider-CGeiusgu.d.ts} +3 -3
  18. package/dist/themes/atelier/index.d.ts +1 -1
  19. package/dist/themes/atelier/index.js +1 -1
  20. package/dist/themes/ember/ember.css +8 -5
  21. package/dist/themes/ember/index.d.ts +1 -1
  22. package/dist/themes/index.d.ts +3 -3
  23. package/dist/themes/index.js +3 -3
  24. package/dist/themes/meridian/index.d.ts +1 -1
  25. package/dist/themes/poppy/index.d.ts +1 -1
  26. package/dist/themes/poppy/poppy.css +1 -1
  27. package/dist/themes/prism/index.d.ts +1 -1
  28. package/dist/themes/sable/index.d.ts +9 -0
  29. package/dist/themes/sable/index.js +1 -0
  30. package/dist/themes/{base/base.css → sable/sable.css} +6 -6
  31. package/dist/themes/verdant/index.d.ts +1 -1
  32. package/dist/themes.css +3 -3
  33. package/llms/components/theme-selector.md +1 -1
  34. package/llms/reference/token-intent.md +1 -1
  35. package/llms/tasks/building-a-page.md +1 -1
  36. package/llms/tasks/creating-a-preset.md +1 -1
  37. package/llms/tasks/designing-a-surface.md +1 -1
  38. package/llms-full.txt +6 -6
  39. package/llms.txt +1 -1
  40. package/package.json +5 -5
  41. package/registry.json +12 -12
  42. package/scripts/design-presets.mjs +1 -1
  43. package/scripts/grade.mjs +18 -5
  44. package/scripts/lint-rules.mjs +20 -1
  45. package/dist/themes/base/index.d.ts +0 -9
  46. 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
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
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
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
+
3
36
  ## 0.3.2
4
37
 
5
38
  ### Workspace panels
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
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
  ],
@@ -210,6 +210,7 @@ const COMMANDS = [
210
210
  { name: "--strict", value: null, describe: "Exit 1 for contract findings or missing/incomplete/unresolved review evidence." },
211
211
  { name: "--cli-log", value: "file.jsonl", describe: "Include retrieval usage from a SABLE_CLI_LOG file." },
212
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." },
213
214
  ],
214
215
  returns: ["grade.score", "grade.template"],
215
216
  },
@@ -1078,7 +1079,7 @@ function cmdDoctor() {
1078
1079
  /* The theme trap: opting into theme switching without importing theme CSS. */
1079
1080
  const src = collectSources(/\.(tsx?|jsx?)$/)
1080
1081
  const srcText = src.map((f) => f.text).join("\n")
1081
- // 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.
1082
1083
  const usesThemes = /ThemeSelector|data-sable-theme|applySableTheme|setThemeId|defaultThemeId/.test(srcText)
1083
1084
  if (usesThemes) {
1084
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)
@@ -1088,7 +1089,7 @@ function cmdDoctor() {
1088
1089
  add(
1089
1090
  "fail",
1090
1091
  "themes",
1091
- "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.",
1092
1093
  'Import themes.css, one theme/<id>.css, or the scoped CSS generated by sable preset --css.',
1093
1094
  )
1094
1095
  }
@@ -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.
@@ -97,9 +97,9 @@ type DensityToken = (typeof DENSITY_TOKENS)[number];
97
97
  * tuned for the opposite job (lifting content off a page).
98
98
  * - `panel-field` / `panel-field-hover` are the inset fill of a compact control
99
99
  * docked in a panel (the `size="xs"` field rung, a field-style button) and its
100
- * hover step. They default to `canvas` / `panel-raised`, but name the role so
101
- * a consumer's own panel control can wear it without borrowing the work
102
- * surface's token or a fixed hover the surface-hover rule rightly rejects.
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.
103
103
  * - `hairline` / `hairline-strong` are that structural edge. In this register
104
104
  * hairlines carry structure INSTEAD of tonal steps — a deliberate reversal of
105
105
  * the page register's "prefer a tonal step or space over a divider line".
@@ -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: {
@@ -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 };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { ClassValue } from 'clsx';
2
2
  import { C as ComponentMeta } from './meta-CcE_SeFu.js';
3
3
  export { A as AnatomyMeta, a as BestPracticeMeta, E as ExampleMeta, M as MotionMeta, b as MotionSlot, c as PropMeta, R as RelatedMeta, S as SubcomponentMeta, U as UsageMeta } from './meta-CcE_SeFu.js';
4
- import { T as ThemeManifest, a as ThemeMode$1 } from './_contract-BlKMR4eX.js';
5
- export { D as DENSITY_TOKENS, b as DensityToken, M as MOTION_TOKENS, c as MotionToken, R as RADIUS_TOKENS, d as RadiusToken, S as SEMANTIC_TOKENS, e as SemanticToken, f as TYPE_OVERLINE_TOKENS, g as TYPE_REFINEMENT_TOKENS, h as TYPE_SCALE_TOKENS, i as TYPE_UI_TOKENS, j as TypeOverlineToken, k as TypeRefinementToken, l as TypeScaleToken, m as TypeUiToken, W as WORKSPACE_SURFACE_TOKENS, n as WorkspaceSurfaceToken } from './_contract-BlKMR4eX.js';
6
- import { T as ThemeId } from './theme-provider-CuYYxF0o.js';
7
- export { D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, h as applySableColorMode, i as applySableTheme, k as defaultThemeId, m as getThemeManifest, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from './theme-provider-CuYYxF0o.js';
4
+ import { T as ThemeManifest, a as ThemeMode$1 } from './_contract-Cn3lUpih.js';
5
+ export { D as DENSITY_TOKENS, b as DensityToken, M as MOTION_TOKENS, c as MotionToken, R as RADIUS_TOKENS, d as RadiusToken, S as SEMANTIC_TOKENS, e as SemanticToken, f as TYPE_OVERLINE_TOKENS, g as TYPE_REFINEMENT_TOKENS, h as TYPE_SCALE_TOKENS, i as TYPE_UI_TOKENS, j as TypeOverlineToken, k as TypeRefinementToken, l as TypeScaleToken, m as TypeUiToken, W as WORKSPACE_SURFACE_TOKENS, n as WorkspaceSurfaceToken } from './_contract-Cn3lUpih.js';
6
+ import { T as ThemeId } from './theme-provider-CGeiusgu.js';
7
+ export { D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, h as applySableColorMode, i as applySableTheme, k as defaultThemeId, m as getThemeManifest, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from './theme-provider-CGeiusgu.js';
8
8
  export { AspectBox, AspectBoxProps, Bleed, BleedProps, Breakpoint, Center, CenterProps, Col, ColProps, Columns, ColumnsProps, Grid, GridOverlay, GridOverlayProps, GridProps, Page, PageProps, Region, RegionMax, RegionProps, Responsive, Row, RowProps, SafeArea, SafeAreaProps, Spacer, SpacerProps, SplitPane, SplitPaneProps, Stack, StackProps } from './layout/index.js';
9
9
  export { Icon, IconContextValue, IconProps, IconProvider, IconProviderProps, composeIconSets, useIconSet } from './icon/index.js';
10
10
  export { reiconIconSet as fallbackIconSet, reiconIconNames, reiconIconSet } from './icon-sets/reicon.js';
@@ -1819,7 +1819,7 @@ type ChartTone = "edition" | "collectible" | "standard" | "info" | "warning" | "
1819
1819
  declare const CHART_SERIES_COUNT = 8;
1820
1820
  /**
1821
1821
  * The colour of series `index` when it names none: the theme's chart palette,
1822
- * in order. The palette (`--chart-1` … `--chart-8`, defined by the Base theme
1822
+ * in order. The palette (`--chart-1` … `--chart-8`, defined by the Sable theme
1823
1823
  * and inherited by every other) is validated for colour-blind separation of
1824
1824
  * neighbours, a normal-vision floor, a lightness band and a chroma floor, in
1825
1825
  * light and dark. Past eight series, fold the rest into "Other".
@@ -7499,6 +7499,6 @@ interface UseCopyResult {
7499
7499
  */
7500
7500
  declare function useCopy(text: string, { timeout }?: UseCopyOptions): UseCopyResult;
7501
7501
 
7502
- declare const SABLE_UI_VERSION: "0.3.2";
7502
+ declare const SABLE_UI_VERSION: "0.4.0";
7503
7503
 
7504
7504
  export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, AppHeader, type AppHeaderProps, AppShell, type AppShellProps, AreaChart, type AreaChartProps, Attachment, type AttachmentProps, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, type AvatarGroupCountProps, type AvatarGroupProps, AvatarImage, type AvatarProps, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, type BadgeAppearance, type BadgeProps, type BadgeTone, Banner, type BannerProps, BarChart, type BarChartProps, BottomNav, BottomNavItem, type BottomNavItemProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, BreadcrumbPage, type BreadcrumbPageProps, BreadcrumbSeparator, type BreadcrumbSeparatorProps, Breadcrumbs, type BreadcrumbsProps, Browser, type BrowserProps, Bubble, type BubbleProps, BubbleReaction, type BubbleReactionProps, BubbleReactions, type BubbleReactionsProps, Button, type ButtonProps, CHART_SERIES_COUNT, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, type CalendarProps, Card, CardAction, CardContent, type CardContentProps, CardDescription, CardFooter, CardHeader, CardList, type CardProps, CardTitle, type CardTitleProps, Carousel, type CarouselProps, type CarouselSlidesToShow, type ChartCategories, type ChartCenterLabel, type ChartCurve, type ChartDatum, type ChartSeries, type ChartTone, type ChartTooltipIndicator, type ChartTooltipOptions, type ChartValueFormatter, ChatComposer, type ChatComposerProps, Checkbox, CheckboxCard, type CheckboxCardProps, type CheckboxCardTone, CheckboxGroup, type CheckboxGroupProps, Code, CodeBlock, type CodeBlockProps, CodeHighlightProvider, type CodeHighlighter, type CodeProps, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, type ColorFieldProps, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, type ComboboxItemGroup, ComboboxItemIndicator, ComboboxList, ComboboxPortal, type ComboboxProps, ComboboxValue, ComboboxVirtualList, type ComboboxVirtualListProps, type CommandFilter, type CommandItem, CommandMenu, CommandMenuGroup, type CommandMenuGroupProps, CommandMenuItem, type CommandMenuItemProps, CommandMenuPanel, type CommandMenuPanelProps, type CommandMenuProps, CommandMenuTrigger, type CommandMenuTriggerProps, type ComponentGroup, type ComponentGuidance, ComponentMeta, type ComponentRegister, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, DateTile, type DateTileProps, Density, DensityProvider, Density as DensityValue, DestructiveActionModal, type DestructiveActionModalProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, type DialogSize, DialogTitle, DialogTrigger, EmptyState, type EmptyStateProps, Entity, type EntityProps, Feedback, type FeedbackData, type FeedbackProps, type FeedbackRating, Field, type FieldProps, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, type FileDropzoneProps, type FileRejection, type FileRejectionCode, Form, type FormProps, GlidingHighlight, type GlidingHighlightProps, type Hsv, InlineStatus, type InlineStatusProps, type StatusTone as InlineTone, Input, type InputProps$1 as InputProps, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, Label, type LabelProps$2 as LabelProps, LineChart, type LineChartProps, Marker, type MarkerProps, Marquee, type MarqueeProps, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, type MessageProps, MessageScroller, type MessageScrollerHandle, type MessageScrollerProps, Meter, MeterIndicator, MeterLabel, type MeterSize, type MeterTone, MeterTrack, MeterValue, MiddleTruncate, type MiddleTruncateProps, type MotionPolicy, MotionProvider, type MotionProviderProps, Note, type NoteAction, type NoteProps, NotificationBadge, type NotificationBadgeProps, NumberField, type NumberFieldProps, type OrbMode, type OrbSize, OtpField, type OtpFieldProps, OverflowMenu, type OverflowMenuProps, PageHeader, type PageHeaderProps, PageLoader, type PageLoaderProps, Pagination, type PaginationProps, Panel, PanelGroup, PanelResizer, type PanelResizerProps, PanelSection, type PanelWidthBounds, Phone, type PhoneProps, PieChart, type PieChartProps, Pill, type PillProps, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, Presence, type PresenceProps, Progress, ProgressIndicator, ProgressLabel, type ProgressSize, ProgressTrack, ProgressValue, PropertyPair, PropertyRow, RadarChart, type RadarChartProps, RadialChart, type RadialChartProps, Radio, RadioCard, type RadioCardProps, type RadioCardTone, RadioGroup, RelativeTime, type RelativeTimeProps, Reveal, type RevealProps, type Rgb, SABLE_UI_VERSION, SEGMENTED_BAR_TONES, STATUS_TONES, STATUS_TONE_ICON, type ScrollCommandOptions, Scroller, type ScrollerProps, ScrubField, type ScrubFieldProps, SearchInput, type SearchInputProps, SegmentedBar, type SegmentedBarProps, type SegmentedBarSegment, type SegmentedBarTone, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps$3 as SeparatorProps, SettingsMenu, type SettingsMenuProps, SettingsRow, type SettingsRowProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, type ShowMoreProps, SideNav, type SideNavActiveVariant, SideNavGroup, type SideNavGroupProps, SideNavItem, type SideNavItemProps, type SideNavProps, type SideNavSize, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarProps, SimpleTooltip, Skeleton, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, Sparkline, type SparklineProps, Spinner, type SpinnerProps, StatusBar, StatusBarGroup, StatusBarSeparator, StatusDot, type StatusDotProps, type StatusTone, type StepStatus, Stepper, StepperItem, type StepperItemProps, type StepperOrientation, type StepperProps, StreamingText, type StreamingTextProps, Switch, TRANSPARENT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, TextReveal, type TextRevealProps, Textarea, type TextareaProps, ThemeId, ThemeManifest, ThemeMode$1 as ThemeManifestMode, type ThemeMode, ThemeSelector, type ThemeSelectorProps, ThemeSwitcher, type ThemeSwitcherProps, ThinkingOrb, type ThinkingOrbProps, ToastAction, type ToastApi, ToastClose, ToastDescription, type ToastManagerApi, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, ToastRoot, ToastTitle, type ToastType, ToastViewport, Toggle, ToggleGroup, type ToggleGroupAppearance, Toggle as ToggleGroupItem, Toolbar, ToolbarButton, type ToolbarButtonProps, ToolbarGroup, type ToolbarGroupProps, ToolbarInput, type ToolbarInputProps, ToolbarLink, type ToolbarLinkProps, type ToolbarProps, ToolbarSeparator, type ToolbarSeparatorProps, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, type UseCopyOptions, type UseCopyResult, type UseInViewOptions, type UsePanelWidthOptions, Video, type VideoProps, ViewTransitionName, type ViewTransitionNameProps, Workspace, WorkspaceBar, WorkspaceCanvas, accordionMeta, appHeaderMeta, appShellMeta, areaChartMeta, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bannerMeta, bannerVariants, barChartMeta, bottomNavMeta, breadcrumbsMeta, browserMeta, bubbleMeta, buttonMeta, buttonVariants, calendarMeta, cardMeta, carouselMeta, chartColor, toneColor as chartToneColor, chatComposerMeta, checkboxCardMeta, checkboxMeta, cn, codeBlockMeta, codeMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, commandMenuMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, dataTableMeta, datePickerMeta, dateTileMeta, destructiveActionModalMeta, dialogMeta, emptyStateMeta, entityMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formMeta, formatFileSize, formatHex, glidingHighlightMeta, hsvToRgb, hueColor, inlineStatusMeta, inputMeta, isTransparent, kbdMeta, labelMeta, lineChartMeta, markerMeta, marqueeMeta, menuMeta, messageMeta, messageScrollerMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, middleTruncateMeta, noteMeta, noteVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, numberFieldMeta, otpFieldMeta, overflowMenuMeta, pageHeaderMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, phoneMeta, pieChartMeta, pillMeta, pillVariants, popoverMeta, progressMeta, progressTrackVariants, radarChartMeta, radialChartMeta, radioCardMeta, radioMeta, relativeTimeMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, searchInputMeta, segmentedBarMeta, segmentedBarSegmentVariants, segmentedBarVariants, selectMeta, separatorMeta, settingsMenuMeta, sheetMeta, sheetVariants, showMoreMeta, sideNavMeta, sidebarMeta, skeletonMeta, sliderMeta, sparklineMeta, spinnerMeta, startViewTransition, statusBarMeta, statusDotMeta, statusDotVariants, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, streamingTextMeta, supportsViewTransitions, switchMeta, tableMeta, tabsMeta, textareaMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, toggleMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, tooltipMeta, treeIndent, treeMeta, useAnchoredDensity, useComboboxFilteredItems, useCommandMenuShortcut, useCopy, useDensity, useInView, useMotion, usePanelWidth, useReducedMotion, useScrollActivity, useSidebar, useToast, useToastManager, useTypeTier, videoMeta, workspaceMeta };
package/dist/index.js CHANGED
@@ -12,15 +12,15 @@ export { cn } from './chunk-SRQWQUTW.js';
12
12
  import { Icon } from './chunk-NGBDP3BM.js';
13
13
  export { Icon, IconProvider, composeIconSets, useIconSet } from './chunk-NGBDP3BM.js';
14
14
  export { reiconIconSet as fallbackIconSet, reiconIconNames, reiconIconSet } from './chunk-TH563WLW.js';
15
- import { featuredThemeManifests, defaultThemeId } from './chunk-MXJX3NZI.js';
16
- export { DEFAULT_THEME_ID, SABLE_COLOR_STORAGE_KEY, SABLE_THEME_ATTRIBUTE, SABLE_THEME_STORAGE_KEY, SableThemeProvider, applySableColorMode, applySableTheme, defaultThemeId, getThemeManifest, isThemeId, themeIds, themeManifestById, themeManifests, useSableTheme } from './chunk-MXJX3NZI.js';
15
+ import { featuredThemeManifests, defaultThemeId } from './chunk-EFLC4NA3.js';
16
+ export { DEFAULT_THEME_ID, SABLE_COLOR_STORAGE_KEY, SABLE_THEME_ATTRIBUTE, SABLE_THEME_STORAGE_KEY, SableThemeProvider, applySableColorMode, applySableTheme, defaultThemeId, getThemeManifest, isThemeId, themeIds, themeManifestById, themeManifests, useSableTheme } from './chunk-EFLC4NA3.js';
17
17
  import './chunk-7MG4SHYX.js';
18
18
  import './chunk-KO3NAEBF.js';
19
19
  import './chunk-L4IEIQQA.js';
20
- import './chunk-EP55KSYA.js';
20
+ import './chunk-EHH4MHO6.js';
21
21
  import './chunk-ADLLDWLW.js';
22
22
  import './chunk-K7RUHUGC.js';
23
- import './chunk-YXBOYWBN.js';
23
+ import './chunk-C4Z3CF54.js';
24
24
  import { NumberField as NumberField$1 } from '@base-ui/react/number-field';
25
25
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
26
26
  import { createContext, useId, useRef, useState, useEffect, useLayoutEffect, useContext, useMemo, useCallback, useSyncExternalStore, Children } from 'react';
@@ -9358,7 +9358,7 @@ var themeSelectorMeta = {
9358
9358
  },
9359
9359
  {
9360
9360
  label: "Controlled",
9361
- code: 'const [themeId, setThemeId] = useState<ThemeId>("base")\n<ThemeSelector value={themeId} onValueChange={setThemeId} />'
9361
+ code: 'const [themeId, setThemeId] = useState<ThemeId>("sable")\n<ThemeSelector value={themeId} onValueChange={setThemeId} />'
9362
9362
  },
9363
9363
  {
9364
9364
  label: "In chrome \u2014 a popover or a docked panel",
@@ -11260,6 +11260,6 @@ var carouselMeta = {
11260
11260
  };
11261
11261
 
11262
11262
  // src/index.ts
11263
- var SABLE_UI_VERSION = "0.3.2";
11263
+ var SABLE_UI_VERSION = "0.4.0";
11264
11264
 
11265
11265
  export { Banner, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Breadcrumbs, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, Carousel, CheckboxCard, CheckboxGroup, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxList, ComboboxPortal, ComboboxValue, ComboboxVirtualList, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DatePicker, Feedback, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, GlidingHighlight, InlineStatus, LineChart, Marquee, MiddleTruncate, Note, NumberField, PageLoader, Pagination, Panel, PanelGroup, PanelResizer, PanelSection, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, PropertyPair, PropertyRow, RadarChart, Radio, RadioCard, RadioGroup, SABLE_UI_VERSION, STATUS_TONES, STATUS_TONE_ICON, ScrubField, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, StatusBar, StatusBarGroup, StatusBarSeparator, TRANSPARENT, ThemeSelector, ThemeSwitcher, ThinkingOrb, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, Video, Workspace, WorkspaceBar, WorkspaceCanvas, bannerMeta, bannerVariants, breadcrumbsMeta, calendarMeta, carouselMeta, checkboxCardMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, datePickerMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formatFileSize, formatHex, glidingHighlightMeta, hsvToRgb, hueColor, inlineStatusMeta, isTransparent, lineChartMeta, marqueeMeta, middleTruncateMeta, noteMeta, noteVariants, numberFieldMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, pillMeta, popoverMeta, radarChartMeta, radioCardMeta, radioMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, sheetMeta, sheetVariants, showMoreMeta, statusBarMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, treeIndent, treeMeta, useComboboxFilteredItems, usePanelWidth, useToast, useToastManager, videoMeta, workspaceMeta };
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../_contract-BlKMR4eX.js';
1
+ import { T as ThemeManifest } from '../_contract-Cn3lUpih.js';
2
2
 
3
3
  /** The public theme vocabulary. Presets never need component overrides. */
4
4
  declare const PRESET_TOKEN_NAMES: readonly string[];
@@ -24,7 +24,7 @@ var PRESET_TOKEN_NAMES = [.../* @__PURE__ */ new Set([
24
24
  "theme-shadow-md",
25
25
  "theme-shadow-lg",
26
26
  "theme-shadow-xl",
27
- // Chart series colours: optional — a preset without them inherits Base's validated set.
27
+ // Chart series colours: optional — a preset without them inherits the Sable theme's validated set.
28
28
  "chart-1",
29
29
  "chart-2",
30
30
  "chart-3",
package/dist/styles.css CHANGED
@@ -12,7 +12,7 @@
12
12
  @utility set, and an @source directive so Tailwind scans the package.
13
13
  ========================================================================== */
14
14
 
15
- /* Base ONLY. It is the default theme, and it is also the layout floor:
15
+ /* The Sable theme ONLY. It is the default theme, and it is also the layout floor:
16
16
  `--page-inset`, the `--region-*` caps, `--rhythm-unit`, `--grid-*` and
17
17
  `--z-*` are declared here on a bare `:root`, so every other theme — bundled
18
18
  or app-owned — inherits them. Removing it does not save 18 KB, it silently
@@ -30,10 +30,10 @@
30
30
  @import "@sableui/react/themes.css";
31
31
 
32
32
  The failure mode if you forget: `data-sable-theme` is set, nothing matches,
33
- and the page renders as Base — no error, just the wrong theme. If a
33
+ and the page renders as the Sable theme — no error, just the wrong theme. If a
34
34
  theme selector looks like it does nothing, this import is the first thing
35
35
  to check. */
36
- @import "./themes/base/base.css";
36
+ @import "./themes/sable/sable.css";
37
37
 
38
38
  /* Scan the package for utility classes (path is relative to THIS file). Covers
39
39
  both workspace source (.ts/.tsx next to this file in src/) and the compiled
@@ -119,7 +119,7 @@
119
119
  /* Motion easings → ease-* utilities (ease-standard/decelerate/emphasized;
120
120
  `linear` is built-in). Durations stay raw CSS vars and are consumed via
121
121
  `duration-(--motion-duration-*)` so the --motion-scale kill-switch applies.
122
- See themes/base/base.css → Motion tokens, and DESIGN.md → Motion. */
122
+ See themes/sable/sable.css → Motion tokens, and DESIGN.md → Motion. */
123
123
  --ease-standard: var(--motion-ease-standard);
124
124
  --ease-decelerate: var(--motion-ease-decelerate);
125
125
  --ease-emphasized: var(--motion-ease-emphasized);
@@ -168,7 +168,7 @@
168
168
  dims what is behind it, per mode.
169
169
 
170
170
  `--overlay` is near-black in every theme and mode, which is right for a
171
- scrim. But a dark page is near-black too: Base's dark `--background` IS its
171
+ scrim. But a dark page is near-black too: the Sable theme's dark `--background` IS its
172
172
  `--overlay`, so a 50% scrim over it changed nothing, and with a dialog open
173
173
  the page's text stayed at nearly full strength — while foundations/surfaces
174
174
  calls the scrim "the primary depth cue" for a modal. Dark mode needs more of
@@ -322,11 +322,12 @@
322
322
  --panel-raised: var(--theme-panel-raised, color-mix(in oklch, var(--background) 94%, var(--foreground)));
323
323
 
324
324
  /* A compact control docked in a panel: the xs field rung and a field-style
325
- button. Inset to the canvas tone at rest, a panel step under the pointer.
326
- Named so a panel control states its role instead of borrowing the work
327
- surface's token and a fixed hover. */
328
- --panel-field: var(--theme-panel-field, var(--canvas));
329
- --panel-field-hover: var(--theme-panel-field-hover, var(--panel-raised));
325
+ button. Mixed from the panel it sits in, a further step under the pointer.
326
+ It used to borrow the canvas tone (3% ink), which measured 1.04:1 against
327
+ a dark panel: the field all but vanished. 9% is what a shipped inspector
328
+ settled on (1.15:1 dark, 1.25:1 light). */
329
+ --panel-field: var(--theme-panel-field, color-mix(in oklch, var(--panel) 91%, var(--panel-foreground)));
330
+ --panel-field-hover: var(--theme-panel-field-hover, color-mix(in oklch, var(--panel) 88%, var(--panel-foreground)));
330
331
 
331
332
  /* The structural edge. Alpha-based so it holds over any surface beneath it. */
332
333
  --hairline: var(--theme-hairline, color-mix(in oklch, transparent 93%, var(--foreground)));
@@ -1477,7 +1478,7 @@
1477
1478
 
1478
1479
  Motion is a first-class layer of Sable. Three ideas:
1479
1480
 
1480
- 1. TOKENS (themes/base/base.css): a duration ladder + semantic
1481
+ 1. TOKENS (themes/sable/sable.css): a duration ladder + semantic
1481
1482
  easings, every duration scaled by --motion-scale.
1482
1483
  2. RECIPES (below): named, reusable motion behaviours a component opts into
1483
1484
  with ONE class (e.g. `motion-pop`, `motion-press`) instead of hand-rolling
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { T as ThemeManifest } from './_contract-BlKMR4eX.js';
3
+ import { T as ThemeManifest } from './_contract-Cn3lUpih.js';
4
4
 
5
5
  declare const SABLE_THEME_ATTRIBUTE: "data-sable-theme";
6
6
  declare const DEFAULT_THEME_ID: string;
@@ -62,7 +62,7 @@ interface SableThemeProviderProps {
62
62
  preference?: SableThemePreference;
63
63
  /** Notified whenever setPreference/setColorPreference is called. */
64
64
  onPreferenceChange?: (preference: SableThemePreference) => void;
65
- /** Initial design theme when uncontrolled and no stored value exists. @default "base" */
65
+ /** Initial design theme when uncontrolled and no stored value exists. @default "sable" */
66
66
  defaultThemeId?: AnyThemeId;
67
67
  /** Controlled design theme id. */
68
68
  themeId?: AnyThemeId;
@@ -98,7 +98,7 @@ declare function applySableTheme(themeId: AnyThemeId, target?: HTMLElement | nul
98
98
  declare function SableThemeProvider({ children, defaultPreference, preference: controlledPreference, onPreferenceChange, defaultThemeId, themeId: controlledThemeId, onThemeIdChange, colorStorageKey, themeStorageKey, target, customThemes, }: SableThemeProviderProps): react.JSX.Element;
99
99
  /**
100
100
  * Reads Sable's active color preference and design theme. Safe without a
101
- * provider; mutators become no-ops and the default (Base, dark) is reported.
101
+ * provider; mutators become no-ops and the default (Sable, dark) is reported.
102
102
  */
103
103
  declare function useSableTheme(): SableThemeContextValue;
104
104
 
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
1
+ import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
2
2
 
3
3
  /**
4
4
  * Mono Editorial — a Sable theme. Concrete token values live in
@@ -1 +1 @@
1
- export { atelierTheme, atelier_default as default } from '../../chunk-YXBOYWBN.js';
1
+ export { atelierTheme, atelier_default as default } from '../../chunk-C4Z3CF54.js';
@@ -5,7 +5,7 @@
5
5
  --tone-edition, …), never any palette ramp directly. See themes/_contract.ts.
6
6
  ========================================================================== */
7
7
 
8
- /* Scoped only — never a bare `:root`. Base alone owns `:root` as the
8
+ /* Scoped only — never a bare `:root`. The Sable theme alone owns `:root` as the
9
9
  package default; a second theme claiming it leaks its tokens into every other
10
10
  theme, and `themes.test.ts` fails the build on it. */
11
11
  [data-sable-theme="ember"] {
@@ -238,8 +238,11 @@
238
238
  --input: oklch(33% 0.012 65);
239
239
  --control-border: oklch(55.6% 0.0084 67.7);
240
240
  --ring: oklch(65.2% 0.235 32.9);
241
- --primary: oklch(65.2% 0.235 32.9);
242
- --primary-foreground: oklch(14.1% 0.018 70.5);
241
+ /* The light-mode fill, not a brighter one: on the brighter fill WCAG preferred
242
+ dark ink (5.6:1), but it reads at APCA Lc 42 — muddy — against Lc 67 for
243
+ white. At 58% white clears both (4.8:1, Lc 76). */
244
+ --primary: oklch(58% 0.245 32.9);
245
+ --primary-foreground: oklch(100% 0 0);
243
246
  --secondary: oklch(26% 0.012 67.2); /* a step ABOVE the card, so a secondary Button reads on a card and on the page */
244
247
  --secondary-foreground: oklch(100% 0 0);
245
248
  --accent: oklch(29% 0.014 60);
@@ -264,8 +267,8 @@
264
267
  --overlay-foreground: oklch(100% 0 0);
265
268
  --sidebar: oklch(15.9% 0.016 65);
266
269
  --sidebar-foreground: oklch(100% 0 0);
267
- --sidebar-primary: oklch(65.2% 0.235 32.9);
268
- --sidebar-primary-foreground: oklch(14.1% 0.018 70.5);
270
+ --sidebar-primary: oklch(58% 0.245 32.9);
271
+ --sidebar-primary-foreground: oklch(100% 0 0);
269
272
  --sidebar-accent: oklch(22% 0.010 60);
270
273
  --sidebar-accent-foreground: oklch(100% 0 0);
271
274
  --sidebar-border: oklch(25.2% 0.012 67.2);
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
1
+ import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
2
2
 
3
3
  /**
4
4
  * Ember — a Sable theme. Concrete token values live in
@@ -1,10 +1,10 @@
1
- export { A as AnyThemeId, D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, T as ThemeId, h as applySableColorMode, i as applySableTheme, j as buildThemeManifestById, k as defaultThemeId, l as featuredThemeManifests, m as getThemeManifest, n as isKnownThemeId, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from '../theme-provider-CuYYxF0o.js';
1
+ export { A as AnyThemeId, D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, T as ThemeId, h as applySableColorMode, i as applySableTheme, j as buildThemeManifestById, k as defaultThemeId, l as featuredThemeManifests, m as getThemeManifest, n as isKnownThemeId, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from '../theme-provider-CGeiusgu.js';
2
2
  export { atelierTheme } from './atelier/index.js';
3
- export { baseTheme } from './base/index.js';
4
3
  export { default as emberTheme } from './ember/index.js';
5
4
  export { default as meridianTheme } from './meridian/index.js';
6
5
  export { default as poppyTheme } from './poppy/index.js';
7
6
  export { default as prismTheme } from './prism/index.js';
7
+ export { default as sableTheme } from './sable/index.js';
8
8
  export { default as verdantTheme } from './verdant/index.js';
9
9
  import 'react';
10
- import '../_contract-BlKMR4eX.js';
10
+ import '../_contract-Cn3lUpih.js';
@@ -1,8 +1,8 @@
1
- export { DEFAULT_THEME_ID, SABLE_COLOR_STORAGE_KEY, SABLE_THEME_ATTRIBUTE, SABLE_THEME_STORAGE_KEY, SableThemeProvider, applySableColorMode, applySableTheme, buildThemeManifestById, defaultThemeId, featuredThemeManifests, getThemeManifest, isKnownThemeId, isThemeId, themeIds, themeManifestById, themeManifests, useSableTheme } from '../chunk-MXJX3NZI.js';
1
+ export { DEFAULT_THEME_ID, SABLE_COLOR_STORAGE_KEY, SABLE_THEME_ATTRIBUTE, SABLE_THEME_STORAGE_KEY, SableThemeProvider, applySableColorMode, applySableTheme, buildThemeManifestById, defaultThemeId, featuredThemeManifests, getThemeManifest, isKnownThemeId, isThemeId, themeIds, themeManifestById, themeManifests, useSableTheme } from '../chunk-EFLC4NA3.js';
2
2
  export { prismTheme } from '../chunk-7MG4SHYX.js';
3
3
  export { verdantTheme } from '../chunk-KO3NAEBF.js';
4
4
  export { emberTheme } from '../chunk-L4IEIQQA.js';
5
- export { baseTheme } from '../chunk-EP55KSYA.js';
5
+ export { sableTheme } from '../chunk-EHH4MHO6.js';
6
6
  export { poppyTheme } from '../chunk-ADLLDWLW.js';
7
7
  export { meridianTheme } from '../chunk-K7RUHUGC.js';
8
- export { atelierTheme } from '../chunk-YXBOYWBN.js';
8
+ export { atelierTheme } from '../chunk-C4Z3CF54.js';
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
1
+ import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
2
2
 
3
3
  /**
4
4
  * Meridian — a Sable theme. Concrete token values live in
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
1
+ import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
2
2
 
3
3
  /** Original Sable preset; no third-party artwork or reference-site code. */
4
4
  declare const poppyTheme: ThemeManifest;
@@ -122,7 +122,7 @@
122
122
  --theme-button-radius: 12px;
123
123
  --button-radius: var(--theme-button-radius);
124
124
  /* Hard offsets the whole way up. Only sm and md were declared, so the
125
- default, lg and xl steps fell through to Base's soft blurs and one ladder
125
+ default, lg and xl steps fell through to the Sable theme's soft blurs and one ladder
126
126
  spoke two shadow languages. Offset doubles roughly per step. */
127
127
  --theme-shadow-sm: 3px 3px 0 oklch(32% 0.05 320 / 0.12);
128
128
  --theme-shadow: 4px 4px 0 oklch(32% 0.05 320 / 0.13);
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
1
+ import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
2
2
 
3
3
  /** Original Sable preset; no third-party artwork or reference-site code. */
4
4
  declare const prismTheme: ThemeManifest;
@@ -0,0 +1,9 @@
1
+ import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
2
+
3
+ /**
4
+ * Sable — the default theme. Concrete token values live in
5
+ * `sable.css`; this manifest is the typed, machine-readable description.
6
+ */
7
+ declare const sableTheme: ThemeManifest;
8
+
9
+ export { sableTheme as default, sableTheme };
@@ -0,0 +1 @@
1
+ export { sable_default as default, sableTheme } from '../../chunk-EHH4MHO6.js';
@@ -1,5 +1,5 @@
1
1
  /* ==========================================================================
2
- Base — the default Sable theme.
2
+ Sable — the default theme.
3
3
 
4
4
  Dark-by-default, content-first, fluid product aesthetic. Palette authored in
5
5
  OKLCH (locked hue per ramp, perceptually-even lightness, sRGB-capped chroma).
@@ -14,7 +14,7 @@
14
14
  ========================================================================== */
15
15
 
16
16
  :root,
17
- [data-sable-theme="base"] {
17
+ [data-sable-theme="sable"] {
18
18
  /* Chart series colours — a categorical palette validated for colour-blind
19
19
  separation (adjacent pairs), a normal-vision floor, a lightness band and a
20
20
  chroma floor against this theme's surfaces. Charts take them in order
@@ -319,7 +319,7 @@
319
319
  font size. The docs app runs a 14px responsive root, so `56rem` rendered
320
320
  784px while claiming 896px and `80rem` rendered 1120px while claiming
321
321
  1280px — the default theme silently 12.5% under its own spec, in its own
322
- primary consumer. Every generated theme already declares px; Base was
322
+ primary consumer. Every generated theme already declares px; Sable was
323
323
  the only one that did not, which is the worst possible one to get wrong.
324
324
  The values below are DESIGN.md's table verbatim. */
325
325
  --region-form: 480px; /* narrow single-col forms, auth, dialogs */
@@ -382,11 +382,11 @@
382
382
  Semantic tokens — dark mode (the brand default)
383
383
  ========================================================================== */
384
384
  .dark,
385
- .dark[data-sable-theme="base"],
386
- [data-sable-theme="base"].dark,
385
+ .dark[data-sable-theme="sable"],
386
+ [data-sable-theme="sable"].dark,
387
387
  /* A preset-scoped subtree inside a dark page follows the page into dark mode;
388
388
  add `.light` to that subtree to pin it light. */
389
- .dark [data-sable-theme="base"]:not(.light) {
389
+ .dark [data-sable-theme="sable"]:not(.light) {
390
390
  /* The same eight hues, stepped for the dark band (L 0.48–0.67, >= 3:1 on
391
391
  the dark surfaces) and validated as a set — not a lightness flip. */
392
392
  --chart-1: oklch(62.21% 0.1612 255.05); /* #3987e5 */
@@ -1,4 +1,4 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
1
+ import { T as ThemeManifest } from '../../_contract-Cn3lUpih.js';
2
2
 
3
3
  /**
4
4
  * Verdant — a Sable theme. Concrete token values live in
package/dist/themes.css CHANGED
@@ -6,8 +6,8 @@
6
6
  @import "@sableui/react/themes.css"; ← this file
7
7
 
8
8
  For an app that offers the bundled set as user-selectable themes (a
9
- `<ThemeSelector>` over Base / Meridian / Prism / Verdant / Ember / Poppy). `styles.css`
10
- carries Base alone, since the alternates are `[data-sable-theme]`-scoped
9
+ `<ThemeSelector>` over Sable / Meridian / Prism / Verdant / Ember / Poppy). `styles.css`
10
+ carries the Sable theme alone, since the alternates are `[data-sable-theme]`-scoped
11
11
  and dead weight in the overwhelmingly common single-theme app.
12
12
 
13
13
  Needing exactly one alternate? Import just that one instead — this file is a
@@ -15,7 +15,7 @@
15
15
 
16
16
  @import "@sableui/react/theme/prism.css";
17
17
 
18
- Base is NOT here: it ships in `styles.css` and must, since it declares
18
+ The Sable theme is NOT here: it ships in `styles.css` and must, since it declares
19
19
  the layout floor (`--page-inset`, `--region-*`, `--rhythm-unit`) on a bare
20
20
  `:root` that every other theme inherits.
21
21
  ========================================================================== */
@@ -62,7 +62,7 @@ const { themeId, setThemeId, themes, mode } = useSableTheme()
62
62
  ```tsx
63
63
  import { ThemeSelector, type ThemeId } from "@sableui/react"
64
64
 
65
- const [themeId, setThemeId] = useState<ThemeId>("base")
65
+ const [themeId, setThemeId] = useState<ThemeId>("sable")
66
66
  <ThemeSelector value={themeId} onValueChange={setThemeId} />
67
67
  ```
68
68
 
@@ -319,7 +319,7 @@ Which token to reach for, and what it is most often confused with. Generated fro
319
319
  - **`panel-raised`** — One step up from `panel`: a selected row, a hover fill, a nested well. *Use for:* Tree row selection, hovered panel rows, and the light square of a checkerboard.
320
320
  - **Not** `accent`. In a workspace the tonal band is only a few units wide; `accent` is a page-register step and reads as a shout in chrome.
321
321
  - **`panel-field`** — The inset fill of a compact control docked in a panel. *Use for:* The `size="xs"` field rung (Input, SelectTrigger, ScrubField share it) and `<Button variant="field">`. A panel control of your own that should sit among those fields.
322
- - **Not** `canvas`. It resolves to the canvas tone by default, but `canvas` names the work surface; a field that borrows it says the wrong thing and stops following a theme that tunes its fields.
322
+ - **Not** `canvas`. `canvas` names the work surface, and its step off the panel is too faint to read as a field in dark mode. This one is mixed from the panel itself, so it follows a theme that retunes the panel. Tune it with `--theme-panel-field`.
323
323
  - **`panel-field-hover`** — The hover step of a `panel-field` control. *Use for:* `hover:bg-panel-field-hover` on anything filled with `panel-field`.
324
324
  - **Not** `hover:bg-panel-raised`. A fixed surface token as a hover is what the surface-hover lint rejects; this one is defined against the panel field it steps from.
325
325
  - **`hairline`** — The 1px structural line that carries all separation in tool chrome. *Use for:* Between panel sections, under a toolbar, above a status bar.
@@ -62,7 +62,7 @@ chosen by what the surface *does*:
62
62
  - `secondary` — a control fill (something you can press)
63
63
  - `accent` — a transient state (hover, selected, current)
64
64
 
65
- Pick by appearance and it works in Base and breaks in Meridian. Every token's
65
+ Pick by appearance and it works in the Sable theme and breaks in Meridian. Every token's
66
66
  intent, and what it gets confused with, is in
67
67
  `llms/reference/token-intent.md`.
68
68
 
@@ -21,7 +21,7 @@ pnpm exec sable preset --check .sable/presets/clay.json --json
21
21
  pnpm exec sable preset --css .sable/presets/clay.json
22
22
  ```
23
23
 
24
- The template starts from a complete Base theme snapshot. Give it a unique ID and
24
+ The template starts from a complete Sable theme snapshot. Give it a unique ID and
25
25
  label; author its tokens, direction, and reference observations. Avoid IDs of
26
26
  bundled themes. Save the record at `.sable/presets/<id>.json`. Advance `revision`
27
27
  when changing a shared record; keep earlier revisions when reproducibility matters.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Tokens make decisions consistent; they do not decide what deserves attention.
4
4
  Start with the user's task and content, then choose a visual direction. Sable's
5
- default Base identity is a starting point. A consumer can be light-first,
5
+ default Sable identity is a starting point. A consumer can be light-first,
6
6
  photographic, editorial, playful, or quiet while retaining semantic tokens,
7
7
  accessible behavior, and a coherent interaction policy.
8
8
 
package/llms-full.txt CHANGED
@@ -243,7 +243,7 @@ On npm as `@sableui/react`. The structured recipe is the `consume` block in `reg
243
243
  @source "<path-to>/@sableui/react/dist/**/*.{js,mjs}"; /* workspace: packages/sable/src; published: node_modules/@sableui/react/dist */
244
244
  ```
245
245
 
246
- 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — base: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
246
+ 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — sable: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
247
247
  5. **Providers + theme switching:**
248
248
 
249
249
  ```tsx
@@ -653,7 +653,7 @@ chosen by what the surface *does*:
653
653
  - `secondary` — a control fill (something you can press)
654
654
  - `accent` — a transient state (hover, selected, current)
655
655
 
656
- Pick by appearance and it works in Base and breaks in Meridian. Every token's
656
+ Pick by appearance and it works in the Sable theme and breaks in Meridian. Every token's
657
657
  intent, and what it gets confused with, is in
658
658
  `llms/reference/token-intent.md`.
659
659
 
@@ -1309,7 +1309,7 @@ pnpm exec sable preset --check .sable/presets/clay.json --json
1309
1309
  pnpm exec sable preset --css .sable/presets/clay.json
1310
1310
  ```
1311
1311
 
1312
- The template starts from a complete Base theme snapshot. Give it a unique ID and
1312
+ The template starts from a complete Sable theme snapshot. Give it a unique ID and
1313
1313
  label; author its tokens, direction, and reference observations. Avoid IDs of
1314
1314
  bundled themes. Save the record at `.sable/presets/<id>.json`. Advance `revision`
1315
1315
  when changing a shared record; keep earlier revisions when reproducibility matters.
@@ -1435,7 +1435,7 @@ requirement that every future brand use the same illustration or font pairing.
1435
1435
 
1436
1436
  Tokens make decisions consistent; they do not decide what deserves attention.
1437
1437
  Start with the user's task and content, then choose a visual direction. Sable's
1438
- default Base identity is a starting point. A consumer can be light-first,
1438
+ default Sable identity is a starting point. A consumer can be light-first,
1439
1439
  photographic, editorial, playful, or quiet while retaining semantic tokens,
1440
1440
  accessible behavior, and a coherent interaction policy.
1441
1441
 
@@ -18843,7 +18843,7 @@ const { themeId, setThemeId, themes, mode } = useSableTheme()
18843
18843
  ```tsx
18844
18844
  import { ThemeSelector, type ThemeId } from "@sableui/react"
18845
18845
 
18846
- const [themeId, setThemeId] = useState<ThemeId>("base")
18846
+ const [themeId, setThemeId] = useState<ThemeId>("sable")
18847
18847
  <ThemeSelector value={themeId} onValueChange={setThemeId} />
18848
18848
  ```
18849
18849
 
@@ -20304,7 +20304,7 @@ Which token to reach for, and what it is most often confused with. Generated fro
20304
20304
  - **`panel-raised`** — One step up from `panel`: a selected row, a hover fill, a nested well. *Use for:* Tree row selection, hovered panel rows, and the light square of a checkerboard.
20305
20305
  - **Not** `accent`. In a workspace the tonal band is only a few units wide; `accent` is a page-register step and reads as a shout in chrome.
20306
20306
  - **`panel-field`** — The inset fill of a compact control docked in a panel. *Use for:* The `size="xs"` field rung (Input, SelectTrigger, ScrubField share it) and `<Button variant="field">`. A panel control of your own that should sit among those fields.
20307
- - **Not** `canvas`. It resolves to the canvas tone by default, but `canvas` names the work surface; a field that borrows it says the wrong thing and stops following a theme that tunes its fields.
20307
+ - **Not** `canvas`. `canvas` names the work surface, and its step off the panel is too faint to read as a field in dark mode. This one is mixed from the panel itself, so it follows a theme that retunes the panel. Tune it with `--theme-panel-field`.
20308
20308
  - **`panel-field-hover`** — The hover step of a `panel-field` control. *Use for:* `hover:bg-panel-field-hover` on anything filled with `panel-field`.
20309
20309
  - **Not** `hover:bg-panel-raised`. A fixed surface token as a hover is what the surface-hover lint rejects; this one is defined against the panel field it steps from.
20310
20310
  - **`hairline`** — The 1px structural line that carries all separation in tool chrome. *Use for:* Between panel sections, under a toolbar, above a status bar.
package/llms.txt CHANGED
@@ -243,7 +243,7 @@ On npm as `@sableui/react`. The structured recipe is the `consume` block in `reg
243
243
  @source "<path-to>/@sableui/react/dist/**/*.{js,mjs}"; /* workspace: packages/sable/src; published: node_modules/@sableui/react/dist */
244
244
  ```
245
245
 
246
- 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — base: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
246
+ 4. **Fonts:** the theme declares the stacks; the host loads the files (every featured primary is available through Google Fonts). Per theme — sable: Figtree + DM Mono · meridian: Instrument Sans + Geist Mono · prism: Outfit + Roboto Mono + Playfair Display (display) · verdant: Inter + IBM Plex Mono + Instrument Serif (display) · ember: Work Sans + DM Mono · poppy: Albert Sans + Red Hat Mono + Unbounded (display). The machine-readable list is `registry.json → themes.available[].fonts`.
247
247
  5. **Providers + theme switching:**
248
248
 
249
249
  ```tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sableui/react",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Sable: themeable React components, layout patterns, and tools for coding agents.",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -71,11 +71,11 @@
71
71
  "default": "./dist/themes/ember/index.js"
72
72
  },
73
73
  "./theme/ember.css": "./dist/themes/ember/ember.css",
74
- "./theme/base": {
75
- "types": "./dist/themes/base/index.d.ts",
76
- "default": "./dist/themes/base/index.js"
74
+ "./theme/sable": {
75
+ "types": "./dist/themes/sable/index.d.ts",
76
+ "default": "./dist/themes/sable/index.js"
77
77
  },
78
- "./theme/base.css": "./dist/themes/base/base.css",
78
+ "./theme/sable.css": "./dist/themes/sable/sable.css",
79
79
  "./theme/poppy": {
80
80
  "types": "./dist/themes/poppy/index.d.ts",
81
81
  "default": "./dist/themes/poppy/index.js"
package/registry.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "https://sable.design/registry.schema.json",
3
3
  "name": "@sableui/react",
4
- "version": "0.3.2",
5
- "description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Base.",
4
+ "version": "0.4.0",
5
+ "description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
6
6
  "engine": {
7
7
  "behavior": "@base-ui/react",
8
8
  "styling": "tailwindcss@4",
@@ -42,9 +42,9 @@
42
42
  "designTheme": "Set data-sable-theme for alternate or app-owned themes; omit the attribute or use base for the default. Register portable presets with presetManifest() through SableThemeProvider customThemes, and load their scoped CSS and fonts.",
43
43
  "import": "import { Button, Card } from \"@sableui/react\"",
44
44
  "cssOptional": {
45
- "themes.css": "@import \"@sableui/react/themes.css\"; — all bundled alternatives. styles.css already includes Base. Alternate CSS is scoped by data-sable-theme.",
45
+ "themes.css": "@import \"@sableui/react/themes.css\"; — all bundled alternatives. styles.css already includes the Sable theme. Alternate CSS is scoped by data-sable-theme.",
46
46
  "theme/<id>.css": "@import \"@sableui/react/theme/prism.css\"; — one bundled alternative. Prefer this when the app uses exactly one.",
47
- "symptom": "The active theme renders as Base when its stylesheet has not been imported. Load the selected theme CSS before applying its ID."
47
+ "symptom": "The active theme renders as the Sable theme when its stylesheet has not been imported. Load the selected theme CSS before applying its ID."
48
48
  },
49
49
  "designWorkflow": {
50
50
  "brief": "sable docs designing-a-surface",
@@ -91,14 +91,14 @@
91
91
  }
92
92
  },
93
93
  "themes": {
94
- "default": "base",
94
+ "default": "sable",
95
95
  "available": [
96
96
  {
97
- "id": "base",
98
- "label": "Base",
97
+ "id": "sable",
98
+ "label": "Sable",
99
99
  "featured": true,
100
- "css": "./src/themes/base/base.css",
101
- "manifest": "./src/themes/base/index.ts",
100
+ "css": "./src/themes/sable/sable.css",
101
+ "manifest": "./src/themes/sable/index.ts",
102
102
  "import": "@sableui/react/styles.css",
103
103
  "modes": [
104
104
  "dark",
@@ -233,7 +233,7 @@
233
233
  "id": "atelier",
234
234
  "label": "Atelier",
235
235
  "deprecated": {
236
- "replacement": "base"
236
+ "replacement": "sable"
237
237
  },
238
238
  "featured": false,
239
239
  "css": "./src/themes/atelier/atelier.css",
@@ -1313,7 +1313,7 @@
1313
1313
  "name": "panel-field",
1314
1314
  "intent": "The inset fill of a compact control docked in a panel.",
1315
1315
  "whenToUse": "The `size=\"xs\"` field rung (Input, SelectTrigger, ScrubField share it) and `<Button variant=\"field\">`. A panel control of your own that should sit among those fields.",
1316
- "insteadOf": "`canvas`. It resolves to the canvas tone by default, but `canvas` names the work surface; a field that borrows it says the wrong thing and stops following a theme that tunes its fields.",
1316
+ "insteadOf": "`canvas`. `canvas` names the work surface, and its step off the panel is too faint to read as a field in dark mode. This one is mixed from the panel itself, so it follows a theme that retunes the panel. Tune it with `--theme-panel-field`.",
1317
1317
  "pairsWith": [
1318
1318
  "panel-field-hover"
1319
1319
  ]
@@ -17085,7 +17085,7 @@
17085
17085
  },
17086
17086
  {
17087
17087
  "label": "Controlled",
17088
- "code": "const [themeId, setThemeId] = useState<ThemeId>(\"base\")\n<ThemeSelector value={themeId} onValueChange={setThemeId} />",
17088
+ "code": "const [themeId, setThemeId] = useState<ThemeId>(\"sable\")\n<ThemeSelector value={themeId} onValueChange={setThemeId} />",
17089
17089
  "imports": "import { ThemeSelector, type ThemeId } from \"@sableui/react\""
17090
17090
  },
17091
17091
  {
@@ -15,7 +15,7 @@ try {
15
15
  const action = modes[0]
16
16
  if (action === "--template") {
17
17
  const manifest = JSON.parse(readFileSync(resolve(packageRoot, "package.json"), "utf8"))
18
- const css = readFileSync(resolve(packageRoot, "dist/themes/base/base.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "")
18
+ const css = readFileSync(resolve(packageRoot, "dist/themes/sable/sable.css"), "utf8").replace(/\/\*[\s\S]*?\*\//g, "")
19
19
  const groups = { light: {}, dark: {} }
20
20
  for (const match of css.matchAll(/([^{}]+)\{([^{}]*)\}/g)) {
21
21
  const target = match[1].includes(".dark") ? groups.dark : groups.light
package/scripts/grade.mjs CHANGED
@@ -33,7 +33,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"
33
33
  import { createHash } from "node:crypto"
34
34
  import { dirname, extname, join, relative, resolve } from "node:path"
35
35
  import { fileURLToPath } from "node:url"
36
- import { lintText, makeRules } from "./lint-rules.mjs"
36
+ import { ignoreGlob, lintText, makeRules } from "./lint-rules.mjs"
37
37
  import { skeletonize } from "./skeleton.mjs"
38
38
  import { assessReview, reviewTemplate } from "./review-evidence.mjs"
39
39
 
@@ -44,6 +44,7 @@ const CWD = process.cwd()
44
44
  const argv = process.argv.slice(2)
45
45
  const flags = new Map()
46
46
  const targets = []
47
+ const ignores = []
47
48
  const BOOLEAN_FLAGS = new Set(["json", "help", "template", "strict"])
48
49
  const VALUE_FLAGS = new Set(["cli-log", "review", "icons"])
49
50
  const JSON_OUT = argv.includes("--json")
@@ -55,7 +56,11 @@ function abort(message) {
55
56
  for (let i = 0; i < argv.length; i++) {
56
57
  const a = argv[i]
57
58
  if (!a.startsWith("--")) targets.push(a)
58
- else if (BOOLEAN_FLAGS.has(a.slice(2))) flags.set(a.slice(2), true)
59
+ else if (a === "--ignore" || a.startsWith("--ignore=")) {
60
+ const glob = a === "--ignore" ? argv[++i] : a.slice(9)
61
+ if (!glob || glob.startsWith("--")) abort("--ignore requires a glob.")
62
+ ignores.push(glob)
63
+ } else if (BOOLEAN_FLAGS.has(a.slice(2))) flags.set(a.slice(2), true)
59
64
  else if (VALUE_FLAGS.has(a.slice(2))) {
60
65
  if (!argv[i + 1] || argv[i + 1].startsWith("--")) abort(`${a} requires a file path.`)
61
66
  flags.set(a.slice(2), argv[++i])
@@ -72,13 +77,14 @@ if (flags.has("help") || !targets.length) {
72
77
  [
73
78
  "grade — check the static contract and recorded review evidence. No model calls.",
74
79
  "",
75
- " sable grade <dir...> [--json] [--review <review.json>] [--icons <icons.json>] [--strict]",
80
+ " sable grade <dir...> [--json] [--review <review.json>] [--icons <icons.json>] [--ignore <glob>] [--strict]",
76
81
  "",
77
82
  " --json machine-readable scorecard",
78
83
  " --cli-log a SABLE_CLI_LOG jsonl, to report whether the CLI was used at all",
79
84
  " --template print a review JSON template; fill after inspecting the rendered build",
80
85
  " --review validate evidence files and observations; paths are relative to this JSON",
81
86
  ' --icons icon names the app adds with composeIconSets: { "names": [...] }. Default: .sable/icons.json',
87
+ " --ignore skip matching paths in the checks, as sable-lint --ignore (repeatable)",
82
88
  " --strict exit 1 for static findings or a missing/incomplete/unresolved review",
83
89
  "",
84
90
  "Checks: design-contract violations · hallucinated components and props ·",
@@ -194,7 +200,13 @@ for (const t of targets) {
194
200
  else snapshotFiles.push(abs)
195
201
  }
196
202
  snapshotFiles.splice(0, snapshotFiles.length, ...new Set(snapshotFiles.sort()))
197
- const files = snapshotFiles.filter((file) => CODE.test(file))
203
+ /* --ignore takes the same globs sable-lint does, so the grade judges the same
204
+ files the lint does. A data layer (palette fixtures, document colours) is
205
+ skipped by the checks but still fingerprinted: it is part of the build. */
206
+ const ignoreRes = ignores.map(ignoreGlob)
207
+ const isIgnored = (file) => ignoreRes.some((re) => re.test(relative(CWD, file).replaceAll("\\", "/")))
208
+ const ignoredFiles = snapshotFiles.filter((file) => CODE.test(file) && isIgnored(file))
209
+ const files = snapshotFiles.filter((file) => CODE.test(file) && !isIgnored(file))
198
210
  if (!files.length) abort("No source files found.")
199
211
  if (!files.some((file) => readFileSync(file, "utf8").trim().length > 0)) abort("No non-empty source content found.")
200
212
  // Git checkouts may use CRLF on Windows and LF in CI. Normalize only known text
@@ -463,6 +475,7 @@ const result = {
463
475
  sourceHash,
464
476
  fingerprint: { algorithm: "sha256", files: snapshotFiles.length, packageHash, coverage: "All non-test files in supplied targets, plus Sable runtime sources, registry, and lint contract. Known text formats normalize CRLF to LF; binary assets retain their bytes. Pass public/ for external asset directories; remote resources are not covered." },
465
477
  files: files.length,
478
+ ignored: { globs: ignores, files: ignoredFiles.length },
466
479
  loc,
467
480
  score,
468
481
  scoreMeaning: "Static contract only. Pattern similarity does not affect this score; visual quality is not scored.",
@@ -485,7 +498,7 @@ if (JSON_OUT) {
485
498
 
486
499
  const L = (s = "") => process.stdout.write(`${s}\n`)
487
500
  L()
488
- L(` grade — ${files.length} files, ${loc} lines, against @sableui/react ${REGISTRY.version}`)
501
+ L(` grade — ${files.length} files, ${loc} lines, against @sableui/react ${REGISTRY.version}${ignoredFiles.length ? ` (${ignoredFiles.length} ignored)` : ""}`)
489
502
  L(` ${"─".repeat(66)}`)
490
503
  const row = (label, n, note = "") =>
491
504
  L(` ${label.padEnd(26)}${String(n).padStart(4)}${note ? ` ${note}` : ""}`)
@@ -537,10 +537,14 @@ export function makeRules({ semanticTokens, motionTokens }) {
537
537
  since: "0.1.0",
538
538
  code: true,
539
539
  why:
540
- "A colour hand-mixed from the foreground ink — `color-mix(… var(--foreground) …)`. Sable already derives these from the surface underneath, in every theme and mode: `sable-well` (a recessed track, pad or well), `sable-surface` + `sable-surface-interactive` (hover and selected fills on any surface), `bg-hairline` / `border-hairline` (a faint ink edge or fill). `npx sable search` finds them; `registry.json → utilities` explains each. If none fits, it is a gap — report it, then take the escape hatch with the reason.",
540
+ "A colour hand-mixed from the foreground ink — `color-mix(… var(--foreground) …)`. Sable already derives these from the surface underneath, in every theme and mode: `sable-well` (a recessed track, pad or well), `sable-surface` + `sable-surface-interactive` (hover and selected fills on any surface), `bg-hairline` / `border-hairline` (a faint ink edge or fill). `npx sable search` finds them; `registry.json → utilities` explains each. If none fits, it is a gap — report it, then take the escape hatch with the reason. " +
541
+ "Exempt: the value of a `--theme-*` hook (`--theme-panel-field: color-mix(…)`), which is how a theme tunes a derived token.",
541
542
  find(line) {
542
543
  const out = []
543
544
  for (const m of line.matchAll(/color-mix\([^;"'`]*?var\(--foreground\)/g)) {
545
+ // A theme hook is the sanctioned place to retune a derived surface,
546
+ // in CSS or as an arbitrary property ([--theme-x:color-mix(…)]).
547
+ if (/--theme-[\w-]+\s*:\s*$/.test(line.slice(0, m.index))) continue
544
548
  out.push({ match: m[0].length > 60 ? `${m[0].slice(0, 57)}…` : m[0] })
545
549
  }
546
550
  return out
@@ -964,6 +968,21 @@ function stripComments(src) {
964
968
  .replace(/(^|[^:])\/\/[^\n]*/gm, (m, p) => p + " ".repeat(m.length - p.length))
965
969
  }
966
970
 
971
+ /**
972
+ * An `--ignore` glob as a RegExp over a CWD-relative, forward-slash path:
973
+ * `**` crosses directories, `*` stays within one. Shared by sable-lint and
974
+ * sable grade so the same flag skips the same files in both.
975
+ */
976
+ export function ignoreGlob(glob) {
977
+ return new RegExp(
978
+ `^${glob
979
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
980
+ .replace(/\*\*/g, "\0")
981
+ .replace(/\*/g, "[^/]*")
982
+ .replace(/\0/g, ".*")}$`,
983
+ )
984
+ }
985
+
967
986
  export function lintText(text, rules, ctx) {
968
987
  const lines = text.split(/\r?\n/)
969
988
  const codeLines = stripComments(text).split(/\r?\n/)
@@ -1,9 +0,0 @@
1
- import { T as ThemeManifest } from '../../_contract-BlKMR4eX.js';
2
-
3
- /**
4
- * Base — the default Sable theme. Concrete token values live in
5
- * `base.css`; this manifest is the typed, machine-readable description.
6
- */
7
- declare const baseTheme: ThemeManifest;
8
-
9
- export { baseTheme, baseTheme as default };
@@ -1 +0,0 @@
1
- export { baseTheme, base_default as default } from '../../chunk-EP55KSYA.js';