@marianmeres/stuic 3.173.0 → 3.174.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.
package/AGENTS.md CHANGED
@@ -101,12 +101,27 @@ Global tokens that control cross-component visual properties. Defined in `src/li
101
101
  }
102
102
  ```
103
103
 
104
- **Element vs Container classification:**
104
+ **Element vs Button vs Container classification:**
105
105
 
106
106
  - **Element** (`--stuic-radius`): inputs, badges, list items, checkboxes, tabs — interactive controls
107
- - **Button** (`--stuic-radius-button`): buttons, button groups — allows rounded buttons even with flat elements
107
+ - **Button** (`--stuic-radius-button`): buttons, split buttons, button groups — allows rounded buttons even with flat elements
108
108
  - **Container** (`--stuic-radius-container`): cards, modals, dropdowns, notifications, accordions — content wrappers
109
109
 
110
+ **Tier tokens vs component tokens — the mirrored names.** Tier tokens (the table above) are
111
+ `--stuic-{property}-{tier}` and ARE declared in `src/lib/index.css`. Component tokens are the
112
+ transposition, `--stuic-{component}-{property}`, and are NEVER declared — they exist only as
113
+ the first argument of a `var()` fallback:
114
+
115
+ ```css
116
+ border-radius: var(--stuic-button-radius, var(--stuic-radius-button));
117
+ /* ^ component token (3) ^ tier token (2) */
118
+ ```
119
+
120
+ So `--stuic-radius-button` applies to Button, SplitButton and ButtonGroupRadio, while
121
+ `--stuic-button-radius` applies to Button alone. Same for `--stuic-border-width-button` vs
122
+ `--stuic-button-border-width`. `button` is the only tier name that is also a component name,
123
+ so it is the only confusable pair — do not "fix" one into the other.
124
+
110
125
  ---
111
126
 
112
127
  ## Before Making Changes
package/README.md CHANGED
@@ -57,7 +57,7 @@ STUIC uses a 4-layer CSS variable token system:
57
57
  ```
58
58
  Layer 1: Theme Tokens (--stuic-color-*)
59
59
 
60
- Layer 2: Structural Tokens (--stuic-radius, --stuic-shadow, --stuic-border-width, --stuic-transition)
60
+ Layer 2: Structural Tokens (--stuic-radius, --stuic-radius-button, --stuic-shadow, ...)
61
61
  ↓ (used as fallback defaults)
62
62
  Layer 3: Component Tokens (--stuic-button-radius, --stuic-input-accent, etc.)
63
63
  ↓ (Tailwind utility class references)
@@ -87,16 +87,20 @@ Override shared structural tokens to change the entire library's visual characte
87
87
  /* Brutalist — sharp, flat, borderless */
88
88
  :root {
89
89
  --stuic-radius: 0;
90
+ --stuic-radius-button: 0;
90
91
  --stuic-radius-container: 0;
91
92
  --stuic-shadow: none;
92
93
  --stuic-shadow-hover: none;
93
94
  --stuic-shadow-overlay: none;
94
95
  --stuic-shadow-dialog: none;
95
96
  --stuic-border-width: 0;
97
+ --stuic-border-width-button: 0;
96
98
  }
97
99
  ```
98
100
 
99
- Available tokens: `--stuic-radius`, `--stuic-radius-container`, `--stuic-shadow`, `--stuic-shadow-hover`, `--stuic-shadow-overlay`, `--stuic-shadow-dialog`, `--stuic-border-width`, `--stuic-transition`.
101
+ Available tokens: `--stuic-radius`, `--stuic-radius-button`, `--stuic-radius-container`, `--stuic-shadow`, `--stuic-shadow-hover`, `--stuic-shadow-overlay`, `--stuic-shadow-dialog`, `--stuic-border-width`, `--stuic-border-width-button`, `--stuic-transition`.
102
+
103
+ Radius and border-width come in three tiers — elements, **buttons**, containers — so you can flatten inputs while keeping pill buttons, or the reverse.
100
104
 
101
105
  ### Per-Component Customization
102
106
 
@@ -104,7 +108,7 @@ Override specific component tokens:
104
108
 
105
109
  ```css
106
110
  :root {
107
- --stuic-button-radius: 9999px; /* Pill buttons — overrides the shared --stuic-radius */
111
+ --stuic-button-radius: 9999px; /* Pill buttons — overrides the shared --stuic-radius-button */
108
112
  --stuic-switch-accent: #10b981; /* Green switches */
109
113
  }
110
114
  ```
@@ -174,16 +174,17 @@ Notes:
174
174
 
175
175
  ### Component Tokens
176
176
 
177
- | Variable | Default | Description |
178
- | ------------------------------ | ---------------------- | ------------------- |
179
- | `--stuic-button-radius` | `--radius-md` | Border radius |
180
- | `--stuic-button-font-family` | `--font-sans` | Font family |
181
- | `--stuic-button-font-weight` | `--font-weight-medium` | Font weight |
182
- | `--stuic-button-transition` | `100ms` | Transition duration |
183
- | `--stuic-button-ring-width` | `3px` | Focus ring width |
184
- | `--stuic-button-ring-color` | `--stuic-color-ring` | Focus ring color |
185
- | `--stuic-button-raised-offset` | `2px` | 3D effect offset |
186
- | `--stuic-button-raised-color` | `rgb(0 0 0 / 0.8)` | 3D shadow color |
177
+ | Variable | Default | Description |
178
+ | ------------------------------ | ------------------------------ | ------------------- |
179
+ | `--stuic-button-radius` | `--stuic-radius-button` | Border radius |
180
+ | `--stuic-button-border-width` | `--stuic-border-width-button` | Border width |
181
+ | `--stuic-button-font-family` | `--font-sans` | Font family |
182
+ | `--stuic-button-font-weight` | `--font-weight-medium` | Font weight |
183
+ | `--stuic-button-transition` | `--stuic-transition` (`150ms`) | Transition duration |
184
+ | `--stuic-button-ring-width` | `3px` | Focus ring width |
185
+ | `--stuic-button-ring-color` | `--stuic-color-ring` | Focus ring color |
186
+ | `--stuic-button-raised-offset` | `2px` | 3D effect offset |
187
+ | `--stuic-button-raised-color` | `rgb(0 0 0 / 0.8)` | 3D shadow color |
187
188
 
188
189
  ### Size Tokens
189
190
 
@@ -129,34 +129,63 @@ disable the whole group instead, use the top-level `disabled` prop.
129
129
 
130
130
  ## CSS Variables
131
131
 
132
+ All tokens below are **declared** in the component's `:root`. That means a `:root` override
133
+ or a `style` prop override both work, but the `var(--token, <fallback>)` fallbacks visible in
134
+ `index.css` never fire — the defaults are the `:root` values listed here.
135
+
132
136
  ### Component Tokens
133
137
 
134
- | Variable | Default | Description |
135
- | ---------------------------------------- | ------------------------- | --------------------------------------- |
136
- | `--stuic-button-group-radius` | `var(--radius-md)` | Border radius for container and buttons |
137
- | `--stuic-button-group-padding` | `0.375rem` | Container padding |
138
- | `--stuic-button-group-gap` | `0.25rem` | Gap between buttons |
139
- | `--stuic-button-group-border-width` | `1px` | Container border width |
140
- | `--stuic-button-group-transition` | `150ms` | Transition duration |
141
- | `--stuic-button-group-ring-width` | `3px` | Focus ring width |
142
- | `--stuic-button-group-ring-color` | `var(--stuic-color-ring)` | Focus ring color |
143
- | `--stuic-button-group-button-padding-x` | `0.75rem` | Button horizontal padding |
144
- | `--stuic-button-group-button-padding-y` | `0.5rem` | Button vertical padding |
145
- | `--stuic-button-group-button-min-height` | `2.75rem` | Button min height (44px touch target) |
138
+ | Variable | Default | Description |
139
+ | ---------------------------------------- | ------------------------- | ---------------------------------------------- |
140
+ | `--stuic-button-group-radius` | `9999px` | Border radius for container and buttons (pill) |
141
+ | `--stuic-button-group-padding-x` | `4px` | Container horizontal padding |
142
+ | `--stuic-button-group-padding-y` | `3px` | Container vertical padding |
143
+ | `--stuic-button-group-gap` | `0.25rem` | Gap between buttons |
144
+ | `--stuic-button-group-border-width` | `1px` | Container border width |
145
+ | `--stuic-button-group-ring-width` | `4px` | Focus ring width |
146
+ | `--stuic-button-group-ring-color` | `var(--stuic-color-ring)` | Focus ring color |
147
+ | `--stuic-button-group-button-padding-x` | `0.75rem` | Button horizontal padding |
148
+ | `--stuic-button-group-button-padding-y` | `0.375rem` | Button vertical padding |
149
+ | `--stuic-button-group-button-min-height` | `2.25rem` | Button min height (36px) |
150
+ | `--stuic-button-group-transition` | `var(--stuic-transition)` | Transition duration (not declared — fallback) |
151
+
152
+ `--stuic-button-group-transition` is the one exception: it is _not_ declared, so it resolves
153
+ through its usage-site fallback to `--stuic-transition`.
146
154
 
147
155
  ### Color Tokens
148
156
 
149
- | Variable | Default | Description |
150
- | --------------------------------------------- | --------------------------------------- | -------------------------------- |
151
- | `--stuic-button-group-bg` | `var(--stuic-color-surface)` | Container background |
152
- | `--stuic-button-group-text` | `var(--stuic-color-foreground)` | Container text color |
153
- | `--stuic-button-group-border` | `var(--stuic-color-border)` | Container border color |
154
- | `--stuic-button-group-button-bg` | `transparent` | Inactive button background |
155
- | `--stuic-button-group-button-text` | `var(--stuic-color-foreground)` | Inactive button text |
156
- | `--stuic-button-group-button-bg-hover` | `var(--stuic-color-muted)` | Inactive button hover background |
157
- | `--stuic-button-group-button-bg-active` | `var(--stuic-color-primary)` | Active button background |
158
- | `--stuic-button-group-button-text-active` | `var(--stuic-color-primary-foreground)` | Active button text |
159
- | `--stuic-button-group-button-bg-active-hover` | `var(--stuic-color-primary-hover)` | Active button hover background |
157
+ | Variable | Default | Description |
158
+ | ----------------------------------------------- | ----------------------------------------- | -------------------------------- |
159
+ | `--stuic-button-group-bg` | `var(--stuic-color-muted)` | Container background |
160
+ | `--stuic-button-group-text` | `var(--stuic-color-muted-foreground)` | Container text color |
161
+ | `--stuic-button-group-border` | `var(--stuic-input-border)` | Container border color |
162
+ | `--stuic-button-group-border-focus` | `var(--stuic-color-primary)` | Container border on focus-within |
163
+ | `--stuic-button-group-button-bg` | `transparent` | Inactive button background |
164
+ | `--stuic-button-group-button-text` | `var(--stuic-color-muted-foreground)` | Inactive button text |
165
+ | `--stuic-button-group-button-bg-hover` | `transparent` | Inactive button hover background |
166
+ | `--stuic-button-group-button-text-hover` | `var(--stuic-color-foreground)` | Inactive button hover text |
167
+ | `--stuic-button-group-button-bg-active` | `var(--stuic-color-surface-1)` | Active button background |
168
+ | `--stuic-button-group-button-text-active` | `var(--stuic-color-surface-1-foreground)` | Active button text |
169
+ | `--stuic-button-group-button-bg-active-hover` | `var(--stuic-color-surface-1)` | Active button hover background |
170
+ | `--stuic-button-group-button-text-active-hover` | `var(--stuic-color-surface-1-foreground)` | Active button hover text |
171
+
172
+ ### Size Variants
173
+
174
+ `size` sets `data-size` on the container, which re-declares a subset of the tokens above.
175
+ `md` is the `:root` default; `sm` and `lg` override these:
176
+
177
+ | Token | `sm` | `md` (default) | `lg` |
178
+ | ---------------------------------------- | ---------- | -------------- | ---------- |
179
+ | `--stuic-button-group-padding-x` | `3px` | `4px` | `8px` |
180
+ | `--stuic-button-group-padding-y` | `2px` | `3px` | `6px` |
181
+ | `--stuic-button-group-gap` | `0.125rem` | `0.25rem` | `0.375rem` |
182
+ | `--stuic-button-group-button-padding-x` | `0.5rem` | `0.75rem` | `1rem` |
183
+ | `--stuic-button-group-button-padding-y` | `0.375rem` | `0.375rem` | `0.625rem` |
184
+ | `--stuic-button-group-button-min-height` | `2.25rem` | `2.25rem` | `3rem` |
185
+ | overall height | 42px | 44px | 62px |
186
+
187
+ Because the size variants declare these on the container itself, they beat the inherited
188
+ `:root` values — but an inline `style` on the same element still wins over both.
160
189
 
161
190
  ### Customization Examples
162
191
 
@@ -78,7 +78,9 @@
78
78
  ============================================================================ */
79
79
 
80
80
  .stuic-button-group[data-size="sm"] {
81
- --stuic-button-group-padding: 0.25rem;
81
+ /* overall height = 36px + 2×2px + 2×1px = 42px */
82
+ --stuic-button-group-padding-x: 3px;
83
+ --stuic-button-group-padding-y: 2px;
82
84
  --stuic-button-group-gap: 0.125rem;
83
85
  --stuic-button-group-button-padding-x: 0.5rem;
84
86
  --stuic-button-group-button-padding-y: 0.375rem;
@@ -86,7 +88,9 @@
86
88
  }
87
89
 
88
90
  .stuic-button-group[data-size="lg"] {
89
- --stuic-button-group-padding: 0.5rem;
91
+ /* overall height = 48px + 2×6px + 2×1px = 62px */
92
+ --stuic-button-group-padding-x: 8px;
93
+ --stuic-button-group-padding-y: 6px;
90
94
  --stuic-button-group-gap: 0.375rem;
91
95
  --stuic-button-group-button-padding-x: 1rem;
92
96
  --stuic-button-group-button-padding-y: 0.625rem;
@@ -177,6 +177,11 @@
177
177
  let _classTiers = $derived(
178
178
  unstyled ? classTiersProp : twMerge("stuic-pricing-table-tiers", classTiersProp)
179
179
  );
180
+ // The toggle's pill look lives in index.css behind --stuic-pricing-table-toggle-*
181
+ // tokens (an inline declaration here would outrank any :root override).
182
+ let _classToggle = $derived(
183
+ unstyled ? classToggleProp : twMerge("stuic-pricing-table-toggle", classToggleProp)
184
+ );
180
185
  </script>
181
186
 
182
187
  <div bind:this={el} class={_class} {style} {...rest}>
@@ -195,20 +200,9 @@
195
200
  onButtonClick={(i) => {
196
201
  billingPeriod = i === 0 ? "monthly" : "annual";
197
202
  }}
198
- class={classToggleProp}
199
- style="
200
- width: auto;
201
- --stuic-button-group-radius: 9999px;
202
- --stuic-button-group-padding: 0.25rem;
203
- --stuic-button-group-bg: var(--stuic-color-muted);
204
- --stuic-button-group-border-width: 0;
205
- --stuic-button-group-button-bg-active: var(--stuic-color-background);
206
- --stuic-button-group-button-text-active: var(--stuic-color-foreground);
207
- --stuic-button-group-button-bg-active-hover: var(--stuic-color-background);
208
- --stuic-button-group-button-text-active-hover: var(--stuic-color-foreground);
209
- {styleToggle ?? ''}
210
- "
211
- classButtonActive="shadow"
203
+ class={_classToggle}
204
+ style={styleToggle}
205
+ classButtonActive={unstyled ? undefined : "shadow"}
212
206
  />
213
207
  {/if}
214
208
  {/if}
@@ -161,12 +161,35 @@ A data-driven pricing tiers component for displaying plan comparisons with featu
161
161
 
162
162
  ### Toggle
163
163
 
164
- | Variable | Default | Description |
165
- | ------------------------------------------- | ---------------- | ------------------------ |
166
- | `--stuic-pricing-table-toggle-bg` | muted | Toggle track background |
167
- | `--stuic-pricing-table-toggle-bg-active` | background | Active option background |
168
- | `--stuic-pricing-table-toggle-color` | muted-foreground | Option text color |
169
- | `--stuic-pricing-table-toggle-color-active` | foreground | Active option text color |
164
+ The billing toggle is an internal `ButtonGroupRadio`. Its pill look is expressed through the
165
+ tokens below, so a theme can reach it from `:root` like any other part of the component:
166
+
167
+ ```css
168
+ :root {
169
+ /* square toggle to match a flat theme */
170
+ --stuic-pricing-table-toggle-radius: var(--stuic-radius-button);
171
+ }
172
+ ```
173
+
174
+ | Variable | Default | Description |
175
+ | ------------------------------------------------- | ---------------- | ------------------------------------------- |
176
+ | `--stuic-pricing-table-toggle-radius` | `9999px` | Track and option border radius |
177
+ | `--stuic-pricing-table-toggle-bg` | muted | Toggle track background |
178
+ | `--stuic-pricing-table-toggle-border-width` | `0` | Toggle track border width |
179
+ | `--stuic-pricing-table-toggle-padding-x` | `0.25rem` | Track horizontal padding around the options |
180
+ | `--stuic-pricing-table-toggle-padding-y` | `0.25rem` | Track vertical padding around the options |
181
+ | `--stuic-pricing-table-toggle-color` | muted-foreground | Inactive option text color |
182
+ | `--stuic-pricing-table-toggle-color-hover` | foreground | Inactive option text color on hover |
183
+ | `--stuic-pricing-table-toggle-bg-active` | background | Active option background |
184
+ | `--stuic-pricing-table-toggle-color-active` | foreground | Active option text color |
185
+ | `--stuic-pricing-table-toggle-bg-active-hover` | `-bg-active` | Active option background on hover |
186
+ | `--stuic-pricing-table-toggle-color-active-hover` | `-color-active` | Active option text color on hover |
187
+
188
+ For anything these do not cover, `styleToggle` sets inline styles on the group (including raw
189
+ `--stuic-button-group-*` tokens) and wins over all of the above; `classToggle` adds classes.
190
+ Note that `--stuic-button-group-*` set at `:root` does **not** reach the pricing toggle for the
191
+ properties listed above — the component declares them on the element, so use the
192
+ `--stuic-pricing-table-toggle-*` tokens instead.
170
193
 
171
194
  ### Typography
172
195
 
@@ -17,6 +17,60 @@
17
17
  width: 100%;
18
18
  }
19
19
 
20
+ /* ============================================================================
21
+ BILLING TOGGLE (an internal ButtonGroupRadio)
22
+
23
+ The opinionated pill look lives here rather than in the component's inline
24
+ `style` attribute, because an inline custom property declaration outranks any
25
+ `:root` declaration for that element and its subtree -- which made the toggle
26
+ the one part of this component a theme could not reach. Each value is a
27
+ never-declared `--stuic-pricing-table-toggle-*` component token, so
28
+ `:root { --stuic-pricing-table-toggle-radius: 0; }` works like every other
29
+ token here. `styleToggle` still wins over all of it (inline).
30
+ ============================================================================ */
31
+
32
+ .stuic-pricing-table-toggle {
33
+ /* hug the options instead of spanning the row (the group defaults to 100%) */
34
+ width: auto;
35
+
36
+ --stuic-button-group-radius: var(--stuic-pricing-table-toggle-radius, 9999px);
37
+ --stuic-button-group-padding-x: var(--stuic-pricing-table-toggle-padding-x, 0.25rem);
38
+ --stuic-button-group-padding-y: var(--stuic-pricing-table-toggle-padding-y, 0.25rem);
39
+ --stuic-button-group-bg: var(
40
+ --stuic-pricing-table-toggle-bg,
41
+ var(--stuic-color-muted)
42
+ );
43
+ --stuic-button-group-border-width: var(--stuic-pricing-table-toggle-border-width, 0);
44
+
45
+ /* Inactive option */
46
+ --stuic-button-group-button-text: var(
47
+ --stuic-pricing-table-toggle-color,
48
+ var(--stuic-color-muted-foreground)
49
+ );
50
+ --stuic-button-group-button-text-hover: var(
51
+ --stuic-pricing-table-toggle-color-hover,
52
+ var(--stuic-color-foreground)
53
+ );
54
+
55
+ /* Active option (hover falls back to the resting value = no hover shift) */
56
+ --stuic-button-group-button-bg-active: var(
57
+ --stuic-pricing-table-toggle-bg-active,
58
+ var(--stuic-color-background)
59
+ );
60
+ --stuic-button-group-button-text-active: var(
61
+ --stuic-pricing-table-toggle-color-active,
62
+ var(--stuic-color-foreground)
63
+ );
64
+ --stuic-button-group-button-bg-active-hover: var(
65
+ --stuic-pricing-table-toggle-bg-active-hover,
66
+ var(--stuic-pricing-table-toggle-bg-active, var(--stuic-color-background))
67
+ );
68
+ --stuic-button-group-button-text-active-hover: var(
69
+ --stuic-pricing-table-toggle-color-active-hover,
70
+ var(--stuic-pricing-table-toggle-color-active, var(--stuic-color-foreground))
71
+ );
72
+ }
73
+
20
74
  /* ============================================================================
21
75
  TIERS GRID
22
76
  ============================================================================ */
package/dist/index.css CHANGED
@@ -29,6 +29,20 @@ In practice:
29
29
  These tokens provide a single point of control for cross-component properties.
30
30
  Override globally: :root { --stuic-radius: 0; }
31
31
  Override per-component: --stuic-button-radius: 9999px; (takes precedence)
32
+
33
+ NOTE on the mirrored names. The tokens below are TIER tokens, named
34
+ --stuic-{property}-{tier}, and they ARE declared here. Component override tokens
35
+ are the transposition, --stuic-{component}-{property}, and are NEVER declared --
36
+ they exist only as the first argument of a var() fallback at the usage site:
37
+
38
+ border-radius: var(--stuic-button-radius, var(--stuic-radius-button));
39
+ ^ component token (3) ^ tier token (2, declared here)
40
+
41
+ So --stuic-radius-button (tier) applies to Button, SplitButton and ButtonGroupRadio,
42
+ while --stuic-button-radius (component) applies to Button alone. Same relationship
43
+ between --stuic-border-width-button and --stuic-button-border-width. "button" is the
44
+ only tier name that is also a component name, hence the only confusable pair -- do
45
+ not "fix" one into the other.
32
46
  ============================================================================ */
33
47
  :root {
34
48
  /* Radius: three tiers */
@@ -43,12 +43,14 @@ Global tokens in `src/lib/index.css` that control cross-component visual propert
43
43
  /* Brutalist example — 7 lines to transform everything */
44
44
  :root {
45
45
  --stuic-radius: 0;
46
+ --stuic-radius-button: 0;
46
47
  --stuic-radius-container: 0;
47
48
  --stuic-shadow: none;
48
49
  --stuic-shadow-hover: none;
49
50
  --stuic-shadow-overlay: none;
50
51
  --stuic-shadow-dialog: none;
51
52
  --stuic-border-width: 0;
53
+ --stuic-border-width-button: 0;
52
54
  }
53
55
  ```
54
56
 
@@ -79,16 +81,34 @@ Component CSS must reference shared tokens as **fallbacks at usage sites**, not
79
81
 
80
82
  Per-component overrides (`--stuic-widget-radius: 0`) still take precedence over the shared fallback.
81
83
 
82
- ### Element vs Container
84
+ ### Element vs Button vs Container
83
85
 
84
- Two tiers of radius for natural visual hierarchy:
86
+ Three tiers of radius for natural visual hierarchy:
85
87
 
86
- | Tier | Token | Default | Use for |
87
- | --------- | -------------------------- | ------------------ | ----------------------------------------------------- |
88
- | Element | `--stuic-radius` | `var(--radius-md)` | Buttons, inputs, badges, list items, checkboxes, tabs |
89
- | Container | `--stuic-radius-container` | `var(--radius-lg)` | Cards, modals, dropdowns, notifications, accordions |
88
+ | Tier | Token | Default | Use for |
89
+ | --------- | -------------------------- | ------------------ | --------------------------------------------------- |
90
+ | Element | `--stuic-radius` | `var(--radius-md)` | Inputs, badges, list items, checkboxes, tabs |
91
+ | Button | `--stuic-radius-button` | `var(--radius-md)` | Buttons, split buttons, button groups |
92
+ | Container | `--stuic-radius-container` | `var(--radius-lg)` | Cards, modals, dropdowns, notifications, accordions |
90
93
 
91
- **Rule of thumb:** if it wraps other interactive elements, it's a container.
94
+ **Rule of thumb:** if it wraps other interactive elements, it's a container. Buttons get
95
+ their own tier so a flat-input theme can still have pill buttons (and vice versa);
96
+ `--stuic-border-width-button` splits out the same way.
97
+
98
+ ### Tier tokens vs component tokens
99
+
100
+ The two layers use mirrored names, which is easy to misread for the button tier:
101
+
102
+ | Name | Layer | Declared? | Scope |
103
+ | ----------------------------- | ------------------- | --------------------- | ----------------------------- |
104
+ | `--stuic-radius-button` | shared tier (2) | yes, in `index.css` | every button-family component |
105
+ | `--stuic-button-radius` | component token (3) | no, fallback arg only | `Button` only |
106
+ | `--stuic-border-width-button` | shared tier (2) | yes, in `index.css` | every button-family component |
107
+ | `--stuic-button-border-width` | component token (3) | no, fallback arg only | `Button` only |
108
+
109
+ Read it as `--stuic-{property}-{tier}` for tier tokens and `--stuic-{component}-{property}`
110
+ for component tokens. `button` is the only tier name that is also a component name, so it
111
+ is the only pair that reads as a transposition.
92
112
 
93
113
  ---
94
114
 
@@ -186,39 +186,55 @@ const css = generateThemeCss(custom, "stuic-");
186
186
 
187
187
  Global tokens defined in `src/lib/index.css` that control cross-component visual properties. Override these to change the entire library's visual character:
188
188
 
189
- | Token | Default | Purpose |
190
- | -------------------------- | ------------------ | ------------------------------------------------- |
191
- | `--stuic-radius` | `var(--radius-md)` | Element-level radius (buttons, inputs, badges) |
192
- | `--stuic-radius-container` | `var(--radius-lg)` | Container-level radius (cards, modals, dropdowns) |
193
- | `--stuic-shadow` | `var(--shadow-sm)` | Default resting shadow |
194
- | `--stuic-shadow-hover` | `var(--shadow-md)` | Hover/elevated shadow |
195
- | `--stuic-shadow-overlay` | `var(--shadow-lg)` | Overlays (dropdowns, notifications) |
196
- | `--stuic-shadow-dialog` | `var(--shadow-xl)` | Dialogs/modals |
197
- | `--stuic-border-width` | `1px` | Default border width |
198
- | `--stuic-transition` | `150ms` | Default transition duration |
189
+ | Token | Default | Purpose |
190
+ | ----------------------------- | ------------------ | ----------------------------------------------------------- |
191
+ | `--stuic-radius` | `var(--radius-md)` | Element-level radius (inputs, badges, list items) |
192
+ | `--stuic-radius-button` | `var(--radius-md)` | Button-level radius (buttons, split buttons, button groups) |
193
+ | `--stuic-radius-container` | `var(--radius-lg)` | Container-level radius (cards, modals, dropdowns) |
194
+ | `--stuic-shadow` | `var(--shadow-sm)` | Default resting shadow |
195
+ | `--stuic-shadow-hover` | `var(--shadow-md)` | Hover/elevated shadow |
196
+ | `--stuic-shadow-overlay` | `var(--shadow-lg)` | Overlays (dropdowns, notifications) |
197
+ | `--stuic-shadow-dialog` | `var(--shadow-xl)` | Dialogs/modals |
198
+ | `--stuic-border-width` | `1px` | Default border width |
199
+ | `--stuic-border-width-button` | `1px` | Button border width (independent from general elements) |
200
+ | `--stuic-transition` | `150ms` | Default transition duration |
201
+
202
+ Radius and border-width come in **three tiers** — elements, buttons, containers — so a
203
+ theme can flatten inputs while keeping pill buttons, or vice versa. See
204
+ [Conventions: Element vs Button vs Container](../conventions.md#element-vs-button-vs-container).
199
205
 
200
206
  Example — brutalist style in 7 lines:
201
207
 
202
208
  ```css
203
209
  :root {
204
210
  --stuic-radius: 0;
211
+ --stuic-radius-button: 0;
205
212
  --stuic-radius-container: 0;
206
213
  --stuic-shadow: none;
207
214
  --stuic-shadow-hover: none;
208
215
  --stuic-shadow-overlay: none;
209
216
  --stuic-shadow-dialog: none;
210
217
  --stuic-border-width: 0;
218
+ --stuic-border-width-button: 0;
211
219
  }
212
220
  ```
213
221
 
214
- Components reference these via the fallback pattern (not `:root` declarations):
222
+ Components reference these via the fallback pattern (not `:root` declarations) — the
223
+ per-component token first, the shared tier token as its fallback:
215
224
 
216
225
  ```css
217
226
  .stuic-button {
218
- border-radius: var(--stuic-button-radius, var(--stuic-radius));
227
+ border-radius: var(--stuic-button-radius, var(--stuic-radius-button));
228
+ /* ^ component token ^ shared tier token */
219
229
  }
220
230
  ```
221
231
 
232
+ > **Note the mirrored names.** `--stuic-radius-button` is the shared tier token (declared
233
+ > in `src/lib/index.css`, applies to every button-family component);
234
+ > `--stuic-button-radius` is the `Button` component's own override token (never declared —
235
+ > it only ever appears as the first argument of a `var()` fallback). Same for
236
+ > `--stuic-border-width-button` vs `--stuic-button-border-width`.
237
+
222
238
  ---
223
239
 
224
240
  ## Component Tokens
@@ -239,7 +255,7 @@ Override per-component tokens globally:
239
255
 
240
256
  ```css
241
257
  :root {
242
- --stuic-button-radius: 9999px; /* Pill buttons — overrides the shared fallback */
258
+ --stuic-button-radius: 9999px; /* Pill buttons — overrides --stuic-radius-button */
243
259
  }
244
260
  ```
245
261
 
@@ -251,6 +267,34 @@ Override locally:
251
267
 
252
268
  Some token sets belong to a CSS-only preset rather than to a component — e.g. `--stuic-frame-*` (ratio-locked frame / letterbox). Those are consumer **inputs** that stuic never declares; see [CSS presets](./css-presets.md).
253
269
 
270
+ ### Pitfall: never pin a nested component's tokens inline
271
+
272
+ When a component renders another stuic component internally and wants to restyle it, the
273
+ opinionated values must go in the outer component's `index.css`, behind its own
274
+ `--stuic-{outer}-{part}-*` tokens — never into the inner component's inline `style`
275
+ attribute:
276
+
277
+ ```svelte
278
+ <!-- WRONG — an inline custom property outranks every :root declaration for that
279
+ element and its subtree, so no theme can reach the inner component -->
280
+ <ButtonGroupRadio style="--stuic-button-group-radius: 9999px;" />
281
+
282
+ <!-- RIGHT — the class carries the look, and `style` stays the caller's escape hatch -->
283
+ <ButtonGroupRadio class="stuic-pricing-table-toggle" style={styleToggle} />
284
+ ```
285
+
286
+ ```css
287
+ .stuic-pricing-table-toggle {
288
+ --stuic-button-group-radius: var(--stuic-pricing-table-toggle-radius, 9999px);
289
+ }
290
+ ```
291
+
292
+ No specificity or `@layer` trick outranks an inline declaration, so the inline form makes
293
+ that part of the component permanently unthemeable. `PricingTable`'s billing toggle is the
294
+ worked example. Note the trade: whatever the outer component declares on the element is no
295
+ longer reachable through the _inner_ component's global token, so document the
296
+ `--stuic-{outer}-*` replacement in the outer component's README.
297
+
254
298
  ---
255
299
 
256
300
  ## Key Files
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.173.0",
3
+ "version": "3.174.0",
4
4
  "packageManager": "pnpm@11.5.0",
5
5
  "scripts": {
6
6
  "dev": "vite dev",