@spaethtech/svelte-ui 0.16.0 → 0.16.1-dev.76.a0169fa

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.
@@ -22,7 +22,7 @@ per-component `--dt-*`/`--card-*` namespaces have all been removed — don't rei
22
22
  ### Palette — `--ui-color-*`
23
23
 
24
24
  ```
25
- --ui-color-background --ui-color-text
25
+ --ui-color-background --ui-color-text --ui-color-neutral
26
26
  --ui-color-primary --ui-color-secondary
27
27
  --ui-color-success --ui-color-warning
28
28
  --ui-color-error --ui-color-info
@@ -30,14 +30,23 @@ per-component `--dt-*`/`--card-*` namespaces have all been removed — don't rei
30
30
 
31
31
  These are the only palette colours a component references. The `variant` axis
32
32
  (`types/variants.ts` → `variantToken`) maps each `Variant` to one of these (`danger`→`error`; the two
33
- non-accent variants `neutral` and `ghost` → `--ui-color-text`). Canonical order:
33
+ non-accent variants `neutral` and `ghost` → **`--ui-color-neutral`**). Canonical order:
34
34
  `neutral · primary · secondary · success · info · warning · danger · ghost`.
35
35
 
36
+ **`neutral` is the default/undefined variant** — every sizeable/themeable component defaults to
37
+ `variant="neutral"`, so out of the box the whole library is **monochrome**; a colour appears only when
38
+ you set an explicit `variant`. **`--ui-color-neutral`** is the themeable neutral base: it **defaults to
39
+ `var(--ui-color-text)`** (so it flips opposite `--ui-color-background` per theme, and a border mixed
40
+ toward `transparent` always reads against the background), but a theme can override it **independently
41
+ of body text** to recolour every neutral border/surface at once. Every neutral border/divider — and
42
+ `--ui-border-color` — derives from it, so it's the single knob for "the colour of all neutral borders".
43
+
36
44
  - `ghost` — borderless, transparent, hover-tint only (the minimal control). For the box-shaped
37
45
  **fields** (Input/Select/TextArea) this also drives the **field background**: `ghost` → transparent,
38
46
  every other variant → `--ui-color-background` (so a field reads as a field on any surface).
39
- - `neutral` — the _uncoloured_ variant (mixes from `--ui-color-text`): **solid** in solid contexts
40
- (Button/Toggle/Banner → black-on-white / white-on-black), a **tint** in soft contexts (Badge/Alert).
47
+ - `neutral` — the _uncoloured_, **default** variant (mixes from `--ui-color-neutral`, which defaults to
48
+ `--ui-color-text`): **solid** in solid contexts (Button/Toggle/Banner → black-on-white /
49
+ white-on-black), a **tint** in soft/bordered contexts (Badge/Alert/Input/Card frame).
41
50
  - (`plain` was removed — it's now `ghost`. Old bordered-`ghost` → `neutral`.)
42
51
 
43
52
  Field **help/error text** uses no new token: the muted `description` row is an inline
@@ -68,7 +77,7 @@ variant token at shared strengths, so every tinted surface is calculated identic
68
77
  ```
69
78
  --ui-tint-soft 12% soft resting fill (Badge fill, Alert `filled`)
70
79
  --ui-tint-divider 30% soft internal divider (CardHeader/CardFooter separators; the default
71
- secondary divider is `--ui-border-color`)
80
+ NEUTRAL divider is `--ui-border-color`)
72
81
  --ui-tint-border 50% bordered surface, REST (Badge/Alert/Banner ring · Card/DataTable/Input/
73
82
  TextArea frame · Button outline)
74
83
  --ui-tint-border-hover 80% bordered field, HOVER (Input/TextArea/Card/DataTable/Button — the
@@ -77,14 +86,16 @@ variant token at shared strengths, so every tinted surface is calculated identic
77
86
  ```
78
87
 
79
88
  e.g. `color-mix(in srgb, var(<variant-token>) var(--ui-tint-soft), transparent)`. A `primary` badge is
80
- primary@these; a `neutral` one is text@these. Theme-independent (a theme MAY override them to tune the
81
- whole tint language). **Every bordered surface uses ONE calc** — `--ui-tint-border` at rest,
82
- `--ui-tint-border-hover` on hover/focus, from the variant token — with **no `secondary` special case**.
83
- Internal **dividers** (Card header/footer separators) use the softer `--ui-tint-divider` (30%) tier,
84
- likewise one calc per variant — so a divider always reads lighter than the frame at any variant.
85
- `--ui-border-color-strong`/`-strong-hover` have been **REMOVED** (migrate any consumer use to
86
- `color-mix(in srgb, var(--ui-color-secondary) var(--ui-tint-border), transparent)`, hover
87
- `--ui-tint-border-hover`). (Distinct from the interactive tints above, which mix from `--ui-accent`.)
89
+ primary@these; a `neutral` (default) one is **`--ui-color-neutral`**@these. Theme-independent (a theme
90
+ MAY override them to tune the whole tint language). **Every bordered surface uses ONE calc** —
91
+ `--ui-tint-border` at rest, `--ui-tint-border-hover` on hover/focus, from the variant token — with **no
92
+ `secondary` special case**. A default (neutral) frame is therefore `--ui-color-neutral` @ these; a
93
+ default divider is `--ui-color-neutral` @ `--ui-tint-divider` (= `--ui-border-color`). Internal
94
+ **dividers** use the softer `--ui-tint-divider` (30%) tier, likewise one calc per variant — so a
95
+ divider always reads lighter than the frame at any variant. `--ui-border-color-strong`/`-strong-hover`
96
+ have been **REMOVED** (migrate to `color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-border),
97
+ transparent)`, hover → `--ui-tint-border-hover`). (Distinct from the interactive tints above, which mix
98
+ from `--ui-accent`.)
88
99
 
89
100
  ### The `borderless` prop (border-colour swap, never a border-width change)
90
101
 
@@ -107,11 +118,21 @@ NOT take `borderless`.
107
118
  ```
108
119
  --ui-border-radius corner radius — ONE fixed value for every rectangular surface; NOT
109
120
  scaled by `size`, no -sm/-lg (pills use rounded-full)
110
- --ui-border-color soft internal divider (row separators, section splits)
121
+ --ui-border-color soft internal divider (row separators, section splits) — NEUTRAL:
122
+ `--ui-color-neutral` @ `--ui-tint-divider` (30%). Repeated per theme
123
+ scope so it re-resolves per theme + survives @theme tree-shaking.
111
124
  (--ui-border-color-strong/-strong-hover were REMOVED — use the
112
125
  `--ui-tint-border` / `--ui-tint-border-hover` scheme instead)
126
+ --ui-color-neutral the neutral/undefined-variant base colour; defaults to
127
+ `var(--ui-color-text)`. Override to recolour ALL neutral borders +
128
+ surfaces at once, independent of body text + the variant colours.
113
129
  ```
114
130
 
131
+ **One hue, two weights.** All *neutral* borders share the `--ui-color-neutral` hue; only the tint
132
+ weight differs by role — **divider** `--ui-tint-divider` (30%, `--ui-border-color`) vs **frame**
133
+ `--ui-tint-border` (50%). A `variant`-set border tints from that variant's token instead (a `secondary`
134
+ Card = cyan border) — that's intended; the neutral default is what's monochrome.
135
+
115
136
  ### Density / text / icon — the `size` axis
116
137
 
117
138
  ```
package/CLAUDE.md CHANGED
@@ -160,8 +160,11 @@ with its clear/help actions as `<Button>`. A raw interactive element inside a co
160
160
 
161
161
  Two shared **cross-cutting axes** every sizeable/themeable component consumes (mirror each other):
162
162
 
163
- - **`variant`** (`types/variants.ts`) — color/style: `primary` `secondary` `danger` `info` `success`
164
- `warning` `ghost` `plain`. `plain` = borderless/transparent, for dense icon controls.
163
+ - **`variant`** (`types/variants.ts`) — color/style: `neutral` `primary` `secondary` `danger` `info`
164
+ `success` `warning` `ghost`. **`neutral` is the default** (undefined variant) — components are
165
+ monochrome out of the box, colour only on an explicit variant. `neutral`/`ghost` mix from
166
+ `--ui-color-neutral` (defaults to `--ui-color-text`, independently themeable). `ghost` =
167
+ borderless/transparent, for dense icon controls.
165
168
  - **`size`** (`types/sizes.ts`) — `sm` (24px) · `md` (32px, **default**) · `lg` (40px), tied to
166
169
  `--ui-height-sm` / `--ui-height` / `--ui-height-lg` (`--ui-height-touch` 44px is a separate token
167
170
  for explicit touch targets, not `lg`). Each component maps `size` to its height/text/padding/icon,
@@ -226,7 +229,7 @@ interface Props {
226
229
  size?: Size;
227
230
  children?: Snippet;
228
231
  }
229
- let { value = "", variant = "secondary", size = "md", children }: Props = $props();
232
+ let { value = "", variant = "neutral", size = "md", children }: Props = $props();
230
233
  ```
231
234
 
232
235
  ## Known Issues (do not "fix" in code)
@@ -24,7 +24,7 @@
24
24
  alt,
25
25
  icon,
26
26
  size = "md",
27
- variant = "secondary",
27
+ variant = "neutral",
28
28
  shape = "circle",
29
29
  status,
30
30
  class: cls = "",
@@ -30,7 +30,7 @@
30
30
  max,
31
31
  size = "md",
32
32
  shape = "circle",
33
- variant = "secondary",
33
+ variant = "neutral",
34
34
  class: cls = "",
35
35
  children,
36
36
  }: {
@@ -45,7 +45,7 @@
45
45
  let {
46
46
  icon,
47
47
  text,
48
- variant = "secondary",
48
+ variant = "neutral",
49
49
  size = "md",
50
50
  loading = false,
51
51
  borderless = true,
@@ -23,7 +23,7 @@
23
23
  import { variantToken, type Variant } from "../types/variants.js";
24
24
 
25
25
  let {
26
- variant = "secondary",
26
+ variant = "neutral",
27
27
  size = "md",
28
28
  borderless = false,
29
29
  class: cls = "",
@@ -14,7 +14,7 @@
14
14
  import { variantToken, type Variant } from "../types/variants.js";
15
15
 
16
16
  let {
17
- variant = "secondary",
17
+ variant = "neutral",
18
18
  size = "md",
19
19
  separator = true,
20
20
  class: cls = "",
@@ -14,7 +14,7 @@
14
14
  import { variantToken, type Variant } from "../types/variants.js";
15
15
 
16
16
  let {
17
- variant = "secondary",
17
+ variant = "neutral",
18
18
  size = "md",
19
19
  separator = true,
20
20
  class: cls = "",
@@ -19,7 +19,7 @@
19
19
  let {
20
20
  text,
21
21
  icon,
22
- variant = "secondary",
22
+ variant = "neutral",
23
23
  size = "md",
24
24
  selected = false,
25
25
  removable = false,
@@ -23,7 +23,7 @@
23
23
  debounceMs = 200,
24
24
  maxResults,
25
25
  onselect,
26
- variant = "secondary",
26
+ variant = "neutral",
27
27
  size = "md",
28
28
  disabled = false,
29
29
  placeholder,
@@ -218,7 +218,7 @@
218
218
  // (If a header-less / footer-less mode is ever added, flip that edge to `bodyBorder` + round the corner.)
219
219
  // `ghost` makes every section transparent (bands AND body) for a bare, see-through table.
220
220
  const sectionBorder =
221
- "color-mix(in srgb, var(--ui-color-secondary) var(--ui-tint-border), transparent)";
221
+ "color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-border), transparent)";
222
222
  const bandBg = $derived(variant === "ghost" ? "transparent" : "var(--ui-color-surface)");
223
223
  const bodyBg = $derived(variant === "ghost" ? "transparent" : "var(--ui-color-background)");
224
224
  const bandBorder = $derived(borderless ? bandBg : sectionBorder); // header + footer, all 4 sides
@@ -216,7 +216,7 @@
216
216
 
217
217
  <Popup anchor={anchorEl} bind:open offset={6}>
218
218
  <div
219
- class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
219
+ class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
220
220
  style="--accent: {accent}"
221
221
  >
222
222
  {#if range}
@@ -17,7 +17,7 @@
17
17
  description,
18
18
  icon,
19
19
  action,
20
- variant = "secondary",
20
+ variant = "neutral",
21
21
  size = "md",
22
22
  class: cls = "",
23
23
  children,
@@ -52,7 +52,7 @@
52
52
  actions,
53
53
  iconPosition = "right",
54
54
  size = "md",
55
- variant = "secondary",
55
+ variant = "neutral",
56
56
  borderless = false,
57
57
  iconClickable = false,
58
58
  onIconClick,
@@ -502,8 +502,8 @@
502
502
  <div
503
503
  bind:this={listElement}
504
504
  class="ui-accent border {disabled
505
- ? '[background-color:color-mix(in_srgb,var(--ui-color-text)_5%,transparent)] [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] cursor-not-allowed'
506
- : '[background-color:var(--ui-color-background)] [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border-hover),transparent)] focus:[border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border-hover),transparent)] focus:[outline:2px_solid_color-mix(in_srgb,currentColor_70%,transparent)] focus:[outline-offset:0px]'} rounded-md overflow-y-scroll custom-list {dropdownClass}"
505
+ ? '[background-color:color-mix(in_srgb,var(--ui-color-text)_5%,transparent)] [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] cursor-not-allowed'
506
+ : '[background-color:var(--ui-color-background)] [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] hover:[border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border-hover),transparent)] focus:[border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border-hover),transparent)] focus:[outline:2px_solid_color-mix(in_srgb,currentColor_70%,transparent)] focus:[outline-offset:0px]'} rounded-md overflow-y-scroll custom-list {dropdownClass}"
507
507
  style="{accentVar} height: {maxHeight()}px; scrollbar-width: none; -ms-overflow-style: none; overscroll-behavior: contain;"
508
508
  onscroll={handleScroll}
509
509
  onkeydown={handleListKeydown}
@@ -90,12 +90,12 @@
90
90
  role="menu"
91
91
  tabindex="0"
92
92
  {onkeydown}
93
- class="ui-accent min-w-48 rounded-md border p-1 shadow-lg outline-none [background-color:var(--ui-color-background)] [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)]"
93
+ class="ui-accent min-w-48 rounded-md border p-1 shadow-lg outline-none [background-color:var(--ui-color-background)] [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)]"
94
94
  style={accentVar}
95
95
  >
96
96
  {#each items as item, i (i)}
97
97
  {#if isSeparator(item)}
98
- <li role="separator" class="my-1 h-px [background-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)]"></li>
98
+ <li role="separator" class="my-1 h-px [background-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)]"></li>
99
99
  {:else}
100
100
  <li role="none">
101
101
  <button
@@ -16,7 +16,7 @@
16
16
  placeholder = "Add your notes...",
17
17
  onSave,
18
18
  size = "md",
19
- variant = "secondary",
19
+ variant = "neutral",
20
20
  copy,
21
21
  className = "",
22
22
  }: {
@@ -78,7 +78,7 @@
78
78
  // Band surface + border: variant re-points --ui-accent so --ui-color-surface/hover re-mix from it
79
79
  // (the shared .ui-accent mechanism DataTable's footer uses). `ghost` ⇒ transparent band.
80
80
  const accentVar = $derived(variant ? `--ui-accent: var(${variantToken[variant]});` : "");
81
- const sectionBorder = "color-mix(in srgb, var(--ui-color-secondary) var(--ui-tint-border), transparent)";
81
+ const sectionBorder = "color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-border), transparent)";
82
82
  const bandBg = $derived(variant === "ghost" ? "transparent" : "var(--ui-color-surface)");
83
83
  const bandBorder = $derived(borderless ? "transparent" : sectionBorder);
84
84
 
@@ -24,7 +24,7 @@
24
24
  dataset,
25
25
  placeholder = "Filter… e.g. $status == active && $price |= [20..50)",
26
26
  size = "md",
27
- variant = "secondary",
27
+ variant = "neutral",
28
28
  borderless = false,
29
29
  }: {
30
30
  dataset: DataSet;
@@ -320,8 +320,8 @@
320
320
  const dockEdge = $derived(dockRight ? "right-0" : "left-0");
321
321
  const dockBorder = $derived(
322
322
  dockRight
323
- ? "[border-left:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]"
324
- : "[border-right:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]",
323
+ ? "[border-left:1px_solid_var(--ui-border-color)]"
324
+ : "[border-right:1px_solid_var(--ui-border-color)]",
325
325
  );
326
326
 
327
327
  // ── Icon-mode hover-expand ──────────────────────────────────────
@@ -1083,7 +1083,7 @@
1083
1083
  data-sbm-popout=""
1084
1084
  data-open={isOpen}
1085
1085
  style="width: max-content;{expandedWidth != null ? ` max-width: ${expandedWidth}px;` : ''} max-height: {maxHeight}; --sbm-accent: {accentVar}; --sbm-lbl: inline; --sbm-affix: inline-flex;{bd.nudge ? ` transform: translateY(${bd.nudge}px);` : ''}"
1086
- class="overflow-hidden shadow-lg [background-color:var(--ui-color-background)] [color:var(--ui-color-text)] [border-color:color-mix(in_srgb,var(--ui-color-text)_15%,transparent)] {bd.cls}"
1086
+ class="overflow-hidden shadow-lg [background-color:var(--ui-color-background)] [color:var(--ui-color-text)] [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] {bd.cls}"
1087
1087
  onmouseenter={popoutOnHover ? cancelClose : undefined}
1088
1088
  onmouseleave={popoutOnHover ? scheduleClose : undefined}
1089
1089
  onwheel={item.popout
@@ -1136,7 +1136,7 @@
1136
1136
  data-sbm-burger={uid}
1137
1137
  class="fixed top-3 {dockRight
1138
1138
  ? 'right-3'
1139
- : 'left-3'} z-[80] w-10 h-10 items-center justify-center cursor-pointer [background-color:var(--ui-color-background)] border [border-color:color-mix(in_srgb,var(--ui-color-text)_15%,transparent)] shadow-md [&_svg]:w-5 [&_svg]:h-5"
1139
+ : 'left-3'} z-[80] w-10 h-10 items-center justify-center cursor-pointer [background-color:var(--ui-color-background)] border [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] shadow-md [&_svg]:w-5 [&_svg]:h-5"
1140
1140
  aria-label={open ? "Close menu" : "Open menu"}
1141
1141
  aria-expanded={open}
1142
1142
  onclick={() => (open = !open)}
@@ -21,7 +21,7 @@
21
21
  deltaLabel,
22
22
  invertDelta = false,
23
23
  icon,
24
- variant = "secondary",
24
+ variant = "neutral",
25
25
  size = "md",
26
26
  class: cls = "",
27
27
  }: {
@@ -99,7 +99,7 @@
99
99
  maxRows,
100
100
  lineHeightRem,
101
101
  size = "md",
102
- variant = "secondary",
102
+ variant = "neutral",
103
103
  borderless = false,
104
104
  required = false,
105
105
  label = null,
@@ -124,7 +124,7 @@
124
124
 
125
125
  <Popup anchor={anchorEl} bind:open offset={6}>
126
126
  <div
127
- class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
127
+ class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
128
128
  style="--accent: {accent}"
129
129
  >
130
130
  <div class="grid place-items-center py-1">
@@ -149,7 +149,7 @@
149
149
 
150
150
  <Popup anchor={anchorEl} bind:open offset={6}>
151
151
  <div
152
- class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-secondary)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
152
+ class="rounded-[var(--ui-border-radius)] border {pad} shadow-lg [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] [background-color:var(--ui-color-background)] [color:var(--ui-color-text)]"
153
153
  style="--accent: {accent}"
154
154
  >
155
155
  <div class="flex flex-col gap-2">
@@ -19,7 +19,7 @@
19
19
  values = $bindable([]),
20
20
  allowDuplicates = false,
21
21
  max,
22
- variant = "secondary",
22
+ variant = "neutral",
23
23
  size = "md",
24
24
  disabled = false,
25
25
  placeholder,
package/dist/theme.css CHANGED
@@ -67,10 +67,11 @@
67
67
  * Fixed, NOT scaled by `size`, so different-size controls share a
68
68
  * corner. (Pills like Toggle/Badge use rounded-full; the tiny
69
69
  * Checkbox box keeps its own small radius — not worth a token.)
70
- * --ui-border-color soft internal divider (row separators, section splits) —
71
- * the default (secondary) divider, mixed at `--ui-tint-divider` */
70
+ * --ui-border-color soft internal divider (row separators, section splits) — the
71
+ * NEUTRAL divider (`--ui-color-neutral` @ `--ui-tint-divider`).
72
+ * Defined in the token scopes below (not here) so it re-resolves per
73
+ * theme and isn't tree-shaken out of @theme. */
72
74
  --ui-border-radius: 0.375rem;
73
- --ui-border-color: color-mix(in srgb, var(--ui-color-secondary) var(--ui-tint-divider), transparent);
74
75
  }
75
76
 
76
77
  /* One interactive-surface language, library-wide. A subtle tint of the ACCENT colour, so it lightens on
@@ -98,6 +99,14 @@
98
99
  :root {
99
100
  /* light default — no theme class + light system */
100
101
  --ui-accent: var(--ui-color-text);
102
+ /* Neutral = the undefined-variant / monochrome default colour. Defaults to the TEXT colour (so it
103
+ * flips opposite --ui-color-background per theme — dark text on light, light text on dark — and a
104
+ * border mixed toward `transparent` always reads against the background). Independently themeable:
105
+ * override --ui-color-neutral to recolour EVERY neutral border/divider (and the `neutral` variant)
106
+ * without touching body text or the accent colours. Repeated per theme scope (like --ui-accent) so
107
+ * it re-resolves per theme and survives Tailwind's @theme tree-shaking. */
108
+ --ui-color-neutral: var(--ui-color-text);
109
+ --ui-border-color: color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-divider), transparent);
101
110
  --ui-color-hover: color-mix(in srgb, var(--ui-accent) 4%, transparent);
102
111
  --ui-color-surface: color-mix(in srgb, var(--ui-accent) 8%, transparent);
103
112
  --ui-color-active: color-mix(in srgb, var(--ui-accent) 12%, transparent);
@@ -151,6 +160,8 @@ body {
151
160
  --ui-color-error: #ef4444;
152
161
  --ui-color-info: #06b6d4;
153
162
  --ui-accent: var(--ui-color-text);
163
+ --ui-color-neutral: var(--ui-color-text);
164
+ --ui-border-color: color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-divider), transparent);
154
165
  --ui-color-hover: color-mix(in srgb, var(--ui-accent) 4%, transparent);
155
166
  --ui-color-surface: color-mix(in srgb, var(--ui-accent) 8%, transparent);
156
167
  --ui-color-active: color-mix(in srgb, var(--ui-accent) 12%, transparent);
@@ -169,6 +180,8 @@ body {
169
180
  --ui-color-info: #0ea5e9;
170
181
  /* forced light must re-assert the tint (overrides the dark @media above) */
171
182
  --ui-accent: var(--ui-color-text);
183
+ --ui-color-neutral: var(--ui-color-text);
184
+ --ui-border-color: color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-divider), transparent);
172
185
  --ui-color-hover: color-mix(in srgb, var(--ui-accent) 4%, transparent);
173
186
  --ui-color-surface: color-mix(in srgb, var(--ui-accent) 8%, transparent);
174
187
  --ui-color-active: color-mix(in srgb, var(--ui-accent) 12%, transparent);
@@ -185,6 +198,8 @@ body {
185
198
  --ui-color-error: #ef4444;
186
199
  --ui-color-info: #06b6d4;
187
200
  --ui-accent: var(--ui-color-text);
201
+ --ui-color-neutral: var(--ui-color-text);
202
+ --ui-border-color: color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-divider), transparent);
188
203
  --ui-color-hover: color-mix(in srgb, var(--ui-accent) 4%, transparent);
189
204
  --ui-color-surface: color-mix(in srgb, var(--ui-accent) 8%, transparent);
190
205
  --ui-color-active: color-mix(in srgb, var(--ui-accent) 12%, transparent);
@@ -1,8 +1,9 @@
1
1
  export type Variant = "neutral" | "primary" | "secondary" | "success" | "info" | "warning" | "danger" | "ghost";
2
2
  /** Variant → semantic colour token, used where a variant needs an ACCENT colour (Toggle track,
3
3
  * Checkbox, Card border, DataTable accent…). `danger` maps to `--ui-color-error`. The two non-accent
4
- * variants — `ghost` (transparent, hover-tint only) and `neutral` (text-mix surface tint, = the
5
- * default DataTable look) — resolve to `--ui-color-text`, so those controls read grey/uncoloured
6
- * rather than borrowing an accent. The interactive tints (hover/surface/active) are text-mixes by
7
- * default and re-mix off whichever colour this points at. */
4
+ * variants — `neutral` (the undefined-variant / monochrome default) and `ghost` (transparent,
5
+ * hover-tint only) — resolve to `--ui-color-neutral`, which itself defaults to `--ui-color-text` but
6
+ * is independently themeable (see theme.css). So neutral/ghost controls read monochrome, and a theme
7
+ * can recolour every neutral border/surface via one token without moving body text. The interactive
8
+ * tints (hover/surface/active) re-mix off whichever colour this points at. */
8
9
  export declare const variantToken: Record<Variant, string>;
@@ -1,16 +1,17 @@
1
1
  /** Variant → semantic colour token, used where a variant needs an ACCENT colour (Toggle track,
2
2
  * Checkbox, Card border, DataTable accent…). `danger` maps to `--ui-color-error`. The two non-accent
3
- * variants — `ghost` (transparent, hover-tint only) and `neutral` (text-mix surface tint, = the
4
- * default DataTable look) — resolve to `--ui-color-text`, so those controls read grey/uncoloured
5
- * rather than borrowing an accent. The interactive tints (hover/surface/active) are text-mixes by
6
- * default and re-mix off whichever colour this points at. */
3
+ * variants — `neutral` (the undefined-variant / monochrome default) and `ghost` (transparent,
4
+ * hover-tint only) — resolve to `--ui-color-neutral`, which itself defaults to `--ui-color-text` but
5
+ * is independently themeable (see theme.css). So neutral/ghost controls read monochrome, and a theme
6
+ * can recolour every neutral border/surface via one token without moving body text. The interactive
7
+ * tints (hover/surface/active) re-mix off whichever colour this points at. */
7
8
  export const variantToken = {
8
- neutral: "--ui-color-text",
9
+ neutral: "--ui-color-neutral",
9
10
  primary: "--ui-color-primary",
10
11
  secondary: "--ui-color-secondary",
11
12
  success: "--ui-color-success",
12
13
  info: "--ui-color-info",
13
14
  warning: "--ui-color-warning",
14
15
  danger: "--ui-color-error",
15
- ghost: "--ui-color-text",
16
+ ghost: "--ui-color-neutral",
16
17
  };
package/docs/themes.md CHANGED
@@ -53,12 +53,17 @@ theme-independent** (identical in light and dark) — only the accent color flip
53
53
  ## Border & radius — `--ui-border-*`
54
54
 
55
55
  - `--ui-border-radius` — `0.375rem` — corner radius for every surface (Card, Input, DataTable…)
56
- - `--ui-border-color` — `color-mix(in srgb, var(--ui-color-secondary) 30%, transparent)`soft
57
- internal divider (row separators, section splits)
58
- - `--ui-border-color-strong` `var(--ui-color-secondary)` prominent outer edge (Card frame,
59
- Input, DataTable outer)
60
- - `--ui-border-color-strong-hover` `color-mix(in srgb, var(--ui-color-secondary) 80%, black)`
61
- darker edge on hover for interactive controls
56
+ - `--ui-color-neutral` — `var(--ui-color-text)` (default) — the neutral / undefined-variant base
57
+ colour. **Override it to recolour every neutral border + surface at once**, independent of body text
58
+ and the variant colours. All neutral borders derive from this one hue.
59
+ - `--ui-border-color` — `color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-divider), transparent)`
60
+ the soft internal **neutral divider** (row separators, section splits). One hue, two weights: a
61
+ **frame** border is the same `--ui-color-neutral` at `--ui-tint-border` (50%) instead of the 30%
62
+ divider tier. A `variant`-set border tints from that variant's token (a `secondary` Card = cyan) —
63
+ only the *neutral* default is monochrome.
64
+ - (`--ui-border-color-strong` / `-strong-hover` were **removed** — use
65
+ `color-mix(in srgb, var(--ui-color-neutral) var(--ui-tint-border), transparent)` for a frame edge,
66
+ `--ui-tint-border-hover` for its hover.)
62
67
 
63
68
  ## Sizing — the `size` axis (`sm` / `md` / `lg`)
64
69
 
@@ -126,9 +131,9 @@ Components reference these via Tailwind's arbitrary-value syntax:
126
131
  <!-- Text / background -->
127
132
  <div class="text-[var(--ui-color-text)] bg-[var(--ui-color-background)]">...</div>
128
133
 
129
- <!-- Border (color + radius tokens) -->
134
+ <!-- Border (a neutral frame edge — the 50% tier — + radius) -->
130
135
  <div
131
- class="border [border-color:var(--ui-border-color-strong)] [border-radius:var(--ui-border-radius)]"
136
+ class="border [border-color:color-mix(in_srgb,var(--ui-color-neutral)_var(--ui-tint-border),transparent)] [border-radius:var(--ui-border-radius)]"
132
137
  >
133
138
  ...
134
139
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.16.0",
3
+ "version": "0.16.1-dev.76.a0169fa",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"