@happyvertical/smrt-ui 0.55.17 → 0.55.19

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 (90) hide show
  1. package/README.md +7 -0
  2. package/dist/components/calendar/CalendarView.svelte +2 -2
  3. package/dist/components/chat/ReactionPicker.svelte +2 -2
  4. package/dist/components/data/CollectionList.svelte +1 -1
  5. package/dist/components/data/CollectionToolbar.svelte +1 -1
  6. package/dist/components/data/DataTable.svelte +44 -32
  7. package/dist/components/data/SortableHeader.svelte +2 -2
  8. package/dist/components/feedback/Alert.svelte +4 -3
  9. package/dist/components/feedback/LoadingOverlay.svelte +2 -2
  10. package/dist/components/feedback/Modal.svelte +11 -9
  11. package/dist/components/feedback/PhoneSheet.svelte +2 -2
  12. package/dist/components/feedback/ToastViewport.svelte +1 -1
  13. package/dist/components/forms/CameraCapture.svelte +2 -2
  14. package/dist/components/forms/Checkbox.svelte +1 -1
  15. package/dist/components/forms/Combobox.svelte +1 -1
  16. package/dist/components/forms/FilePicker.svelte +1 -1
  17. package/dist/components/forms/Input.svelte +11 -7
  18. package/dist/components/forms/InputGroup.svelte +1 -1
  19. package/dist/components/forms/Listbox.svelte +1 -1
  20. package/dist/components/forms/Radio.svelte +1 -1
  21. package/dist/components/forms/RangeSlider.svelte +1 -1
  22. package/dist/components/forms/SearchInput.svelte +2 -2
  23. package/dist/components/forms/SegmentedControl.svelte +1 -1
  24. package/dist/components/forms/Select.svelte +11 -7
  25. package/dist/components/forms/SignaturePad.svelte +2 -2
  26. package/dist/components/forms/Slider.svelte +1 -1
  27. package/dist/components/forms/StagedControlReview.svelte +1 -1
  28. package/dist/components/forms/Switch.svelte +1 -1
  29. package/dist/components/forms/TagsInput.svelte +1 -1
  30. package/dist/components/forms/Textarea.svelte +10 -6
  31. package/dist/components/forms/Toggle.svelte +2 -2
  32. package/dist/components/forms/ToggleButton.svelte +1 -1
  33. package/dist/components/layout/PageHeader.svelte +2 -2
  34. package/dist/components/nav/Tabs.svelte +47 -17
  35. package/dist/components/permissions/PermissionPicker.svelte +1 -1
  36. package/dist/components/roles/RoleSelector.svelte +2 -2
  37. package/dist/components/ui/AccordionItem.svelte +1 -1
  38. package/dist/components/ui/Badge.svelte +2 -1
  39. package/dist/components/ui/Button.svelte +38 -29
  40. package/dist/components/ui/Card.svelte +11 -10
  41. package/dist/components/ui/Chip.svelte +31 -7
  42. package/dist/components/ui/Disclosure.svelte +1 -1
  43. package/dist/components/ui/Dropdown.svelte +20 -15
  44. package/dist/components/ui/IconToggle.svelte +2 -2
  45. package/dist/components/ui/Popover.svelte +4 -4
  46. package/dist/components/ui/Tooltip.svelte +1 -0
  47. package/dist/components/ui/Tree.svelte +2 -2
  48. package/dist/themes/CUSTOM_THEME_GUIDE.md +16 -1
  49. package/dist/themes/README.md +126 -0
  50. package/dist/themes/ThemeProvider.svelte +173 -13
  51. package/dist/themes/ThemeProvider.svelte.d.ts +7 -2
  52. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  53. package/dist/themes/create-theme.d.ts +22 -1
  54. package/dist/themes/create-theme.d.ts.map +1 -1
  55. package/dist/themes/create-theme.js +93 -5
  56. package/dist/themes/css-generator.d.ts.map +1 -1
  57. package/dist/themes/css-generator.js +15 -16
  58. package/dist/themes/derive.d.ts +31 -0
  59. package/dist/themes/derive.d.ts.map +1 -0
  60. package/dist/themes/derive.js +183 -0
  61. package/dist/themes/glass/index.d.ts.map +1 -1
  62. package/dist/themes/glass/index.js +127 -14
  63. package/dist/themes/happyvertical/index.d.ts.map +1 -1
  64. package/dist/themes/happyvertical/index.js +101 -20
  65. package/dist/themes/index.d.ts +2 -2
  66. package/dist/themes/index.d.ts.map +1 -1
  67. package/dist/themes/index.js +1 -1
  68. package/dist/themes/material/index.d.ts.map +1 -1
  69. package/dist/themes/material/index.js +129 -28
  70. package/dist/themes/required-tokens.generated.json +31 -0
  71. package/dist/themes/shared.d.ts +35 -0
  72. package/dist/themes/shared.d.ts.map +1 -1
  73. package/dist/themes/shared.js +32 -0
  74. package/dist/themes/smrt/index.d.ts +3 -0
  75. package/dist/themes/smrt/index.d.ts.map +1 -1
  76. package/dist/themes/smrt/index.js +108 -26
  77. package/dist/themes/studio/index.d.ts.map +1 -1
  78. package/dist/themes/studio/index.js +116 -40
  79. package/dist/themes/styles/base.css +225 -0
  80. package/dist/themes/styles/glass.css +103 -19
  81. package/dist/themes/styles/happyvertical.css +104 -12
  82. package/dist/themes/styles/material.css +127 -35
  83. package/dist/themes/styles/smrt.css +122 -30
  84. package/dist/themes/styles/studio.css +128 -36
  85. package/dist/themes/theme-script.d.ts +3 -1
  86. package/dist/themes/theme-script.d.ts.map +1 -1
  87. package/dist/themes/theme-script.js +7 -2
  88. package/dist/themes/types.d.ts +118 -6
  89. package/dist/themes/types.d.ts.map +1 -1
  90. package/package.json +3 -3
@@ -146,7 +146,7 @@ useControlRegistration(() => {
146
146
  .switch__thumb { display: block; width: 1.125rem; height: 1.125rem; border-radius: 50%; background: var(--smrt-color-outline); transition: transform var(--smrt-duration-short2), background var(--smrt-duration-short2); }
147
147
  input:checked + .switch__track { background: var(--smrt-color-primary); border-color: var(--smrt-color-primary); }
148
148
  input:checked + .switch__track .switch__thumb { transform: translateX(1.25rem); background: var(--smrt-color-on-primary); }
149
- input:focus-visible + .switch__track { outline: 2px solid var(--smrt-color-primary); outline-offset: 3px; }
149
+ input:focus-visible + .switch__track { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
150
150
  .switch--sm { font-size: .8rem; transform-origin: left center; }
151
151
  .switch--lg .switch__track { transform: scale(1.15); margin-inline: .2rem; }
152
152
  .switch[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; border-radius: var(--smrt-radius-full); }
@@ -147,7 +147,7 @@ useControlRegistration(() => {
147
147
  .tag { padding-inline-end: 0; }
148
148
  .tag button { min-inline-size: 2.75rem; min-block-size: 2.75rem; font-size: 1.25rem; }
149
149
  }
150
- .tag button:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: -2px; border-radius: var(--smrt-radius-full); }
150
+ .tag button:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: calc(var(--smrt-focus-ring-width) * -1); border-radius: var(--smrt-radius-full); }
151
151
  .tags > input { flex: 1; min-width: 8rem; border: 0; outline: 0; background: transparent; color: inherit; }
152
152
  :global(.tags[data-smrt-highlighted='true']) { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
153
153
  }
@@ -155,9 +155,10 @@ export function getElement(): HTMLTextAreaElement | null {
155
155
  font-size: var(--smrt-typography-body-medium-size, 0.875rem);
156
156
  line-height: var(--smrt-typography-body-medium-line-height, 1.5);
157
157
  color: var(--smrt-color-on-surface);
158
- background-color: var(--smrt-color-surface);
159
- border: 1px solid var(--smrt-color-outline);
160
- border-radius: 0.375rem;
158
+ background-color: var(--smrt-surface-control);
159
+ font-weight: var(--smrt-control-weight);
160
+ border: var(--smrt-hairline-strong);
161
+ border-radius: var(--smrt-control-radius);
161
162
  resize: vertical;
162
163
  min-height: 80px;
163
164
  transition:
@@ -166,9 +167,12 @@ export function getElement(): HTMLTextAreaElement | null {
166
167
  }
167
168
 
168
169
  .textarea:focus {
169
- outline: none;
170
170
  border-color: var(--smrt-color-primary);
171
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--smrt-color-primary) 10%, transparent);
171
+ }
172
+
173
+ .textarea:focus-visible {
174
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
175
+ outline-offset: var(--smrt-focus-ring-offset);
172
176
  }
173
177
 
174
178
  .textarea[data-smrt-highlighted='true'] {
@@ -179,7 +183,7 @@ export function getElement(): HTMLTextAreaElement | null {
179
183
  .textarea:disabled {
180
184
  background-color: var(--smrt-color-surface-container-high);
181
185
  cursor: not-allowed;
182
- opacity: 0.7;
186
+ opacity: var(--smrt-state-disabled);
183
187
  }
184
188
 
185
189
  .textarea::placeholder {
@@ -149,8 +149,8 @@ const sizeClasses = {
149
149
 
150
150
  /* Focus state */
151
151
  .toggle__input:focus-visible + .toggle__thumb {
152
- outline: 2px solid var(--smrt-color-primary);
153
- outline-offset: 2px;
152
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
153
+ outline-offset: var(--smrt-focus-ring-offset);
154
154
  }
155
155
 
156
156
  /* Hover state */
@@ -96,7 +96,7 @@ useControlRegistration(() => {
96
96
  @layer smrt.theme {
97
97
  .toggle-button { display: inline-flex; align-items: center; justify-content: center; min-height: 2.5rem; padding: 0 var(--smrt-spacing-4); border: 1px solid var(--smrt-color-outline); border-radius: var(--smrt-radius-full); background: var(--smrt-color-surface); color: var(--smrt-color-on-surface); cursor: pointer; }
98
98
  .toggle-button.pressed { background: var(--smrt-color-secondary-container); color: var(--smrt-color-on-secondary-container); border-color: var(--smrt-color-secondary); }
99
- .toggle-button:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
99
+ .toggle-button:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
100
100
  .toggle-button:disabled { opacity: .5; cursor: not-allowed; }
101
101
  .toggle-button[data-smrt-highlighted='true'] { outline: 3px solid var(--smrt-color-tertiary); outline-offset: 4px; }
102
102
  }
@@ -187,8 +187,8 @@ $effect(() => shell?.report?.({ title: shortTitle ?? title, parents }));
187
187
  }
188
188
 
189
189
  .page-crumbs a:focus-visible {
190
- outline: 2px solid var(--smrt-color-primary);
191
- outline-offset: 2px;
190
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
191
+ outline-offset: var(--smrt-focus-ring-offset);
192
192
  border-radius: var(--smrt-radius-sm);
193
193
  }
194
194
 
@@ -280,7 +280,7 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
280
280
 
281
281
  .tabs-nav {
282
282
  display: flex;
283
- border-bottom: 1px solid var(--smrt-color-surface-variant);
283
+ border-bottom: var(--smrt-hairline);
284
284
  width: 100%;
285
285
  }
286
286
 
@@ -293,10 +293,11 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
293
293
  padding: 0 var(--smrt-spacing-4);
294
294
  height: 48px;
295
295
  font: var(--smrt-typography-title-small-font);
296
- font-weight: var(--smrt-typography-weight-medium, 500);
296
+ font-weight: var(--smrt-control-weight);
297
297
  color: var(--smrt-color-on-surface-variant);
298
298
  background: transparent;
299
299
  border: none;
300
+ border-radius: var(--smrt-control-radius) var(--smrt-control-radius) 0 0;
300
301
  cursor: pointer;
301
302
  transition: all 200ms cubic-bezier(0.2, 0, 0, 1);
302
303
  position: relative;
@@ -314,14 +315,35 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
314
315
  font: var(--smrt-typography-title-medium-font);
315
316
  }
316
317
 
318
+ /* State layer: currentColor mixed at the theme's state opacity. */
319
+ .tab-button::after {
320
+ content: '';
321
+ position: absolute;
322
+ inset: 0;
323
+ border-radius: inherit;
324
+ background: transparent;
325
+ pointer-events: none;
326
+ }
327
+
317
328
  .tab-button:hover:not(:disabled) {
318
- background-color: var(--smrt-color-surface-container-high);
319
329
  color: var(--smrt-color-on-surface);
320
330
  }
321
331
 
332
+ .tab-button:hover:not(:disabled)::after {
333
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent);
334
+ }
335
+
336
+ .tab-button:active:not(:disabled)::after {
337
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-pressed) * 100%), transparent);
338
+ }
339
+
340
+ .tab-button.active::after {
341
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-selected) * 100%), transparent);
342
+ }
343
+
322
344
  .tab-button:focus-visible {
323
- outline: 2px solid var(--smrt-color-primary);
324
- outline-offset: -2px;
345
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
346
+ outline-offset: calc(var(--smrt-focus-ring-width) * -1);
325
347
  }
326
348
 
327
349
  .tab-button.active {
@@ -329,7 +351,7 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
329
351
  }
330
352
 
331
353
  .tab-button:disabled {
332
- opacity: 0.38;
354
+ opacity: var(--smrt-state-disabled);
333
355
  cursor: not-allowed;
334
356
  }
335
357
 
@@ -375,7 +397,7 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
375
397
  min-width: 1.25rem;
376
398
  padding: 0.05rem 0.4rem;
377
399
  border-radius: 999px;
378
- background: var(--smrt-color-surface-container-high);
400
+ background: var(--smrt-surface-sunken);
379
401
  color: var(--smrt-color-on-surface);
380
402
  font: var(--smrt-typography-label-small-font);
381
403
  font-weight: var(--smrt-typography-weight-bold, 700);
@@ -388,7 +410,7 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
388
410
  align-items: stretch;
389
411
  gap: var(--smrt-spacing-1);
390
412
  width: 100%;
391
- border-bottom: 1px solid var(--smrt-color-outline-variant);
413
+ border-bottom: var(--smrt-hairline);
392
414
  }
393
415
 
394
416
  .tabs-links-row {
@@ -405,20 +427,22 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
405
427
  flex: 0 0 auto;
406
428
  align-items: center;
407
429
  gap: var(--smrt-spacing-2);
408
- min-height: 44px;
430
+ min-height: var(--smrt-control-height);
409
431
  padding: 0 var(--smrt-spacing-4);
410
432
  margin-bottom: -1px;
433
+ border-radius: var(--smrt-control-radius) var(--smrt-control-radius) 0 0;
411
434
  border-bottom: 2px solid transparent;
412
435
  color: var(--smrt-color-on-surface-variant);
413
436
  font: var(--smrt-typography-title-small-font);
414
- font-weight: var(--smrt-typography-weight-semibold, 600);
437
+ font-weight: var(--smrt-control-weight);
415
438
  text-decoration: none;
416
439
  white-space: nowrap;
417
440
  cursor: pointer;
418
441
  }
419
442
 
420
443
  .tabs-links.sm .tab-link {
421
- min-height: 40px;
444
+ min-height: var(--smrt-control-height-small);
445
+ border-radius: var(--smrt-control-radius-small) var(--smrt-control-radius-small) 0 0;
422
446
  font: var(--smrt-typography-label-large-font);
423
447
  }
424
448
 
@@ -429,6 +453,11 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
429
453
 
430
454
  .tab-link:hover {
431
455
  color: var(--smrt-color-on-surface);
456
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent);
457
+ }
458
+
459
+ .tab-link:active {
460
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-pressed) * 100%), transparent);
432
461
  }
433
462
 
434
463
  .tab-link.active {
@@ -437,13 +466,13 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
437
466
  }
438
467
 
439
468
  .tab-link[aria-disabled='true'] {
440
- opacity: 0.38;
469
+ opacity: var(--smrt-state-disabled);
441
470
  cursor: not-allowed;
442
471
  }
443
472
 
444
473
  .tab-link:focus-visible {
445
- outline: 2px solid var(--smrt-color-primary);
446
- outline-offset: -2px;
474
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
475
+ outline-offset: calc(var(--smrt-focus-ring-width) * -1);
447
476
  }
448
477
 
449
478
  .tab-dot {
@@ -473,10 +502,11 @@ function handleKeydown(event: KeyboardEvent, tabId: string) {
473
502
  display: grid;
474
503
  min-width: 12rem;
475
504
  padding: var(--smrt-spacing-1);
476
- border: 1px solid var(--smrt-color-outline-variant);
505
+ border: var(--smrt-hairline);
477
506
  border-radius: var(--smrt-radius-small, 0.5rem);
478
- background: var(--smrt-color-surface);
479
- box-shadow: var(--smrt-elevation-2);
507
+ background: var(--smrt-glass-surface-strong);
508
+ backdrop-filter: blur(var(--smrt-glass-blur)) saturate(var(--smrt-glass-saturation));
509
+ box-shadow: var(--smrt-inset-highlight), var(--smrt-elevation-3);
480
510
  }
481
511
 
482
512
  .tabs-more-menu .tab-link {
@@ -201,7 +201,7 @@ function matches(permission: PermissionPickerItem, category: string): boolean {
201
201
  .groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); gap: var(--smrt-spacing-4); align-items: start; }
202
202
  .group { min-inline-size: 0; border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface); }
203
203
  summary { display: flex; min-block-size: 2.75rem; align-items: center; justify-content: space-between; gap: var(--smrt-spacing-3); padding: var(--smrt-spacing-3) var(--smrt-spacing-4); color: var(--smrt-color-on-surface); font: var(--smrt-typography-title-medium-font); cursor: pointer; }
204
- summary:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
204
+ summary:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
205
205
  .selected-count { flex: 0 0 auto; color: var(--smrt-color-on-surface-variant); font: var(--smrt-typography-label-medium-font); }
206
206
  .choices { display: grid; gap: var(--smrt-spacing-4); padding: 0 var(--smrt-spacing-4) var(--smrt-spacing-4); border-block-start: 1px solid var(--smrt-color-outline-variant); padding-block-start: var(--smrt-spacing-4); }
207
207
  .choice { display: grid; min-inline-size: 0; gap: var(--smrt-spacing-1); }
@@ -287,8 +287,8 @@ $effect(() => {
287
287
  (#2322). */
288
288
  .option:focus-visible {
289
289
  background: var(--smrt-color-surface-container-high);
290
- outline: 2px solid var(--smrt-color-primary);
291
- outline-offset: -2px;
290
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
291
+ outline-offset: calc(var(--smrt-focus-ring-width) * -1);
292
292
  }
293
293
 
294
294
  .option.selected {
@@ -39,7 +39,7 @@ const open = $derived(context.isOpen(value));
39
39
  h3 { margin: 0; }
40
40
  button { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: var(--smrt-spacing-3); padding: var(--smrt-spacing-4); border: 0; background: transparent; color: var(--smrt-color-on-surface); font: var(--smrt-typography-title-small-font); text-align: left; cursor: pointer; }
41
41
  button:hover:not(:disabled) { background: var(--smrt-color-surface-container-low); }
42
- button:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: -2px; }
42
+ button:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: calc(var(--smrt-focus-ring-width) * -1); }
43
43
  button:disabled { opacity: .5; cursor: not-allowed; }
44
44
  .chevron { transition: transform var(--smrt-duration-short4) var(--smrt-easing-standard); }
45
45
  button[aria-expanded='true'] .chevron { transform: rotate(180deg); }
@@ -26,7 +26,8 @@ const { variant = 'default', size = 'md', children, ...rest }: Props = $props();
26
26
  display: inline-flex;
27
27
  align-items: center;
28
28
  justify-content: center;
29
- font-weight: var(--smrt-typography-body-medium-weight);
29
+ font-weight: var(--smrt-control-weight);
30
+ border: var(--smrt-hairline);
30
31
  border-radius: var(--smrt-radius-full);
31
32
  white-space: nowrap;
32
33
  }
@@ -166,16 +166,46 @@ function handleClick(event: MouseEvent) {
166
166
  align-items: center;
167
167
  justify-content: center;
168
168
  gap: var(--smrt-spacing-2);
169
- font-weight: var(--smrt-typography-body-medium-weight);
170
- border-radius: var(--smrt-radius-medium);
169
+ position: relative;
170
+ font-weight: var(--smrt-control-weight);
171
+ border-radius: var(--smrt-control-radius);
171
172
  transition: all var(--smrt-duration-short2) var(--smrt-easing-standard);
172
173
  cursor: pointer;
173
174
  text-decoration: none;
174
175
  border: 1px solid transparent;
175
176
  }
176
177
 
178
+ /* One state layer for every variant: currentColor mixed at the theme's
179
+ state opacity. Per-variant tweaks use --smrt-button-state-* names. */
180
+ .button::after {
181
+ content: '';
182
+ position: absolute;
183
+ inset: 0;
184
+ border-radius: inherit;
185
+ background: transparent;
186
+ pointer-events: none;
187
+ transition: background-color var(--smrt-duration-short2) var(--smrt-easing-standard);
188
+ }
189
+
190
+ .button:hover:not(:disabled):not(.disabled)::after {
191
+ background: color-mix(
192
+ in srgb,
193
+ currentColor calc(var(--smrt-state-hover) * 100%),
194
+ transparent
195
+ );
196
+ }
197
+
198
+ .button:active:not(:disabled):not(.disabled)::after {
199
+ background: color-mix(
200
+ in srgb,
201
+ currentColor calc(var(--smrt-state-pressed) * 100%),
202
+ transparent
203
+ );
204
+ }
205
+
177
206
  /* Sizes */
178
207
  .sm {
208
+ border-radius: var(--smrt-control-radius-small);
179
209
  padding: var(--smrt-spacing-2) var(--smrt-spacing-3);
180
210
  font-size: var(--smrt-typography-body-medium-size);
181
211
  }
@@ -196,47 +226,25 @@ function handleClick(event: MouseEvent) {
196
226
  color: var(--smrt-color-on-primary);
197
227
  }
198
228
 
199
- /* M3 hover state layer: 8% of the on-color blended over the base, so the
200
- button visibly darkens on hover instead of repeating its base color. */
201
- .primary:hover:not(:disabled):not(.disabled) {
202
- background: color-mix(
203
- in srgb,
204
- var(--smrt-color-on-primary) 8%,
205
- var(--smrt-color-primary)
206
- );
207
- }
208
229
 
209
230
  .secondary {
210
- background: var(--smrt-color-surface);
231
+ background: var(--smrt-surface-control);
211
232
  color: var(--smrt-color-primary);
212
233
  border-color: var(--smrt-color-primary);
213
234
  }
214
235
 
215
- .secondary:hover:not(:disabled):not(.disabled) {
216
- background: var(--smrt-color-primary-container);
217
- }
218
236
 
219
237
  .ghost {
220
238
  background: transparent;
221
239
  color: var(--smrt-color-primary);
222
240
  }
223
241
 
224
- .ghost:hover:not(:disabled):not(.disabled) {
225
- background: var(--smrt-color-primary-container);
226
- }
227
242
 
228
243
  .danger {
229
244
  background: var(--smrt-color-error);
230
245
  color: var(--smrt-color-on-error);
231
246
  }
232
247
 
233
- .danger:hover:not(:disabled):not(.disabled) {
234
- background: color-mix(
235
- in srgb,
236
- var(--smrt-color-on-error) 8%,
237
- var(--smrt-color-error)
238
- );
239
- }
240
248
 
241
249
  /* Full width */
242
250
  .full-width {
@@ -289,15 +297,15 @@ function handleClick(event: MouseEvent) {
289
297
  /* Disabled state */
290
298
  .button:disabled,
291
299
  .button.disabled {
292
- opacity: 0.5;
300
+ opacity: var(--smrt-state-disabled);
293
301
  cursor: not-allowed;
294
302
  pointer-events: none;
295
303
  }
296
304
 
297
305
  /* Focus state */
298
306
  .button:focus-visible {
299
- outline: 2px solid var(--smrt-color-primary);
300
- outline-offset: 2px;
307
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
308
+ outline-offset: var(--smrt-focus-ring-offset);
301
309
  }
302
310
 
303
311
  /* Phones: every size is at least a 44px touch target. */
@@ -316,6 +324,7 @@ function handleClick(event: MouseEvent) {
316
324
 
317
325
  .button[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min); }
318
326
  .button[data-density='comfortable'] { --smrt-control-target-min: 0px; }
319
- .button { min-height: var(--smrt-control-target-min, 0px); min-width: var(--smrt-control-target-min, 0px); }
327
+ .button { min-height: max(var(--smrt-control-height), var(--smrt-control-target-min, 0px)); min-width: var(--smrt-control-target-min, 0px); }
328
+ .button.sm { min-height: max(var(--smrt-control-height-small), var(--smrt-control-target-min, 0px)); }
320
329
  }
321
330
  </style>
@@ -63,29 +63,30 @@ const {
63
63
  * The `outlined` variant always draws its outline.
64
64
  */
65
65
  .card {
66
- background: var(--smrt-card-background, var(--smrt-color-surface));
66
+ background: var(--smrt-card-background, var(--smrt-surface-panel));
67
67
  border-radius: var(--smrt-radius-medium);
68
68
  transition: all var(--smrt-duration-short2) var(--smrt-easing-standard);
69
69
  }
70
70
 
71
71
  /* Variants */
72
72
  .default {
73
- border: var(--smrt-card-border, 1px solid var(--smrt-color-outline-variant));
74
- box-shadow: var(--smrt-card-shadow, none);
73
+ border: var(--smrt-card-border, var(--smrt-hairline));
74
+ box-shadow: var(--smrt-card-shadow, var(--smrt-inset-highlight));
75
75
  }
76
76
 
77
77
  .outlined {
78
- border: 2px solid var(--smrt-color-outline);
78
+ border: var(--smrt-hairline-strong);
79
+ box-shadow: var(--smrt-inset-highlight);
79
80
  }
80
81
 
81
82
  .elevated {
82
- border: var(--smrt-card-border, 1px solid var(--smrt-color-outline-variant));
83
- box-shadow: var(--smrt-elevation-2);
83
+ border: var(--smrt-card-border, var(--smrt-hairline));
84
+ box-shadow: var(--smrt-inset-highlight), var(--smrt-elevation-2);
84
85
  }
85
86
 
86
87
  /* Hoverable state */
87
88
  .hoverable:hover {
88
- box-shadow: var(--smrt-elevation-3);
89
+ box-shadow: var(--smrt-inset-highlight), var(--smrt-elevation-3);
89
90
  transform: translateY(-2px);
90
91
  }
91
92
 
@@ -109,7 +110,7 @@ const {
109
110
  /* Header */
110
111
  .card-header {
111
112
  padding: var(--smrt-spacing-6);
112
- border-bottom: var(--smrt-card-divider, 1px solid var(--smrt-color-outline-variant));
113
+ border-bottom: var(--smrt-card-divider, var(--smrt-hairline));
113
114
  }
114
115
 
115
116
  .padding-sm .card-header {
@@ -123,8 +124,8 @@ const {
123
124
  /* Footer */
124
125
  .card-footer {
125
126
  padding: var(--smrt-spacing-6);
126
- border-top: var(--smrt-card-divider, 1px solid var(--smrt-color-outline-variant));
127
- background: var(--smrt-color-surface-container-low);
127
+ border-top: var(--smrt-card-divider, var(--smrt-hairline));
128
+ background: var(--smrt-surface-sunken);
128
129
  border-bottom-left-radius: var(--smrt-radius-medium);
129
130
  border-bottom-right-radius: var(--smrt-radius-medium);
130
131
  }
@@ -96,21 +96,45 @@ const {
96
96
  @layer smrt.reset, smrt.structure, smrt.theme;
97
97
  @layer smrt.theme {
98
98
  .chip {
99
+ position: relative;
99
100
  display: inline-flex;
100
101
  align-items: center;
101
102
  gap: var(--smrt-spacing-1);
102
- border-radius: var(--smrt-radius-full);
103
- background: var(--smrt-color-surface-container-high);
103
+ border-radius: var(--smrt-control-radius);
104
+ background: var(--smrt-surface-control);
104
105
  color: var(--smrt-color-on-surface);
105
- border: 1px solid var(--smrt-color-outline-variant);
106
+ border: var(--smrt-hairline);
107
+ font-weight: var(--smrt-control-weight);
106
108
  max-width: 100%;
107
109
  }
108
110
 
111
+ /* State layer: currentColor mixed at the theme's state opacity. */
112
+ .chip::after {
113
+ content: '';
114
+ position: absolute;
115
+ inset: 0;
116
+ border-radius: inherit;
117
+ background: transparent;
118
+ pointer-events: none;
119
+ }
120
+
121
+ .chip.selectable:hover::after {
122
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent);
123
+ }
124
+
125
+ .chip.selectable:active::after {
126
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-pressed) * 100%), transparent);
127
+ }
128
+
109
129
  .chip.sm {
110
130
  padding: 0 var(--smrt-spacing-1);
111
131
  font-size: var(--smrt-typography-label-small-size, 0.6875rem);
112
132
  }
133
+ .chip.sm {
134
+ border-radius: var(--smrt-control-radius-small);
135
+ }
113
136
  .chip.md {
137
+ min-block-size: var(--smrt-control-height-small);
114
138
  padding: 0 var(--smrt-spacing-2);
115
139
  font-size: var(--smrt-typography-label-large-size, 0.875rem);
116
140
  }
@@ -122,7 +146,7 @@ const {
122
146
  }
123
147
 
124
148
  .chip.disabled {
125
- opacity: 0.5;
149
+ opacity: var(--smrt-state-disabled);
126
150
  pointer-events: none;
127
151
  }
128
152
 
@@ -158,13 +182,13 @@ const {
158
182
  }
159
183
  .chip__close:hover {
160
184
  opacity: 1;
161
- background: color-mix(in srgb, currentColor 12%, transparent);
185
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent);
162
186
  }
163
187
 
164
188
  .chip__body:focus-visible,
165
189
  .chip__close:focus-visible {
166
- outline: 2px solid var(--smrt-color-primary);
167
- outline-offset: 1px;
190
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
191
+ outline-offset: var(--smrt-focus-ring-offset);
168
192
  }
169
193
  }
170
194
  </style>
@@ -49,7 +49,7 @@ function handleToggle(event: Event) {
49
49
  .disclosure { border-bottom: 1px solid var(--smrt-color-outline-variant); color: var(--smrt-color-on-surface); }
50
50
  summary { display: flex; justify-content: space-between; gap: var(--smrt-spacing-3); padding: var(--smrt-spacing-3) 0; font: var(--smrt-typography-title-small-font); cursor: pointer; list-style: none; }
51
51
  summary::-webkit-details-marker { display: none; }
52
- summary:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
52
+ summary:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
53
53
  summary[aria-disabled='true'] { opacity: .5; cursor: not-allowed; }
54
54
  .chevron { transition: transform var(--smrt-duration-short4) var(--smrt-easing-standard); }
55
55
  details[open] .chevron { transform: rotate(180deg); }
@@ -205,14 +205,15 @@ $effect(() => {
205
205
  font: inherit;
206
206
  cursor: pointer;
207
207
  padding: var(--smrt-spacing-2) var(--smrt-spacing-3);
208
- border-radius: var(--smrt-radius-medium);
209
- border: 1px solid var(--smrt-color-outline);
210
- background: var(--smrt-color-surface);
208
+ border-radius: var(--smrt-control-radius);
209
+ border: var(--smrt-hairline-strong);
210
+ background: var(--smrt-surface-control);
211
211
  color: var(--smrt-color-on-surface);
212
+ font-weight: var(--smrt-control-weight);
212
213
  }
213
214
  .dropdown__trigger:focus-visible {
214
- outline: 2px solid var(--smrt-color-primary);
215
- outline-offset: 2px;
215
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
216
+ outline-offset: var(--smrt-focus-ring-offset);
216
217
  }
217
218
  /* Icon trigger: a 44px round "more" button in the surrounding text colour. */
218
219
  .dropdown__trigger--icon {
@@ -230,10 +231,10 @@ $effect(() => {
230
231
  }
231
232
  .dropdown__trigger--icon:hover:not(:disabled),
232
233
  .dropdown__trigger--icon[aria-expanded='true'] {
233
- background: color-mix(in srgb, currentColor 10%, transparent);
234
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent);
234
235
  }
235
236
  .dropdown__trigger:disabled {
236
- opacity: 0.5;
237
+ opacity: var(--smrt-state-disabled);
237
238
  cursor: not-allowed;
238
239
  }
239
240
 
@@ -247,9 +248,10 @@ $effect(() => {
247
248
  flex-direction: column;
248
249
  gap: var(--smrt-spacing-1);
249
250
  border-radius: var(--smrt-radius-medium);
250
- background: var(--smrt-color-surface-container);
251
- border: 1px solid var(--smrt-color-outline-variant);
252
- box-shadow: var(--smrt-elevation-2, 0 2px 6px rgba(0, 0, 0, 0.15));
251
+ background: var(--smrt-glass-surface-strong);
252
+ backdrop-filter: blur(var(--smrt-glass-blur)) saturate(var(--smrt-glass-saturation));
253
+ border: var(--smrt-hairline);
254
+ box-shadow: var(--smrt-inset-highlight), var(--smrt-elevation-3);
253
255
  }
254
256
 
255
257
  .dropdown__menu--bottom-start {
@@ -293,7 +295,10 @@ $effect(() => {
293
295
  color: var(--smrt-color-on-surface-variant);
294
296
  }
295
297
  .dropdown__item:hover:not(:disabled) {
296
- background: var(--smrt-color-surface-container-high);
298
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent);
299
+ }
300
+ .dropdown__item:active:not(:disabled) {
301
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-pressed) * 100%), transparent);
297
302
  }
298
303
  /* Keyboard focus needs its own indicator: the hover background alone is a
299
304
  ~1.1:1 change against the menu surface in every preset (1.00:1 in glass
@@ -301,12 +306,12 @@ $effect(() => {
301
306
  sits inside the item's padding box rather than overlapping the neighbouring
302
307
  item or the menu's edge (#2322). */
303
308
  .dropdown__item:focus-visible {
304
- background: var(--smrt-color-surface-container-high);
305
- outline: 2px solid var(--smrt-color-primary);
306
- outline-offset: -2px;
309
+ background: color-mix(in srgb, currentColor calc(var(--smrt-state-hover) * 100%), transparent);
310
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
311
+ outline-offset: calc(var(--smrt-focus-ring-width) * -1);
307
312
  }
308
313
  .dropdown__item:disabled {
309
- opacity: 0.5;
314
+ opacity: var(--smrt-state-disabled);
310
315
  cursor: not-allowed;
311
316
  }
312
317
  }
@@ -107,8 +107,8 @@ const name = $derived(count === undefined ? label : `${label} (${count})`);
107
107
  box-shadow: inset 0 0 0 2px var(--smrt-icon-toggle-tone);
108
108
  }
109
109
  .icon-toggle:focus-visible {
110
- outline: 2px solid var(--smrt-color-primary);
111
- outline-offset: 2px;
110
+ outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
111
+ outline-offset: var(--smrt-focus-ring-offset);
112
112
  }
113
113
  .icon-toggle:disabled {
114
114
  opacity: 0.5;