@fluixi-ui/tokens 0.4.0-alpha.1 → 0.4.0-alpha.10

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 (57) hide show
  1. package/dist/css/accordion.css +81 -3
  2. package/dist/css/alert.css +52 -23
  3. package/dist/css/avatar.css +19 -0
  4. package/dist/css/badge.css +129 -5
  5. package/dist/css/base.css +3 -1
  6. package/dist/css/bottom-sheet.css +4 -4
  7. package/dist/css/breadcrumb.css +1 -1
  8. package/dist/css/bulk-editor.css +2 -17
  9. package/dist/css/button.css +76 -16
  10. package/dist/css/calendar.css +9 -7
  11. package/dist/css/card.css +5 -1
  12. package/dist/css/carousel.css +80 -0
  13. package/dist/css/checkbox-radio.css +55 -1
  14. package/dist/css/date-field.css +1 -1
  15. package/dist/css/date-picker.css +2 -2
  16. package/dist/css/descriptions.css +29 -0
  17. package/dist/css/drawer.css +4 -4
  18. package/dist/css/empty.css +40 -0
  19. package/dist/css/fab.css +48 -0
  20. package/dist/css/field-variants.css +139 -0
  21. package/dist/css/file-upload.css +40 -0
  22. package/dist/css/glow.css +127 -0
  23. package/dist/css/icon-button.css +42 -0
  24. package/dist/css/image.css +67 -0
  25. package/dist/css/input.css +15 -0
  26. package/dist/css/layout.css +10 -0
  27. package/dist/css/navigation-menu.css +8 -7
  28. package/dist/css/number-field.css +3 -3
  29. package/dist/css/pagination.css +3 -3
  30. package/dist/css/parts/listbox.css +9 -4
  31. package/dist/css/pin-input.css +40 -0
  32. package/dist/css/progress.css +56 -0
  33. package/dist/css/rating.css +59 -0
  34. package/dist/css/scroll-area.css +44 -0
  35. package/dist/css/select-combobox.css +19 -4
  36. package/dist/css/sidenav.css +180 -10
  37. package/dist/css/slider.css +4 -4
  38. package/dist/css/speed-dial.css +85 -0
  39. package/dist/css/statistic.css +34 -0
  40. package/dist/css/stepper.css +3 -3
  41. package/dist/css/surface.css +71 -0
  42. package/dist/css/switch.css +12 -0
  43. package/dist/css/table.css +11 -0
  44. package/dist/css/tabs.css +10 -1
  45. package/dist/css/tags-input.css +91 -0
  46. package/dist/css/timeline.css +59 -0
  47. package/dist/css/toggle.css +6 -0
  48. package/dist/css/tree-view.css +70 -0
  49. package/dist/lib/css/index.d.ts +8 -12
  50. package/dist/lib/css/index.d.ts.map +1 -1
  51. package/dist/lib/css/index.js +19 -7
  52. package/dist/lib/reference/colors.d.ts.map +1 -1
  53. package/dist/lib/reference/colors.js +15 -1
  54. package/dist/preset.css +20 -1
  55. package/dist/tokens.css +52 -4
  56. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  57. package/package.json +2 -2
@@ -1,7 +1,85 @@
1
1
  @layer flx.components {
2
- /* Accordion */
3
- :where([role='region']) {
4
- padding-bottom: var(--flx-ui-space-3);
2
+ /* ----------------------------------------------------------------------------
3
+ * Accordion — a bordered stack of collapsible sections. The trigger is a full
4
+ * width header row with a chevron that rotates on open; content reveals with a
5
+ * gentle slide.
6
+ * ------------------------------------------------------------------------- */
7
+ :where([data-accordion]) {
8
+ border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
9
+ border-radius: var(--flx-ui-radius-md);
10
+ overflow: hidden;
11
+ }
12
+ :where([data-accordion-item]:not(:last-child)) {
13
+ border-bottom: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
14
+ }
15
+
16
+ :where([data-accordion-trigger]) {
17
+ box-sizing: border-box;
18
+ width: 100%;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ gap: var(--flx-ui-space-3);
23
+ padding: var(--flx-ui-space-3) var(--flx-ui-space-4);
24
+ border: none;
25
+ background: none;
26
+ font: inherit;
27
+ font-weight: var(--flx-ui-font-weight-medium);
28
+ color: var(--flx-ui-color-fg);
29
+ text-align: left;
30
+ cursor: pointer;
31
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
32
+ }
33
+ :where([data-accordion-trigger]:hover:not([data-disabled])) {
34
+ background: var(--flx-ui-color-bg-subtle);
35
+ }
36
+ :where([data-accordion-trigger]:focus-visible) {
37
+ outline: 2px solid var(--flx-ui-color-focus-ring);
38
+ outline-offset: -2px;
39
+ }
40
+ :where([data-accordion-trigger][data-disabled]) {
41
+ opacity: var(--flx-ui-opacity-disabled);
42
+ cursor: not-allowed;
43
+ }
44
+ /* Chevron: a crisp masked glyph, centred, that flips 180° when open. */
45
+ :where([data-accordion-trigger])::after {
46
+ content: '';
47
+ width: 1em;
48
+ height: 1em;
49
+ flex: none;
50
+ background: currentColor;
51
+ opacity: 0.55;
52
+ -webkit-mask: var(--flx-ui-chevron-down) center / contain no-repeat;
53
+ mask: var(--flx-ui-chevron-down) center / contain no-repeat;
54
+ transition: transform var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
55
+ }
56
+ :where([data-accordion-trigger][data-state='open'])::after {
57
+ transform: rotate(180deg);
58
+ }
59
+
60
+ :where([data-accordion-content]) {
61
+ padding: 0 var(--flx-ui-space-4) var(--flx-ui-space-3);
5
62
  color: var(--flx-ui-color-fg-muted);
6
63
  }
64
+ :where([data-accordion-content][data-state='open']) {
65
+ animation: flx-ui-accordion-in var(--flx-ui-duration-normal) var(--flx-ui-ease-standard);
66
+ }
67
+
68
+ @keyframes flx-ui-accordion-in {
69
+ from {
70
+ opacity: 0;
71
+ transform: translateY(-4px);
72
+ }
73
+ to {
74
+ opacity: 1;
75
+ transform: none;
76
+ }
77
+ }
78
+ @media (prefers-reduced-motion: reduce) {
79
+ :where([data-accordion-trigger])::after,
80
+ :where([data-accordion-content][data-state='open']) {
81
+ transition: none;
82
+ animation: none;
83
+ }
84
+ }
7
85
  }
@@ -1,42 +1,71 @@
1
1
  @layer flx.components {
2
2
  /* ----------------------------------------------------------------------------
3
- * Alert
3
+ * Alert — a tinted callout with an optional leading icon. The icon column spans
4
+ * the title + description rows; without an icon the content sits flush left.
4
5
  * ------------------------------------------------------------------------- */
5
6
  :where([data-alert]) {
6
- display: flex;
7
- flex-direction: column;
8
- align-items: flex-start;
9
- gap: var(--flx-ui-space-1);
10
- text-align: left;
11
- padding-block: var(--flx-ui-space-4);
12
- padding-inline: var(--flx-ui-space-4);
7
+ display: grid;
8
+ grid-template-columns: auto minmax(0, 1fr);
9
+ row-gap: var(--flx-ui-space-1);
10
+ align-items: start;
11
+ padding: var(--flx-ui-space-3) var(--flx-ui-space-4);
13
12
  border: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
14
- border-left-width: 3px;
15
13
  border-radius: var(--flx-ui-radius-md);
16
14
  background: var(--flx-ui-color-bg-subtle);
17
15
  color: var(--flx-ui-color-fg);
18
16
  }
19
- :where([data-alert][data-variant='info']) {
20
- border-color: var(--flx-ui-color-primary);
21
- background: var(--flx-ui-color-primary-subtle);
17
+ :where([data-alert]) > * {
18
+ grid-column: 2;
19
+ min-width: 0;
22
20
  }
23
- :where([data-alert][data-variant='success']) {
24
- border-color: var(--flx-ui-color-success);
25
- background: var(--flx-ui-color-success-subtle);
21
+ :where([data-alert-icon]) {
22
+ grid-column: 1;
23
+ grid-row: 1 / -1;
24
+ margin-right: var(--flx-ui-space-3);
25
+ display: inline-flex;
26
+ flex: none;
27
+ color: var(--flx-ui-color-fg-muted);
26
28
  }
27
- :where([data-alert][data-variant='warning']) {
28
- border-color: var(--flx-ui-color-warning);
29
- background: var(--flx-ui-color-warning-subtle);
30
- }
31
- :where([data-alert][data-variant='danger']) {
32
- border-color: var(--flx-ui-color-danger);
33
- background: var(--flx-ui-color-danger-subtle);
29
+ :where([data-alert-icon]) > svg {
30
+ width: 1.15em;
31
+ height: 1.15em;
34
32
  }
35
33
  :where([data-alert-title]) {
36
34
  font-weight: var(--flx-ui-font-weight-semibold);
35
+ line-height: var(--flx-ui-line-height-tight);
37
36
  }
38
37
  :where([data-alert-description]) {
39
38
  font-size: var(--flx-ui-font-size-sm);
40
- opacity: 0.9;
39
+ color: var(--flx-ui-color-fg-muted);
40
+ }
41
+
42
+ /* Variants tint the surface, border and icon with the matching semantic hue. */
43
+ :where([data-alert][data-alert-variant='info']) {
44
+ border-color: color-mix(in oklab, var(--flx-ui-color-primary) 35%, var(--flx-ui-color-border));
45
+ background: var(--flx-ui-color-primary-subtle);
46
+ }
47
+ :where([data-alert][data-alert-variant='info']) :where([data-alert-icon]) {
48
+ color: var(--flx-ui-color-primary);
49
+ }
50
+ :where([data-alert][data-alert-variant='success']) {
51
+ border-color: color-mix(in oklab, var(--flx-ui-color-success) 35%, var(--flx-ui-color-border));
52
+ background: var(--flx-ui-color-success-subtle);
53
+ }
54
+ :where([data-alert][data-alert-variant='success']) :where([data-alert-icon]) {
55
+ color: var(--flx-ui-color-success);
56
+ }
57
+ :where([data-alert][data-alert-variant='warning']) {
58
+ border-color: color-mix(in oklab, var(--flx-ui-color-warning) 35%, var(--flx-ui-color-border));
59
+ background: var(--flx-ui-color-warning-subtle);
60
+ }
61
+ :where([data-alert][data-alert-variant='warning']) :where([data-alert-icon]) {
62
+ color: var(--flx-ui-color-warning);
63
+ }
64
+ :where([data-alert][data-alert-variant='danger']) {
65
+ border-color: color-mix(in oklab, var(--flx-ui-color-danger) 35%, var(--flx-ui-color-border));
66
+ background: var(--flx-ui-color-danger-subtle);
67
+ }
68
+ :where([data-alert][data-alert-variant='danger']) :where([data-alert-icon]) {
69
+ color: var(--flx-ui-color-danger);
41
70
  }
42
71
  }
@@ -33,4 +33,23 @@
33
33
  width: 100%;
34
34
  height: 100%;
35
35
  }
36
+
37
+ /* ----------------------------------------------------------------------------
38
+ * Avatar group — overlapping cluster
39
+ * ------------------------------------------------------------------------- */
40
+ :where([data-avatar-group]) {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ vertical-align: middle;
44
+ }
45
+ :where([data-avatar-group]) > * {
46
+ /* A ring in the page background so the overlap reads as separate discs. */
47
+ box-shadow: 0 0 0 2px var(--flx-ui-color-bg);
48
+ }
49
+ :where([data-avatar-group]) > * + * {
50
+ margin-inline-start: calc(-1 * var(--flx-ui-avatar-group-spacing, 8px));
51
+ }
52
+ :where([data-avatar-group-overflow]) {
53
+ font-size: var(--flx-ui-font-size-xs);
54
+ }
36
55
  }
@@ -1,12 +1,136 @@
1
1
  @layer flx.components {
2
- /* Badge */
3
- :where([data-variant].flx-ui-badge, .flx-ui-badge[data-variant]) {
4
- height: auto;
5
- padding: 2px var(--flx-ui-space-2);
2
+ /* Badge (from @fluixi-ui/badge). A small status/label pill: a `data-variant` fill
3
+ style (soft/solid/outline) crossed with a semantic `data-tone`. Each tone sets
4
+ three custom props — the strong colour, the soft background, and the on-solid
5
+ foreground — and the variants consume them, so tone × variant needs no per-combo
6
+ rules. */
7
+ :where([data-badge]) {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ gap: var(--flx-ui-space-1);
11
+ min-width: 0;
12
+ /* A compact chip, not a button: tight vertical padding + line-height so the
13
+ height tracks the text, no wide tracking. */
14
+ padding: 1px var(--flx-ui-space-2);
6
15
  border-radius: var(--flx-ui-radius-full);
7
16
  font-size: var(--flx-ui-font-size-xs);
8
17
  font-weight: var(--flx-ui-font-weight-semibold);
9
- letter-spacing: var(--flx-ui-letter-spacing-wide);
18
+ line-height: 1.35;
19
+ white-space: nowrap;
20
+ border: var(--flx-ui-border-width-thin) solid transparent;
10
21
  box-shadow: none;
22
+ /* Each tone sets four props: `color` (strong — solid fill / outline border),
23
+ `text` (a legible-on-tint shade — soft/outline label), `bg` (soft fill) and
24
+ `on` (label on the solid fill). neutral defaults: */
25
+ --flx-ui-badge-color: var(--flx-ui-color-fg-muted);
26
+ --flx-ui-badge-text: var(--flx-ui-color-fg-muted);
27
+ --flx-ui-badge-bg: var(--flx-ui-color-bg-muted);
28
+ --flx-ui-badge-on: var(--flx-ui-color-bg);
29
+ }
30
+
31
+ :where([data-badge][data-tone='primary']),
32
+ :where([data-badge][data-tone='info']) {
33
+ --flx-ui-badge-color: var(--flx-ui-color-primary);
34
+ --flx-ui-badge-text: var(--flx-ui-color-primary-text);
35
+ --flx-ui-badge-bg: var(--flx-ui-color-primary-subtle);
36
+ --flx-ui-badge-on: var(--flx-ui-color-primary-fg);
37
+ }
38
+ :where([data-badge][data-tone='success']) {
39
+ --flx-ui-badge-color: var(--flx-ui-color-success);
40
+ --flx-ui-badge-text: var(--flx-ui-color-success-text);
41
+ --flx-ui-badge-bg: var(--flx-ui-color-success-subtle);
42
+ --flx-ui-badge-on: var(--flx-ui-color-success-fg);
43
+ }
44
+ :where([data-badge][data-tone='warning']) {
45
+ --flx-ui-badge-color: var(--flx-ui-color-warning);
46
+ --flx-ui-badge-text: var(--flx-ui-color-warning-text);
47
+ --flx-ui-badge-bg: var(--flx-ui-color-warning-subtle);
48
+ --flx-ui-badge-on: var(--flx-ui-color-warning-fg);
49
+ }
50
+ :where([data-badge][data-tone='danger']) {
51
+ --flx-ui-badge-color: var(--flx-ui-color-danger);
52
+ --flx-ui-badge-text: var(--flx-ui-color-danger-text);
53
+ --flx-ui-badge-bg: var(--flx-ui-color-danger-subtle);
54
+ --flx-ui-badge-on: var(--flx-ui-color-danger-fg);
55
+ }
56
+
57
+ :where([data-badge][data-variant='soft']) {
58
+ background: var(--flx-ui-badge-bg);
59
+ color: var(--flx-ui-badge-text);
60
+ }
61
+ :where([data-badge][data-variant='solid']) {
62
+ background: var(--flx-ui-badge-color);
63
+ color: var(--flx-ui-badge-on);
64
+ }
65
+ :where([data-badge][data-variant='outline']) {
66
+ color: var(--flx-ui-badge-text);
67
+ border-color: var(--flx-ui-badge-color);
68
+ }
69
+
70
+ /* ----------------------------------------------------------------------------
71
+ * Overlay badge (BadgeAnchor) — a count / dot pinned to a corner of a wrapped
72
+ * element (a Material-style notification badge). Reuses the [data-badge] fill.
73
+ * ------------------------------------------------------------------------- */
74
+ :where([data-badge-anchor]) {
75
+ position: relative;
76
+ display: inline-flex;
77
+ vertical-align: middle;
78
+ }
79
+ :where([data-badge][data-overlay]) {
80
+ position: absolute;
81
+ z-index: 1;
82
+ min-width: 1.25rem;
83
+ height: 1.25rem;
84
+ padding: 0 var(--flx-ui-space-1);
85
+ justify-content: center;
86
+ font-size: var(--flx-ui-font-size-2xs, 0.6875rem);
87
+ font-variant-numeric: tabular-nums;
88
+ pointer-events: none;
89
+ /* A surface-coloured ring so the badge reads as floating above the element. */
90
+ box-shadow: 0 0 0 2px var(--flx-ui-color-bg);
91
+ }
92
+ :where([data-badge][data-overlay][data-dot]) {
93
+ min-width: 0;
94
+ width: 0.5rem;
95
+ height: 0.5rem;
96
+ padding: 0;
97
+ }
98
+ :where([data-badge-anchor][data-placement='top-end']) > [data-overlay] {
99
+ top: 0;
100
+ right: 0;
101
+ transform: translate(50%, -50%);
102
+ }
103
+ :where([data-badge-anchor][data-placement='top-start']) > [data-overlay] {
104
+ top: 0;
105
+ left: 0;
106
+ transform: translate(-50%, -50%);
107
+ }
108
+ :where([data-badge-anchor][data-placement='bottom-end']) > [data-overlay] {
109
+ bottom: 0;
110
+ right: 0;
111
+ transform: translate(50%, 50%);
112
+ }
113
+ :where([data-badge-anchor][data-placement='bottom-start']) > [data-overlay] {
114
+ bottom: 0;
115
+ left: 0;
116
+ transform: translate(-50%, 50%);
117
+ }
118
+ /* Circular anchor (avatar / circle): nudge the badge in along the diagonal so it
119
+ sits on the round edge instead of the bounding-box corner. */
120
+ :where([data-badge-anchor][data-overlap='circular'][data-placement='top-end']) > [data-overlay] {
121
+ top: 14%;
122
+ right: 14%;
123
+ }
124
+ :where([data-badge-anchor][data-overlap='circular'][data-placement='top-start']) > [data-overlay] {
125
+ top: 14%;
126
+ left: 14%;
127
+ }
128
+ :where([data-badge-anchor][data-overlap='circular'][data-placement='bottom-end']) > [data-overlay] {
129
+ bottom: 14%;
130
+ right: 14%;
131
+ }
132
+ :where([data-badge-anchor][data-overlap='circular'][data-placement='bottom-start']) > [data-overlay] {
133
+ bottom: 14%;
134
+ left: 14%;
11
135
  }
12
136
  }
package/dist/css/base.css CHANGED
@@ -1,4 +1,4 @@
1
- @layer flx.tokens, flx.components, flx.skin;
1
+ @layer flx.reset, flx.base, flx.tokens, flx.components, flx.skin;
2
2
 
3
3
  @layer flx.components {
4
4
 
@@ -6,5 +6,7 @@
6
6
  :where(:root) {
7
7
  --flx-ui-ctl-ring: 0 0 0 2px var(--flx-ui-color-bg), 0 0 0 4px var(--flx-ui-color-focus-ring);
8
8
  --flx-ui-ctl-font: var(--flx-ui-font-weight-medium);
9
+ /* Shared chevron glyph (mask source) — colour comes from the consuming rule. */
10
+ --flx-ui-chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
9
11
  }
10
12
  }
@@ -74,13 +74,13 @@
74
74
  align-items: flex-start;
75
75
  justify-content: space-between;
76
76
  gap: var(--flx-ui-space-3);
77
- padding: var(--flx-ui-space-2) var(--flx-ui-space-5) var(--flx-ui-space-4);
77
+ padding: var(--flx-ui-space-2) var(--flx-ui-space-4) var(--flx-ui-space-4);
78
78
  }
79
79
  :where([data-bottom-sheet-title]) {
80
80
  margin: 0;
81
81
  font-size: var(--flx-ui-font-size-lg);
82
82
  font-weight: var(--flx-ui-font-weight-semibold);
83
- line-height: var(--flx-ui-leading-tight);
83
+ line-height: var(--flx-ui-line-height-tight);
84
84
  color: var(--flx-ui-color-fg);
85
85
  }
86
86
  :where([data-bottom-sheet-description]) {
@@ -92,14 +92,14 @@
92
92
  flex: 1;
93
93
  min-height: 0;
94
94
  overflow-y: auto;
95
- padding: 0 var(--flx-ui-space-5) var(--flx-ui-space-5);
95
+ padding: 0 var(--flx-ui-space-4) var(--flx-ui-space-4);
96
96
  }
97
97
  :where([data-bottom-sheet-footer]) {
98
98
  display: flex;
99
99
  align-items: center;
100
100
  justify-content: flex-end;
101
101
  gap: var(--flx-ui-space-2);
102
- padding: var(--flx-ui-space-4) var(--flx-ui-space-5);
102
+ padding: var(--flx-ui-space-4) var(--flx-ui-space-4);
103
103
  border-top: var(--flx-ui-border-width-thin) solid var(--flx-ui-color-border);
104
104
  }
105
105
 
@@ -21,7 +21,7 @@
21
21
  color: var(--flx-ui-color-fg-muted);
22
22
  text-decoration: none;
23
23
  border-radius: var(--flx-ui-radius-sm);
24
- transition: color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
24
+ transition: color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
25
25
  }
26
26
  :where([data-breadcrumb-link]:hover) {
27
27
  color: var(--flx-ui-color-fg);
@@ -183,12 +183,8 @@
183
183
  align-items: center;
184
184
  justify-content: center;
185
185
  }
186
- :where(.flx-ui-be-check) {
187
- width: 15px;
188
- height: 15px;
189
- cursor: pointer;
190
- accent-color: var(--flx-ui-color-primary);
191
- }
186
+ /* Selection checkboxes render the <Checkbox> component ([role='checkbox']),
187
+ which styles itself; the wrap just centres it in the cell. */
192
188
 
193
189
  /* Fill handle on the active cell */
194
190
  :where(.flx-ui-be-fill-handle) {
@@ -406,17 +402,6 @@
406
402
  text-align: left;
407
403
  }
408
404
 
409
- /* Bulk editor — column header select-all checkbox */
410
- :where(.flx-ui-be-head-check) {
411
- width: 14px;
412
- height: 14px;
413
- cursor: pointer;
414
- accent-color: var(--flx-ui-color-primary);
415
- flex-shrink: 0;
416
- }
417
- :where(.flx-ui-be-check:disabled) {
418
- cursor: not-allowed;
419
- }
420
405
 
421
406
  /* Bulk editor — column resize grip */
422
407
  :where(.flx-ui-be-col-resize) {
@@ -1,8 +1,43 @@
1
1
  @layer flx.components {
2
2
  /* ----------------------------------------------------------------------------
3
- * Button (and any [data-variant] control, e.g. dialog triggers)
3
+ * Button (and any [data-variant] control, e.g. dialog triggers). Badges also
4
+ * carry [data-variant], so every rule here excludes [data-badge] — a badge must
5
+ * never pick up button sizing/colour.
4
6
  * ------------------------------------------------------------------------- */
5
- :where([data-variant]) {
7
+
8
+ /* Tone (data-tone) is orthogonal to variant: it remaps the button's own
9
+ * `--flx-ui-color-primary*` to a semantic colour, so every variant (solid/outline/
10
+ * ghost/soft) renders in that tone AND skins follow (they read color-primary). The
11
+ * hover/active steps are derived (the semantic tokens only ship solid/subtle/fg). */
12
+ :where([data-tone='danger']) {
13
+ --flx-ui-color-primary: var(--flx-ui-color-danger);
14
+ --flx-ui-color-primary-hover: color-mix(in oklab, var(--flx-ui-color-danger) 88%, #000);
15
+ --flx-ui-color-primary-active: color-mix(in oklab, var(--flx-ui-color-danger) 78%, #000);
16
+ --flx-ui-color-primary-fg: var(--flx-ui-color-danger-fg);
17
+ --flx-ui-color-primary-subtle: var(--flx-ui-color-danger-subtle);
18
+ }
19
+ :where([data-tone='success']) {
20
+ --flx-ui-color-primary: var(--flx-ui-color-success);
21
+ --flx-ui-color-primary-hover: color-mix(in oklab, var(--flx-ui-color-success) 88%, #000);
22
+ --flx-ui-color-primary-active: color-mix(in oklab, var(--flx-ui-color-success) 78%, #000);
23
+ --flx-ui-color-primary-fg: var(--flx-ui-color-success-fg);
24
+ --flx-ui-color-primary-subtle: var(--flx-ui-color-success-subtle);
25
+ }
26
+ :where([data-tone='warning']) {
27
+ --flx-ui-color-primary: var(--flx-ui-color-warning);
28
+ --flx-ui-color-primary-hover: color-mix(in oklab, var(--flx-ui-color-warning) 88%, #000);
29
+ --flx-ui-color-primary-active: color-mix(in oklab, var(--flx-ui-color-warning) 78%, #000);
30
+ --flx-ui-color-primary-fg: var(--flx-ui-color-warning-fg);
31
+ --flx-ui-color-primary-subtle: var(--flx-ui-color-warning-subtle);
32
+ }
33
+ :where([data-tone='neutral']) {
34
+ --flx-ui-color-primary: var(--flx-ui-color-fg-muted);
35
+ --flx-ui-color-primary-hover: var(--flx-ui-color-fg);
36
+ --flx-ui-color-primary-active: var(--flx-ui-color-fg);
37
+ --flx-ui-color-primary-fg: var(--flx-ui-color-bg);
38
+ --flx-ui-color-primary-subtle: var(--flx-ui-color-bg-muted);
39
+ }
40
+ :where([data-variant]:not([data-badge])) {
6
41
  box-sizing: border-box;
7
42
  display: inline-flex;
8
43
  align-items: center;
@@ -20,6 +55,9 @@
20
55
  border: var(--flx-ui-border-width-thin) solid transparent;
21
56
  border-radius: var(--flx-ui-radius-md);
22
57
  height: var(--flx-ui-size-md);
58
+ /* Never narrower than tall, so an icon-only button (a close ×, a chevron)
59
+ stays square instead of collapsing to its glyph width. */
60
+ min-width: var(--flx-ui-size-md);
23
61
  padding: 0 var(--flx-ui-space-4);
24
62
  color: var(--flx-ui-color-fg);
25
63
  transition:
@@ -27,54 +65,57 @@
27
65
  border-color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
28
66
  box-shadow var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
29
67
  }
30
- :where([data-variant][data-size='sm']) {
68
+ :where([data-variant]:not([data-badge])[data-size='sm']) {
31
69
  height: var(--flx-ui-size-sm);
70
+ min-width: var(--flx-ui-size-sm);
32
71
  padding: 0 var(--flx-ui-space-3);
33
72
  font-size: var(--flx-ui-font-size-sm);
34
73
  }
35
- :where([data-variant][data-size='lg']) {
74
+ :where([data-variant]:not([data-badge])[data-size='lg']) {
36
75
  height: var(--flx-ui-size-lg);
76
+ min-width: var(--flx-ui-size-lg);
37
77
  padding: 0 var(--flx-ui-space-5);
38
78
  font-size: var(--flx-ui-font-size-lg);
39
79
  }
40
80
 
41
81
  /* Solid: the primary action — a flat, confident accent. Depth comes from the
42
82
  color step change on interaction, not from shadow or sheen. */
43
- :where([data-variant='solid']) {
83
+ :where([data-variant='solid']:not([data-badge])) {
44
84
  background: var(--flx-ui-color-primary);
45
85
  color: var(--flx-ui-color-primary-fg);
46
86
  }
47
- :where([data-variant='solid']:hover:not([data-disabled])) {
87
+ :where([data-variant='solid']:not([data-badge]):hover:not([data-disabled])) {
48
88
  background: var(--flx-ui-color-primary-hover);
49
89
  }
50
- :where([data-variant='solid']:active:not([data-disabled])) {
90
+ :where([data-variant='solid']:not([data-badge]):active:not([data-disabled])) {
51
91
  background: var(--flx-ui-color-primary-active);
52
92
  }
53
93
 
54
- /* Soft: a tinted, low-key primary. */
55
- :where([data-variant='soft']) {
56
- background: var(--flx-ui-color-primary-subtle);
94
+ /* Soft: a tinted, low-key primary. Deepened a touch over the bare subtle tint so
95
+ it carries enough weight to read as a control, then steps up on hover. */
96
+ :where([data-variant='soft']:not([data-badge])) {
97
+ background: color-mix(in oklab, var(--flx-ui-color-primary-subtle), var(--flx-ui-color-primary) 10%);
57
98
  color: var(--flx-ui-color-primary-active);
58
99
  }
59
- :where([data-variant='soft']:hover:not([data-disabled])) {
60
- background: color-mix(in oklab, var(--flx-ui-color-primary-subtle), var(--flx-ui-color-primary) 12%);
100
+ :where([data-variant='soft']:not([data-badge]):hover:not([data-disabled])) {
101
+ background: color-mix(in oklab, var(--flx-ui-color-primary-subtle), var(--flx-ui-color-primary) 20%);
61
102
  }
62
103
 
63
104
  /* Outline: a flat hairline-bordered control. */
64
- :where([data-variant='outline']) {
105
+ :where([data-variant='outline']:not([data-badge])) {
65
106
  border-color: var(--flx-ui-color-border-strong);
66
107
  background: var(--flx-ui-color-bg);
67
108
  }
68
- :where([data-variant='outline']:hover:not([data-disabled])) {
109
+ :where([data-variant='outline']:not([data-badge]):hover:not([data-disabled])) {
69
110
  background: var(--flx-ui-color-bg-subtle);
70
111
  border-color: var(--flx-ui-color-fg-muted);
71
112
  }
72
113
 
73
114
  /* Ghost: text-only until hovered. */
74
- :where([data-variant='ghost']) {
115
+ :where([data-variant='ghost']:not([data-badge])) {
75
116
  color: var(--flx-ui-color-fg);
76
117
  }
77
- :where([data-variant='ghost']:hover:not([data-disabled])) {
118
+ :where([data-variant='ghost']:not([data-badge]):hover:not([data-disabled])) {
78
119
  background: var(--flx-ui-color-bg-muted);
79
120
  }
80
121
 
@@ -83,6 +124,25 @@
83
124
  cursor: not-allowed;
84
125
  pointer-events: none;
85
126
  }
127
+ /* Buttons get a flat, neutral disabled instead of a dimmed accent (which reads
128
+ muddy). Filled variants drop to a grey fill; outline/ghost stay flat. */
129
+ :where([data-variant]:not([data-badge])[data-disabled]) {
130
+ opacity: 1;
131
+ box-shadow: none;
132
+ color: var(--flx-ui-color-fg-muted);
133
+ }
134
+ :where([data-variant='solid']:not([data-badge])[data-disabled]),
135
+ :where([data-variant='soft']:not([data-badge])[data-disabled]) {
136
+ background: var(--flx-ui-color-bg-muted);
137
+ border-color: transparent;
138
+ }
139
+ :where([data-variant='outline']:not([data-badge])[data-disabled]) {
140
+ background: transparent;
141
+ border-color: var(--flx-ui-color-border);
142
+ }
143
+ :where([data-variant='ghost']:not([data-badge])[data-disabled]) {
144
+ background: transparent;
145
+ }
86
146
 
87
147
  /* The signature focus ring: a surface-colored gap then an accent halo. */
88
148
  :where(:focus-visible) {
@@ -34,11 +34,12 @@
34
34
  background: transparent;
35
35
  color: var(--flx-ui-color-fg-muted);
36
36
  cursor: pointer;
37
- transition: background var(--flx-ui-duration-fast) var(--flx-ui-easing-standard),
38
- color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
37
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
38
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
39
39
  }
40
40
  :where([data-calendar-nav]:hover:not([disabled])) {
41
- background: var(--flx-ui-color-bg-subtle);
41
+ background: var(--flx-ui-color-bg-muted);
42
+ border-color: var(--flx-ui-color-border-strong);
42
43
  color: var(--flx-ui-color-fg);
43
44
  }
44
45
  :where([data-calendar-nav]:focus-visible) {
@@ -77,11 +78,12 @@
77
78
  font-size: var(--flx-ui-font-size-sm);
78
79
  color: var(--flx-ui-color-fg);
79
80
  cursor: pointer;
80
- transition: background var(--flx-ui-duration-fast) var(--flx-ui-easing-standard),
81
- color var(--flx-ui-duration-fast) var(--flx-ui-easing-standard);
81
+ transition: background var(--flx-ui-duration-fast) var(--flx-ui-ease-standard),
82
+ color var(--flx-ui-duration-fast) var(--flx-ui-ease-standard);
82
83
  }
83
- :where([data-calendar-day]:hover:not([data-disabled]):not([data-unavailable])) {
84
- background: var(--flx-ui-color-bg-subtle);
84
+ :where([data-calendar-day]:hover:not([data-disabled]):not([data-unavailable]):not([data-selected])) {
85
+ background: var(--flx-ui-color-primary-subtle);
86
+ color: var(--flx-ui-color-primary-active);
85
87
  }
86
88
  :where([data-calendar-day]:focus-visible) {
87
89
  outline: none;
package/dist/css/card.css CHANGED
@@ -12,6 +12,10 @@
12
12
  box-shadow: var(--flx-ui-shadow-sm);
13
13
  overflow: hidden;
14
14
  }
15
+ /* Tonal elevation in dark — a card lifts by lightening, not by an unseen shadow. */
16
+ [data-theme='dark'] :where([data-card]) {
17
+ background: var(--flx-ui-gray-2);
18
+ }
15
19
  :where([data-card-header]) {
16
20
  display: flex;
17
21
  flex-direction: column;
@@ -22,7 +26,7 @@
22
26
  margin: 0;
23
27
  font-size: var(--flx-ui-font-size-lg);
24
28
  font-weight: var(--flx-ui-font-weight-semibold);
25
- line-height: var(--flx-ui-leading-tight);
29
+ line-height: var(--flx-ui-line-height-tight);
26
30
  color: var(--flx-ui-color-fg);
27
31
  }
28
32
  :where([data-card-description]) {