@stisla/style 3.0.0-beta.8

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 (63) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +16 -0
  3. package/dist/accordion/accordion.css +194 -0
  4. package/dist/alert/alert.css +138 -0
  5. package/dist/autocomplete/autocomplete.css +193 -0
  6. package/dist/avatar/avatar.css +142 -0
  7. package/dist/avatar-group/avatar-group.css +42 -0
  8. package/dist/badge/badge.css +74 -0
  9. package/dist/breadcrumb/breadcrumb.css +71 -0
  10. package/dist/button/button.css +318 -0
  11. package/dist/button/index.d.ts +1 -0
  12. package/dist/button/index.js +6 -0
  13. package/dist/button-group/button-group.css +108 -0
  14. package/dist/card/card.css +219 -0
  15. package/dist/carousel/carousel.css +170 -0
  16. package/dist/checkbox/checkbox.css +98 -0
  17. package/dist/chunk-K45KLI3Y.js +74 -0
  18. package/dist/collapsible/collapsible.css +36 -0
  19. package/dist/combobox/combobox.css +106 -0
  20. package/dist/combobox/combobox.tomselect.css +251 -0
  21. package/dist/config-CARtrJ7I.d.ts +61 -0
  22. package/dist/dialog/dialog.css +258 -0
  23. package/dist/drawer/drawer.css +318 -0
  24. package/dist/empty-state/empty-state.css +138 -0
  25. package/dist/field/field.css +70 -0
  26. package/dist/icon-box/icon-box.css +64 -0
  27. package/dist/illustration/illustration.css +103 -0
  28. package/dist/index.d.ts +14 -0
  29. package/dist/index.js +60 -0
  30. package/dist/indicator/indicator.css +84 -0
  31. package/dist/input/input.css +220 -0
  32. package/dist/input-group/input-group.css +141 -0
  33. package/dist/kbd/kbd.css +55 -0
  34. package/dist/link/link.css +28 -0
  35. package/dist/list-group/list-group.css +261 -0
  36. package/dist/media/media.css +115 -0
  37. package/dist/menu/menu.css +237 -0
  38. package/dist/meter/meter.css +124 -0
  39. package/dist/navbar/navbar.css +170 -0
  40. package/dist/page/page.css +95 -0
  41. package/dist/pagination/pagination.css +125 -0
  42. package/dist/placeholders/placeholders.css +58 -0
  43. package/dist/popover/popover.css +251 -0
  44. package/dist/progress/progress.css +139 -0
  45. package/dist/radio/radio.css +81 -0
  46. package/dist/scroll-area/scroll-area.css +25 -0
  47. package/dist/scroll-area/scroll-area.overlayscrollbars.css +42 -0
  48. package/dist/select/select.css +282 -0
  49. package/dist/separator/separator.css +26 -0
  50. package/dist/sidebar/sidebar.css +493 -0
  51. package/dist/slider/slider.css +159 -0
  52. package/dist/spinner/spinner.css +65 -0
  53. package/dist/switch/switch.css +91 -0
  54. package/dist/table/table.css +284 -0
  55. package/dist/tabs/tabs.css +137 -0
  56. package/dist/textarea/textarea.css +99 -0
  57. package/dist/timeline/timeline.css +271 -0
  58. package/dist/toast/toast.css +267 -0
  59. package/dist/toggle/toggle.css +125 -0
  60. package/dist/toggle-group/toggle-group.css +87 -0
  61. package/dist/tooltip/tooltip.css +95 -0
  62. package/package.json +46 -0
  63. package/src/theme.css +151 -0
@@ -0,0 +1,42 @@
1
+ /* @stisla/style — Avatar group. Ported from src/scss/components/_avatar-group.scss. A row of
2
+ * overlapping .avatar children that reads as a cluster (team row, attendee list). Members keep their
3
+ * own paint; the group owns the overlap (negative inline margin) and turns the per-avatar ring on so
4
+ * adjacent shapes read as separate objects. The hovered/focused member lifts so its ring paints over
5
+ * its neighbor — same z-index ladder as .button-group. References the @theme tokens (colors
6
+ * var(--color-*), spacing --spacing(n)). Knobs are --avatar-group-*. @layer components.
7
+ * Authoring rules: ../../../../PORTING.md */
8
+
9
+ @layer components {
10
+ .avatar-group {
11
+ display: inline-flex;
12
+ vertical-align: middle;
13
+ }
14
+
15
+ /* Turn the ring on for every member (members declare the --avatar-ring-* slots; the group publishes
16
+ values into them). */
17
+ .avatar-group > .avatar {
18
+ --avatar-ring-width: var(--avatar-group-ring-width, 2px);
19
+ --avatar-ring-color: var(--avatar-group-ring-color, var(--color-background));
20
+ position: relative;
21
+ }
22
+
23
+ /* Negative inline margin collapses each member onto its left neighbor. */
24
+ .avatar-group > .avatar + .avatar {
25
+ margin-inline-start: calc(-1 * var(--avatar-group-overlap, --spacing(2.5)));
26
+ }
27
+
28
+ /* Stacking order — hovered / focused member rises so its ring paints over the following neighbor. */
29
+ .avatar-group > .avatar:hover {
30
+ z-index: 1;
31
+ }
32
+ .avatar-group > .avatar:focus-visible {
33
+ z-index: 2;
34
+ }
35
+
36
+ /* === Overflow tail === a plain .avatar member; this modifier just retunes its paint to read as
37
+ * "more" rather than a missing portrait. */
38
+ .avatar-group__more {
39
+ --avatar-bg: var(--color-surface-3);
40
+ --avatar-color: var(--color-muted-foreground);
41
+ }
42
+ }
@@ -0,0 +1,74 @@
1
+ /* @stisla/style — Badge. Ported from src/scss/components/_badge.scss. References the Tailwind
2
+ * @theme tokens: colors var(--color-*), spacing --spacing(n), type var(--text-*) /
3
+ * var(--font-weight-*), radius literal pill. Knobs are --badge-* (fallback-default). Intent
4
+ * modifiers publish --badge-tone + set the filled --badge-bg / --badge-color; .badge--soft reads
5
+ * --badge-tone for a tinted variant. Pill by default; set --badge-radius to flatten. @layer components. */
6
+
7
+ @layer components {
8
+ .badge {
9
+ display: inline-flex;
10
+ align-items: center;
11
+ gap: --spacing(1);
12
+ min-height: var(--badge-min-height, --spacing(5.5));
13
+ padding-block: var(--badge-padding-block, --spacing(0.5));
14
+ padding-inline: var(--badge-padding-inline, --spacing(2));
15
+ font-family: inherit;
16
+ font-size: var(--badge-font-size, var(--text-xs));
17
+ font-weight: var(--badge-font-weight, var(--font-weight-medium));
18
+ line-height: var(--leading-none);
19
+ color: var(--badge-color, var(--color-neutral-foreground));
20
+ background: var(--badge-bg, var(--color-neutral));
21
+ border-radius: var(--badge-radius, 9999px);
22
+ white-space: nowrap;
23
+ }
24
+
25
+ /* Inline icons scale to the badge's font size (same convention as .button). */
26
+ .badge :is(svg, i, .badge__icon) {
27
+ width: 1em;
28
+ height: 1em;
29
+ flex-shrink: 0;
30
+ }
31
+
32
+ /* === Intent modifiers — filled defaults ===
33
+ * Each intent publishes --badge-tone (consumed by .badge--soft below) and sets the filled
34
+ * --badge-bg / --badge-color. */
35
+ .badge--primary {
36
+ --badge-tone: var(--color-primary);
37
+ --badge-bg: var(--color-primary);
38
+ --badge-color: var(--color-primary-foreground);
39
+ }
40
+ .badge--success {
41
+ --badge-tone: var(--color-success);
42
+ --badge-bg: var(--color-success);
43
+ --badge-color: var(--color-success-foreground);
44
+ }
45
+ .badge--warning {
46
+ --badge-tone: var(--color-warning);
47
+ --badge-bg: var(--color-warning);
48
+ --badge-color: var(--color-warning-foreground);
49
+ }
50
+ .badge--danger {
51
+ --badge-tone: var(--color-danger);
52
+ --badge-bg: var(--color-danger);
53
+ --badge-color: var(--color-danger-foreground);
54
+ }
55
+ .badge--info {
56
+ --badge-tone: var(--color-info);
57
+ --badge-bg: var(--color-info);
58
+ --badge-color: var(--color-info-foreground);
59
+ }
60
+
61
+ /* === Soft modifier ===
62
+ * Tinted bg (15%) + solid tone text. Reads --badge-tone (set by an intent modifier, or the
63
+ * muted-foreground default for a bare .badge--soft). Does NOT redefine --badge-tone — that
64
+ * would clobber the intent and break .badge--soft.badge--primary. The tone is a runtime var,
65
+ * so tint with color-mix (same precedent as .button--soft), not the compile-time --alpha(). */
66
+ .badge--soft {
67
+ --badge-bg: color-mix(
68
+ in oklch,
69
+ var(--badge-tone, var(--color-muted-foreground)) 15%,
70
+ transparent
71
+ );
72
+ --badge-color: var(--badge-tone, var(--color-muted-foreground));
73
+ }
74
+ }
@@ -0,0 +1,71 @@
1
+ /* @stisla/style — Breadcrumb. Ported from src/scss/components/_breadcrumb.scss. A trail of links
2
+ * showing where the user is in the hierarchy. The trail stays muted and lifts toward body color on
3
+ * hover; the last crumb (aria-current="page") gets full emphasis. Divider is a Unicode chevron via
4
+ * ::before (inherits the crumb color, so it themes/retints for free). References the @theme tokens
5
+ * (colors var(--color-*), spacing/sizes --spacing(n), radius var(--radius-*)); only no-namespace
6
+ * customs use --st-* (border-width). Knobs are --breadcrumb-*. State via aria-current, no is-*.
7
+ * @layer components. Authoring rules: ../../../../PORTING.md */
8
+
9
+ @layer components {
10
+ .breadcrumb {
11
+ display: flex;
12
+ flex-wrap: wrap;
13
+ align-items: center;
14
+ gap: var(--breadcrumb-gap, --spacing(2));
15
+ padding: var(--breadcrumb-padding-block, 0) var(--breadcrumb-padding-inline, 0);
16
+ font-size: var(--breadcrumb-font-size, inherit);
17
+ background: var(--breadcrumb-bg, transparent);
18
+ border-radius: var(--breadcrumb-radius, 0);
19
+ color: var(--breadcrumb-color, var(--color-muted-foreground));
20
+ }
21
+
22
+ .breadcrumb__item {
23
+ display: inline-flex;
24
+ align-items: center;
25
+ gap: var(--breadcrumb-gap, --spacing(2));
26
+
27
+ > a {
28
+ display: inline-flex;
29
+ align-items: center;
30
+ gap: var(--breadcrumb-gap, --spacing(2));
31
+ color: inherit;
32
+ text-decoration: none;
33
+ transition: color var(--transition-duration-fast) ease;
34
+
35
+ &:hover {
36
+ color: var(--breadcrumb-color-hover, var(--color-foreground));
37
+ }
38
+
39
+ &:focus-visible {
40
+ outline: 2px solid var(--color-ring);
41
+ outline-offset: 2px;
42
+ border-radius: var(--radius-sm);
43
+ }
44
+ }
45
+
46
+ /* Leading/trailing icon — fixed size, tracks currentColor through every state. */
47
+ > :is(svg, i),
48
+ > a > :is(svg, i) {
49
+ width: var(--breadcrumb-icon-size, --spacing(4));
50
+ height: var(--breadcrumb-icon-size, --spacing(4));
51
+ flex-shrink: 0;
52
+ }
53
+
54
+ &[aria-current="page"] {
55
+ color: var(--breadcrumb-color-active, var(--color-foreground));
56
+ }
57
+
58
+ /* Divider — chevron on every crumb past the first. Default "›" lives as the var() fallback (NOT a
59
+ declaration on .breadcrumb) so a per-instance override on the wrapper wins. */
60
+ + .breadcrumb__item::before {
61
+ content: var(--breadcrumb-divider, "›");
62
+ color: var(--breadcrumb-color, var(--color-muted-foreground));
63
+ }
64
+ }
65
+
66
+ @media (prefers-reduced-motion: reduce) {
67
+ .breadcrumb__item > a {
68
+ transition: none;
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,318 @@
1
+ /* @stisla/style — Button. Ported from src/scss/components/_btn.scss. References the Tailwind
2
+ * @theme tokens: colors var(--color-*), spacing --spacing(n), type var(--text-*) /
3
+ * var(--font-weight-*) / var(--leading-*), radius var(--radius-*). Only no-namespace customs
4
+ * use --st-* (border-width, duration). Knobs are --button-* (fallback-default). @layer components. */
5
+
6
+ @layer components {
7
+ .button {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ gap: var(--button-gap, --spacing(2));
12
+ height: var(--button-height, --spacing(9)); /* md */
13
+ padding-block: var(--button-padding-block, 0);
14
+ padding-inline: var(--button-padding-inline, --spacing(3));
15
+ font-family: inherit;
16
+ font-size: var(--button-font-size, var(--text-sm));
17
+ font-weight: var(--button-font-weight, var(--font-weight-medium));
18
+ line-height: var(--leading-none);
19
+ color: var(--button-color);
20
+ background: var(--button-bg, var(--button-tone));
21
+ border: var(--button-border-width, var(--st-border-width)) solid
22
+ var(
23
+ --button-border-color,
24
+ color-mix(
25
+ in oklch,
26
+ var(--button-tone) var(--button-rim-mix, 85%),
27
+ black
28
+ )
29
+ );
30
+ border-radius: var(--button-radius, var(--radius-md));
31
+ box-shadow: var(--button-bevel, inset 0 1px 0 rgba(255, 255, 255, 0.15));
32
+ cursor: pointer;
33
+ user-select: none;
34
+ text-decoration: none;
35
+ white-space: nowrap;
36
+ overflow: hidden;
37
+ text-overflow: ellipsis;
38
+ transition:
39
+ background-color var(--transition-duration-fast) ease,
40
+ border-color var(--transition-duration-fast) ease,
41
+ color var(--transition-duration-fast) ease,
42
+ box-shadow var(--transition-duration-fast) ease;
43
+
44
+ &:hover {
45
+ background: var(
46
+ --button-bg-hover,
47
+ color-mix(in oklch, var(--button-tone) 88%, black)
48
+ );
49
+ }
50
+ &:active,
51
+ &[aria-pressed="true"],
52
+ &[data-state="active"] {
53
+ background: var(
54
+ --button-bg-active,
55
+ color-mix(in oklch, var(--button-tone) 78%, black)
56
+ );
57
+ }
58
+ &:focus-visible {
59
+ outline: 2px solid var(--color-ring);
60
+ outline-offset: 2px;
61
+ }
62
+ &:disabled,
63
+ &[aria-disabled="true"] {
64
+ cursor: not-allowed;
65
+ pointer-events: none;
66
+ opacity: 0.55;
67
+ }
68
+ }
69
+
70
+ .button :is(svg, i, .button__icon) {
71
+ width: var(--button-icon-size, --spacing(4));
72
+ height: var(--button-icon-size, --spacing(4));
73
+ flex-shrink: 0;
74
+ }
75
+
76
+ /* === Sizes (base = md) === */
77
+ .button--sm {
78
+ --button-height: --spacing(7);
79
+ --button-padding-inline: --spacing(2.5);
80
+ --button-font-size: var(--text-sm);
81
+ --button-radius: var(--radius-sm);
82
+ }
83
+ .button--lg {
84
+ --button-height: --spacing(11);
85
+ --button-padding-inline: --spacing(4);
86
+ --button-radius: var(--radius-lg);
87
+ }
88
+ .button--xl {
89
+ --button-height: --spacing(13);
90
+ --button-padding-inline: --spacing(5);
91
+ --button-font-size: var(--text-base);
92
+ --button-radius: var(--radius-lg);
93
+ }
94
+
95
+ /* === Icon shapes === */
96
+ .button--icon-only {
97
+ --button-padding-inline: 0;
98
+ width: var(--button-height, --spacing(9));
99
+ }
100
+ .button--icon-round {
101
+ border-radius: 9999px;
102
+ }
103
+
104
+ /* === Wrap / block === */
105
+ .button--wrap {
106
+ height: auto;
107
+ min-height: var(--button-height, --spacing(9));
108
+ --button-padding-block: --spacing(1.5);
109
+ white-space: normal;
110
+ overflow: visible;
111
+ text-overflow: clip;
112
+ line-height: var(--leading-tight);
113
+ }
114
+ .button--block {
115
+ display: flex;
116
+ width: 100%;
117
+ }
118
+
119
+ /* === Tones — filled === */
120
+ .button--primary {
121
+ --button-tone: var(--color-primary);
122
+ --button-color: var(--color-primary-foreground);
123
+ }
124
+ .button--danger {
125
+ --button-tone: var(--color-danger);
126
+ --button-color: var(--color-danger-foreground);
127
+ }
128
+ .button--neutral {
129
+ --button-tone: var(--color-foreground);
130
+ --button-bg: var(--color-neutral);
131
+ --button-color: var(--color-neutral-foreground);
132
+ --button-border-color: color-mix(
133
+ in oklch,
134
+ var(--color-neutral) var(--button-rim-mix, 85%),
135
+ var(--color-muted-foreground)
136
+ );
137
+
138
+ &:hover {
139
+ background: var(
140
+ --button-bg-hover,
141
+ color-mix(in oklch, var(--color-neutral) 88%, black)
142
+ );
143
+ }
144
+ &:active,
145
+ &[aria-pressed="true"],
146
+ &[data-state="active"] {
147
+ background: var(
148
+ --button-bg-active,
149
+ color-mix(in oklch, var(--color-neutral) 78%, black)
150
+ );
151
+ }
152
+ }
153
+ .button--tertiary {
154
+ --button-tone: var(--color-foreground);
155
+ --button-color: var(--color-background);
156
+
157
+ &:hover {
158
+ background: var(
159
+ --button-bg-hover,
160
+ color-mix(
161
+ in oklch,
162
+ var(--color-foreground) 88%,
163
+ var(--color-background)
164
+ )
165
+ );
166
+ }
167
+ &:active,
168
+ &[aria-pressed="true"],
169
+ &[data-state="active"] {
170
+ background: var(
171
+ --button-bg-active,
172
+ color-mix(
173
+ in oklch,
174
+ var(--color-foreground) 78%,
175
+ var(--color-background)
176
+ )
177
+ );
178
+ }
179
+ }
180
+
181
+ /* === Shapes === */
182
+ .button--outline {
183
+ --button-bg: transparent;
184
+ --button-color: var(--button-tone);
185
+ --button-border-color: var(--button-tone);
186
+ --button-bevel: none;
187
+
188
+ &:hover {
189
+ background: var(
190
+ --button-bg-hover,
191
+ color-mix(in oklch, var(--button-tone) 14%, transparent)
192
+ );
193
+ }
194
+ &:active,
195
+ &[aria-pressed="true"],
196
+ &[data-state="active"] {
197
+ background: var(
198
+ --button-bg-active,
199
+ color-mix(in oklch, var(--button-tone) 22%, transparent)
200
+ );
201
+ }
202
+ }
203
+ .button--ghost {
204
+ --button-bg: transparent;
205
+ --button-color: var(--button-tone);
206
+ --button-border-color: transparent;
207
+ --button-bevel: none;
208
+
209
+ &:hover {
210
+ background: var(
211
+ --button-bg-hover,
212
+ color-mix(in oklch, var(--button-tone) 18%, transparent)
213
+ );
214
+ }
215
+ &:active,
216
+ &[aria-pressed="true"],
217
+ &[data-state="active"] {
218
+ background: var(
219
+ --button-bg-active,
220
+ color-mix(in oklch, var(--button-tone) 26%, transparent)
221
+ );
222
+ }
223
+ }
224
+ .button--soft {
225
+ --button-bg: color-mix(in oklch, var(--button-tone) 12%, transparent);
226
+ --button-color: var(--button-tone);
227
+ --button-border-color: transparent;
228
+ --button-bevel: none;
229
+
230
+ &:hover {
231
+ background: var(
232
+ --button-bg-hover,
233
+ color-mix(in oklch, var(--button-tone) 20%, transparent)
234
+ );
235
+ }
236
+ &:active,
237
+ &[aria-pressed="true"],
238
+ &[data-state="active"] {
239
+ background: var(
240
+ --button-bg-active,
241
+ color-mix(in oklch, var(--button-tone) 28%, transparent)
242
+ );
243
+ }
244
+ }
245
+
246
+ /* === Neutral × shape === */
247
+ .button--outline.button--neutral {
248
+ --button-border-color: var(--color-border);
249
+ &:hover {
250
+ background: var(--button-bg-hover, var(--color-accent));
251
+ }
252
+ &:active,
253
+ &[aria-pressed="true"],
254
+ &[data-state="active"] {
255
+ background: var(
256
+ --button-bg-active,
257
+ color-mix(in oklch, var(--color-accent) 92%, var(--color-foreground))
258
+ );
259
+ }
260
+ }
261
+ .button--ghost.button--neutral {
262
+ &:hover {
263
+ background: var(--button-bg-hover, var(--color-accent));
264
+ }
265
+ &:active,
266
+ &[aria-pressed="true"],
267
+ &[data-state="active"] {
268
+ background: var(
269
+ --button-bg-active,
270
+ color-mix(in oklch, var(--color-accent) 92%, var(--color-foreground))
271
+ );
272
+ }
273
+ }
274
+
275
+ /* === Loading — styled off aria-busy === */
276
+ .button[aria-busy="true"] {
277
+ cursor: progress;
278
+ pointer-events: none;
279
+
280
+ & > :is(svg, i, .button__icon) {
281
+ display: none;
282
+ }
283
+ &::before {
284
+ content: "";
285
+ display: inline-block;
286
+ flex-shrink: 0;
287
+ width: var(--button-icon-size, --spacing(4));
288
+ height: var(--button-icon-size, --spacing(4));
289
+ border: 2px solid currentColor;
290
+ border-right-color: transparent;
291
+ border-radius: 50%;
292
+ animation: st-button-spin 0.75s linear infinite;
293
+ }
294
+ }
295
+
296
+ /* === Flush === */
297
+ .button--flush-start {
298
+ margin-inline-start: calc(-1 * var(--button-padding-inline, --spacing(3)));
299
+ }
300
+ .button--flush-end {
301
+ margin-inline-end: calc(-1 * var(--button-padding-inline, --spacing(3)));
302
+ }
303
+ }
304
+
305
+ @media (prefers-reduced-motion: reduce) {
306
+ .button {
307
+ transition: none;
308
+ }
309
+ .button[aria-busy="true"]::before {
310
+ animation-duration: 3s;
311
+ }
312
+ }
313
+
314
+ @keyframes st-button-spin {
315
+ to {
316
+ transform: rotate(360deg);
317
+ }
318
+ }
@@ -0,0 +1 @@
1
+ export { c as button } from '../config-CARtrJ7I.js';
@@ -0,0 +1,6 @@
1
+ import {
2
+ button
3
+ } from "../chunk-K45KLI3Y.js";
4
+ export {
5
+ button
6
+ };
@@ -0,0 +1,108 @@
1
+ /* @stisla/style — Button group. Ported from src/scss/components/_button-group.scss. A row (or column)
2
+ * of .button members that shares chrome so the cluster reads as one control: outer corners round,
3
+ * inner corners square, adjacent borders dedupe into a single seam. .button-group owns layout; member
4
+ * .button keeps its own paint. Purely visual grouping — no state hooks, no JS. (For press toggles /
5
+ * segmented control, see toggle / toggle-group.)
6
+ *
7
+ * Renamed from the legacy .btn-group → .button-group, members .btn → .button, and child knobs
8
+ * --btn-* → --button-* (full-word BEM, ARCHITECTURE §11). References the @theme tokens; only
9
+ * no-namespace customs use --st-* (border-width). @layer components.
10
+ * Authoring rules: ../../../../PORTING.md */
11
+
12
+ @layer components {
13
+ .button-group,
14
+ .button-group--vertical {
15
+ position: relative;
16
+ display: inline-flex;
17
+ vertical-align: middle;
18
+
19
+ /* Members participate in stacking so the seam-dedupe negative margin doesn't bury hover / focus /
20
+ active borders under the next sibling. Inner corners square; outer edges re-rounded below. */
21
+ > .button {
22
+ position: relative;
23
+ flex: 0 1 auto;
24
+ border-radius: 0;
25
+ /* Soften the rim mix on filled tones so an internal seam (two adjacent rims overlapping at the
26
+ -1px margin) reads as a quiet divider, not a hard frame. Outline/ghost/soft bypass this. */
27
+ --button-rim-mix: 92%;
28
+ }
29
+
30
+ /* Lift the interactive member above its neighbors so its border + ring clear the seam dedupe:
31
+ focus on top, then active, then transient hover. */
32
+ > .button:hover {
33
+ z-index: 1;
34
+ }
35
+ > .button[aria-pressed="true"],
36
+ > .button[data-state="active"] {
37
+ z-index: 2;
38
+ }
39
+ > .button:focus-visible {
40
+ z-index: 3;
41
+ }
42
+ }
43
+
44
+ /* === Horizontal seams === negative inline margin collapses adjacent borders into one seam. */
45
+ .button-group {
46
+ > .button + .button {
47
+ margin-inline-start: calc(-1 * var(--st-border-width));
48
+ }
49
+ > .button:first-child {
50
+ border-start-start-radius: var(--button-group-radius, var(--radius-md));
51
+ border-end-start-radius: var(--button-group-radius, var(--radius-md));
52
+ }
53
+ > .button:last-child {
54
+ border-start-end-radius: var(--button-group-radius, var(--radius-md));
55
+ border-end-end-radius: var(--button-group-radius, var(--radius-md));
56
+ }
57
+ }
58
+
59
+ /* === Vertical seams === same dedupe rotated 90°. */
60
+ .button-group--vertical {
61
+ flex-direction: column;
62
+ align-items: stretch;
63
+
64
+ > .button + .button {
65
+ margin-block-start: calc(-1 * var(--st-border-width));
66
+ }
67
+ > .button:first-child {
68
+ border-start-start-radius: var(--button-group-radius, var(--radius-md));
69
+ border-start-end-radius: var(--button-group-radius, var(--radius-md));
70
+ }
71
+ > .button:last-child {
72
+ border-end-start-radius: var(--button-group-radius, var(--radius-md));
73
+ border-end-end-radius: var(--button-group-radius, var(--radius-md));
74
+ }
75
+ }
76
+
77
+ /* === Sizes (base = md) — retune the child .button's vars in modifier scope; no per-size vars on
78
+ * the group itself, so members stay consistent with standalone .button--sm / .button--lg. */
79
+ .button-group--sm {
80
+ --button-group-radius: var(--radius-sm);
81
+
82
+ > .button {
83
+ --button-height: --spacing(7);
84
+ --button-padding-inline: --spacing(2.5);
85
+ --button-font-size: var(--text-xs);
86
+ --button-radius: var(--radius-sm);
87
+ }
88
+ }
89
+
90
+ .button-group--lg {
91
+ --button-group-radius: var(--radius-lg);
92
+
93
+ > .button {
94
+ --button-height: --spacing(11);
95
+ --button-padding-inline: --spacing(4);
96
+ --button-font-size: var(--text-base);
97
+ --button-radius: var(--radius-lg);
98
+ }
99
+ }
100
+
101
+ /* === Toolbar === a separate block: gaps multiple .button-group children apart without inline flex
102
+ * utilities at each call site. */
103
+ .button-toolbar {
104
+ display: flex;
105
+ flex-wrap: wrap;
106
+ gap: var(--button-toolbar-gap, --spacing(2));
107
+ }
108
+ }