@nonoun/native-ui 0.2.4 → 0.2.5

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 (34) hide show
  1. package/dist/components/ui-kbd/index.d.ts +3 -0
  2. package/dist/components/ui-kbd/index.d.ts.map +1 -0
  3. package/dist/components/ui-kbd/ui-kbd-element.d.ts +5 -0
  4. package/dist/components/ui-kbd/ui-kbd-element.d.ts.map +1 -0
  5. package/dist/components/ui-kbd/ui-kbd.d.ts +3 -0
  6. package/dist/components/ui-kbd/ui-kbd.d.ts.map +1 -0
  7. package/dist/components/ui-nav/ui-nav-group-element.d.ts +8 -0
  8. package/dist/components/ui-nav/ui-nav-group-element.d.ts.map +1 -1
  9. package/dist/components-lean.css +288 -222
  10. package/dist/components.css +290 -229
  11. package/dist/containers/ui-layout-sidebar/index.d.ts +1 -1
  12. package/dist/containers/ui-layout-sidebar/index.d.ts.map +1 -1
  13. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-element.d.ts +4 -1
  14. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-element.d.ts.map +1 -1
  15. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item-element.d.ts +10 -0
  16. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item-element.d.ts.map +1 -0
  17. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item.d.ts +3 -0
  18. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item.d.ts.map +1 -0
  19. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar.d.ts +1 -1
  20. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar.d.ts.map +1 -1
  21. package/dist/custom-elements.json +3352 -2944
  22. package/dist/foundation.css +79 -1
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/native-ui-lean.css +367 -223
  26. package/dist/native-ui.css +369 -230
  27. package/dist/native-ui.js +3 -6
  28. package/dist/register-all.js +1 -1
  29. package/dist/ui-icon-element.js +95 -7
  30. package/package.json +1 -1
  31. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger-element.d.ts +0 -8
  32. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger-element.d.ts.map +0 -1
  33. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger.d.ts +0 -3
  34. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger.d.ts.map +0 -1
@@ -1267,6 +1267,54 @@
1267
1267
  display: none;
1268
1268
  }
1269
1269
 
1270
+ /* WHY: Custom elements render as unstyled inline text until define() runs.
1271
+ visibility:hidden preserves layout — no reflow when the element upgrades.
1272
+ Only targets elements that call define(); CSS-only containers are excluded
1273
+ (they never become :defined so would be permanently hidden). */
1274
+ :where(
1275
+ ui-accordion, ui-accordion-item,
1276
+ ui-avatar,
1277
+ ui-badge,
1278
+ ui-breadcrumb, ui-breadcrumb-item,
1279
+ ui-button,
1280
+ ui-calendar,
1281
+ ui-card,
1282
+ ui-chat-input,
1283
+ ui-checkbox,
1284
+ ui-combobox,
1285
+ ui-command, ui-command-empty, ui-command-group, ui-command-input, ui-command-item, ui-command-list,
1286
+ ui-controller,
1287
+ ui-dialog,
1288
+ ui-drawer,
1289
+ ui-field,
1290
+ ui-icon,
1291
+ ui-input,
1292
+ ui-input-otp,
1293
+ ui-kbd,
1294
+ ui-layout-chat,
1295
+ ui-layout-inspector,
1296
+ ui-layout-sidebar, ui-layout-sidebar-item,
1297
+ ui-listbox,
1298
+ ui-nav, ui-nav-group, ui-nav-group-header, ui-nav-item,
1299
+ ui-option, ui-option-group, ui-option-group-header,
1300
+ ui-pagination,
1301
+ ui-radio, ui-radio-group,
1302
+ ui-range,
1303
+ ui-section,
1304
+ ui-segment, ui-segmented-control,
1305
+ ui-select,
1306
+ ui-slide, ui-slideshow,
1307
+ ui-switch,
1308
+ ui-tab, ui-tab-panel, ui-tab-panels, ui-tabs,
1309
+ ui-table, ui-table-body, ui-table-cell, ui-table-head, ui-table-header, ui-table-row,
1310
+ ui-textarea,
1311
+ ui-toolbar,
1312
+ ui-tooltip,
1313
+ ui-tree, ui-tree-item
1314
+ ):not(:defined) {
1315
+ visibility: hidden;
1316
+ }
1317
+
1270
1318
  /* ── Document Defaults ── */
1271
1319
 
1272
1320
  :where(:root) {
@@ -1460,7 +1508,7 @@
1460
1508
  --ui-tooltip-max-width: 20rem;
1461
1509
 
1462
1510
  /* Popover (select / combobox / command) */
1463
- --ui-popover-max-height: 18rem;
1511
+ --ui-popover-max-height: calc(100dvh - 2rem);
1464
1512
  --ui-popover-gap: 0.25rem;
1465
1513
 
1466
1514
  /* Drawer */
@@ -1493,6 +1541,18 @@
1493
1541
  --ui-badge-size-xl: 1.375rem;
1494
1542
  --ui-badge-dot: 0.5rem;
1495
1543
 
1544
+ /* Kbd */
1545
+ --ui-kbd-font-xs: 0.625rem;
1546
+ --ui-kbd-font-sm: 0.6875rem;
1547
+ --ui-kbd-font-md: 0.6875rem;
1548
+ --ui-kbd-font-lg: 0.75rem;
1549
+ --ui-kbd-font-xl: 0.8125rem;
1550
+ --ui-kbd-size-xs: 1rem;
1551
+ --ui-kbd-size-sm: 1.125rem;
1552
+ --ui-kbd-size-md: 1.25rem;
1553
+ --ui-kbd-size-lg: 1.375rem;
1554
+ --ui-kbd-size-xl: 1.5rem;
1555
+
1496
1556
  /* Group header (option-group, table category) */
1497
1557
  --ui-group-header-font: 0.625rem;
1498
1558
 
@@ -1606,6 +1666,8 @@
1606
1666
  /* ── Intent (maps to color token families) ── */
1607
1667
 
1608
1668
  :root {
1669
+ --_body: var(--neutral-body);
1670
+
1609
1671
  --_card: var(--neutral-card);
1610
1672
  --_card-hover: var(--neutral-card-hover);
1611
1673
 
@@ -1657,6 +1719,22 @@
1657
1719
  --_ink-disabled: var(--neutral-ink-disabled);
1658
1720
  }
1659
1721
 
1722
+ /* ── Toggle control defaults ── */
1723
+ /* WHY: Checkboxes, radios, switches, and range sliders use accent-colored
1724
+ surface fills for their checked/active states by default.
1725
+ [intent] selectors below override --_surface when an explicit intent is set. */
1726
+
1727
+ :where(ui-checkbox, ui-radio, ui-switch, ui-range) {
1728
+ --_surface: var(--accent-surface);
1729
+ --_surface-hover: var(--accent-surface-hover);
1730
+ --_surface-active: var(--accent-surface-active);
1731
+ --_surface-disabled: var(--accent-surface-disabled);
1732
+ --_surface-ink: var(--accent-surface-ink);
1733
+ --_surface-ink-hover: var(--accent-surface-ink-hover);
1734
+ --_surface-ink-active: var(--accent-surface-ink-active);
1735
+ --_surface-ink-disabled: var(--accent-surface-ink-disabled);
1736
+ }
1737
+
1660
1738
  :where([intent="neutral"]) {
1661
1739
  --_card: var(--neutral-card);
1662
1740
  --_card-hover: var(--neutral-card-hover);
@@ -2365,7 +2443,7 @@
2365
2443
  font-weight: var(--_font-weight);
2366
2444
 
2367
2445
  border-radius: var(--_radius);
2368
- border: 1px solid var(--_border-color, var(--neutral-border-muted));
2446
+ border: 1px solid var(--_border-color, var(--_border-muted));
2369
2447
 
2370
2448
  background: var(--_background, var(--_button));
2371
2449
  color: var(--_color, var(--_ink));
@@ -2381,16 +2459,16 @@
2381
2459
  /* ── States ── */
2382
2460
 
2383
2461
  :where(ui-button):hover{
2384
- background: var(--_background-hover, var(--neutral-button-hover));
2462
+ background: var(--_background-hover, var(--_button-hover));
2385
2463
  color: var(--_color-hover, var(--_ink-hover));
2386
- border-color: var(--_border-color-hover, var(--neutral-border-hover));
2464
+ border-color: var(--_border-color-hover, var(--_border-hover));
2387
2465
  }
2388
2466
 
2389
2467
  :where(ui-button):active,
2390
2468
  :where(ui-button)[pressed]{
2391
- background: var(--_background-active, var(--neutral-button-active));
2469
+ background: var(--_background-active, var(--_button-active));
2392
2470
  color: var(--_color-active, var(--_ink-active));
2393
- border-color: var(--_border-color-active, var(--neutral-border-active));
2471
+ border-color: var(--_border-color-active, var(--_border-active));
2394
2472
  }
2395
2473
 
2396
2474
  :where(ui-button):focus-visible{
@@ -2399,9 +2477,9 @@
2399
2477
  }
2400
2478
 
2401
2479
  :where(ui-button)[aria-disabled="true"] {
2402
- background: var(--_background-disabled, var(--neutral-button-disabled));
2480
+ background: var(--_background-disabled, var(--_button-disabled));
2403
2481
  color: var(--_color-disabled, var(--_ink-disabled));
2404
- border-color: var(--_border-color-disabled, var(--neutral-border-muted));
2482
+ border-color: var(--_border-color-disabled, var(--_border-muted));
2405
2483
  cursor: not-allowed;
2406
2484
  pointer-events: none;
2407
2485
  }
@@ -2969,34 +3047,25 @@
2969
3047
  }
2970
3048
 
2971
3049
  /* ── Checked State ── */
2972
- /* WHY: Default checked fill uses accent so checkbox is visually distinct
2973
- without needing intent="accent". Unchecked borders stay neutral.
3050
+ /* WHY: Default checked fill uses --_surface (accent by default via
3051
+ intent="accent" being the implicit default for toggle controls).
2974
3052
  When an explicit [intent] is set, the intent selector's --_surface wins. */
2975
3053
 
2976
3054
  :where(ui-checkbox)[aria-checked="true"]::before {
2977
- background: var(--accent-surface);
2978
- border-color: var(--accent-surface);
2979
- }
2980
-
2981
- :where(ui-checkbox)[aria-checked="true"]::after {
2982
- background: var(--accent-surface-ink);
2983
- transform: translateY(-50%) scale(1);
2984
- }
2985
-
2986
- :where(ui-checkbox)[intent][aria-checked="true"]::before {
2987
3055
  background: var(--_surface);
2988
3056
  border-color: var(--_surface);
2989
3057
  }
2990
3058
 
2991
- :where(ui-checkbox)[intent][aria-checked="true"]::after {
3059
+ :where(ui-checkbox)[aria-checked="true"]::after {
2992
3060
  background: var(--_surface-ink);
3061
+ transform: translateY(-50%) scale(1);
2993
3062
  }
2994
3063
 
2995
3064
  /* ── Indeterminate State ── */
2996
3065
 
2997
3066
  :where(ui-checkbox)[aria-checked="mixed"]::before {
2998
- background: var(--accent-surface);
2999
- border-color: var(--accent-surface);
3067
+ background: var(--_surface);
3068
+ border-color: var(--_surface);
3000
3069
  }
3001
3070
 
3002
3071
  :where(ui-checkbox)[aria-checked="mixed"]::after {
@@ -3004,17 +3073,8 @@
3004
3073
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' fill='currentColor' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M228,128a12,12,0,0,1-12,12H40a12,12,0,0,1,0-24H216A12,12,0,0,1,228,128Z'/%3E%3C/svg%3E");
3005
3074
  mask-size: 75%;
3006
3075
  -webkit-mask-size: 75%;
3007
- background: var(--accent-surface-ink);
3008
- transform: translateY(-50%) scale(1);
3009
- }
3010
-
3011
- :where(ui-checkbox)[intent][aria-checked="mixed"]::before {
3012
- background: var(--_surface);
3013
- border-color: var(--_surface);
3014
- }
3015
-
3016
- :where(ui-checkbox)[intent][aria-checked="mixed"]::after {
3017
3076
  background: var(--_surface-ink);
3077
+ transform: translateY(-50%) scale(1);
3018
3078
  }
3019
3079
 
3020
3080
  /* ── Hover ── */
@@ -3031,13 +3091,6 @@
3031
3091
  :where(ui-checkbox)[aria-checked="true"]:hover::before,
3032
3092
  :where(ui-checkbox)[aria-checked="mixed"]:hover::before
3033
3093
  :where(ui-checkbox)[aria-checked="mixed"][force-hover]::before {
3034
- background: var(--accent-surface-hover);
3035
- border-color: var(--accent-surface-hover);
3036
- }
3037
-
3038
- :where(ui-checkbox)[intent][aria-checked="true"]:hover::before,
3039
- :where(ui-checkbox)[intent][aria-checked="mixed"]:hover::before
3040
- :where(ui-checkbox)[intent][aria-checked="mixed"][force-hover]::before {
3041
3094
  background: var(--_surface-hover);
3042
3095
  border-color: var(--_surface-hover);
3043
3096
  }
@@ -3051,12 +3104,6 @@
3051
3104
 
3052
3105
  :where(ui-checkbox)[aria-checked="true"][pressed]::before,
3053
3106
  :where(ui-checkbox)[aria-checked="mixed"][pressed]::before {
3054
- background: var(--accent-surface-active);
3055
- border-color: var(--accent-surface-active);
3056
- }
3057
-
3058
- :where(ui-checkbox)[intent][aria-checked="true"][pressed]::before,
3059
- :where(ui-checkbox)[intent][aria-checked="mixed"][pressed]::before {
3060
3107
  background: var(--_surface-active);
3061
3108
  border-color: var(--_surface-active);
3062
3109
  }
@@ -3703,6 +3750,45 @@
3703
3750
 
3704
3751
  }
3705
3752
 
3753
+ @layer ui {
3754
+
3755
+ /* ╭──────────────────────────────────────────────────────────╮
3756
+ │ ui-kbd │
3757
+ │ Keyboard shortcut indicator. Own font/size scale │
3758
+ │ (like ui-badge) — zero-attribute = md. │
3759
+ ╰──────────────────────────────────────────────────────────╯ */
3760
+
3761
+ :where(ui-kbd) {
3762
+ --_icon-size: 1.125em;
3763
+
3764
+ display: inline-flex;
3765
+ align-items: center;
3766
+ justify-content: center;
3767
+ font-family: ui-monospace, monospace;
3768
+ font-size: var(--ui-kbd-font-md);
3769
+ line-height: 1;
3770
+ min-height: var(--ui-kbd-size-md);
3771
+ padding-inline: 0.4em;
3772
+ border-radius: 0.25rem;
3773
+ background: var(--_ground, var(--_body));
3774
+ border: 1px solid var(--_border-muted);
3775
+ color: var(--_ink-muted);
3776
+ white-space: nowrap;
3777
+ user-select: none;
3778
+ flex-shrink: 0;
3779
+ vertical-align: baseline;
3780
+ }
3781
+
3782
+ /* ── Sizes ── */
3783
+
3784
+ :where(ui-kbd[size="xs"]) { font-size: var(--ui-kbd-font-xs); min-height: var(--ui-kbd-size-xs); }
3785
+ :where(ui-kbd[size="sm"]) { font-size: var(--ui-kbd-font-sm); min-height: var(--ui-kbd-size-sm); }
3786
+ :where(ui-kbd[size="md"]) { font-size: var(--ui-kbd-font-md); min-height: var(--ui-kbd-size-md); }
3787
+ :where(ui-kbd[size="lg"]) { font-size: var(--ui-kbd-font-lg); min-height: var(--ui-kbd-size-lg); }
3788
+ :where(ui-kbd[size="xl"]) { font-size: var(--ui-kbd-font-xl); min-height: var(--ui-kbd-size-xl); }
3789
+
3790
+ }
3791
+
3706
3792
  @layer ui {
3707
3793
 
3708
3794
  /* ── Listbox Base ── */
@@ -3840,11 +3926,14 @@
3840
3926
  │ Uses ui-nav-item / ui-nav-group for children. │
3841
3927
  ╰──────────────────────────────────────────────────────────╯ */
3842
3928
 
3929
+ /* WHY: ui-nav is a transparent flex wrapper — no inline padding.
3930
+ Inline padding is owned by leaf items (nav-item, summary).
3931
+ Block padding is owned by nav-group margins. */
3843
3932
  :where(ui-nav) {
3844
3933
  display: flex;
3845
3934
  flex-direction: column;
3846
3935
  gap: 0;
3847
- padding: var(--_space);
3936
+ padding: 0;
3848
3937
  outline: none;
3849
3938
  }
3850
3939
 
@@ -3859,6 +3948,7 @@
3859
3948
  display: flex;
3860
3949
  align-items: center;
3861
3950
  gap: calc(var(--_space) * 2);
3951
+ padding-inline: calc(var(--_space-k) * var(--_space));
3862
3952
 
3863
3953
  min-height: var(--_min-height);
3864
3954
 
@@ -3955,6 +4045,7 @@
3955
4045
  display: flex;
3956
4046
  align-items: center;
3957
4047
  gap: calc(var(--_space) * 2);
4048
+ padding-inline: calc(var(--_space-k) * var(--_space));
3958
4049
  cursor: pointer;
3959
4050
  user-select: none;
3960
4051
  list-style: none;
@@ -4040,8 +4131,11 @@
4040
4131
  /* WHY: Connector draws from below the header to the bottom of the group.
4041
4132
  Only shown when header contains an icon. Scoped via :has(). */
4042
4133
 
4134
+ /* WHY: Insets account for the summary's padding-inline so the connector
4135
+ line passes through the icon center and child text aligns with header text. */
4043
4136
  :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)) {
4044
- --_group-line-inset: calc(var(--_icon-size) / 2);
4137
+ --_group-pad: calc(var(--_space-k) * var(--_space));
4138
+ --_group-line-inset: calc(var(--_group-pad) + var(--_icon-size) / 2);
4045
4139
  --_group-child-inset: calc(var(--_icon-size) + var(--_space) * 2);
4046
4140
  }
4047
4141
 
@@ -4087,6 +4181,73 @@
4087
4181
  margin-inline-start: var(--_group-child-inset, 0);
4088
4182
  }
4089
4183
 
4184
+ /* ── Nav Group Flyout (collapsed sidebar) ── */
4185
+ /* WHY: In collapsed mode, summary click opens a ui-listbox popover to the right
4186
+ instead of expanding the <details>. Same pattern as sidebar-trigger menus. */
4187
+
4188
+ :where(ui-nav-group) > :where(ui-listbox.nav-group-flyout[popover]) {
4189
+ position: fixed;
4190
+ position-area: inline-end span-block-end;
4191
+ position-try-fallbacks: --nav-flyout-flip-up;
4192
+ margin: 0 0 0 var(--ui-popover-gap);
4193
+ min-width: 200px;
4194
+ max-height: var(--ui-popover-max-height);
4195
+ overflow-y: auto;
4196
+ }
4197
+
4198
+ @position-try --nav-flyout-flip-up {
4199
+ position-area: inline-end span-block-start;
4200
+ }
4201
+
4202
+ /* ── Container Query: Collapsed Sidebar ── */
4203
+ /* WHY: Nav components own their own collapsed behavior via @container.
4204
+ The sidebar aside declares container-name: sidebar. When it shrinks
4205
+ to 48px (icon rail), nav responds to the width — not to [collapsed].
4206
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
4207
+
4208
+ @container sidebar (max-width: 80px) {
4209
+
4210
+ /* Nav items hide entirely — only group headers (with icons) remain. */
4211
+ :where(ui-nav-item) {
4212
+ display: none;
4213
+ }
4214
+
4215
+ /* Summary shrinks to icon-only. Reduced inline padding wraps tightly
4216
+ around the icon; parent align-items: center handles horizontal centering. */
4217
+ :where(ui-nav-group) > :where(details) > :where(summary) {
4218
+ padding-inline: var(--_space);
4219
+ border-radius: var(--_radius);
4220
+ }
4221
+
4222
+ /* Hide chevron — no expand/collapse in icon rail (flyout instead). */
4223
+ :where(ui-nav-group) > :where(details) > :where(summary)::after {
4224
+ display: none;
4225
+ }
4226
+
4227
+ /* Hide vertical connector line and sliding indicator. */
4228
+ :where(ui-nav-group)::after,
4229
+ :where(ui-nav-group)::before {
4230
+ display: none;
4231
+ }
4232
+
4233
+ /* Header collapses to icon-only. font-size: 0 hides text nodes
4234
+ (can't be targeted by CSS). Icon overrides back to normal below. */
4235
+ :where(ui-nav-group-header) {
4236
+ flex: 0 0 auto;
4237
+ font-size: 0;
4238
+ gap: 0;
4239
+ }
4240
+
4241
+ :where(ui-nav-group-header) :where(ui-icon) {
4242
+ font-size: var(--_font-size, 1rem);
4243
+ }
4244
+
4245
+ /* Collapse inter-group spacing in icon rail. */
4246
+ :where(ui-nav-group) + :where(ui-nav-group) {
4247
+ margin-block-start: 0;
4248
+ }
4249
+ }
4250
+
4090
4251
  }
4091
4252
 
4092
4253
  @layer ui {
@@ -4227,27 +4388,15 @@
4227
4388
  }
4228
4389
 
4229
4390
  /* ── Selected ── */
4230
- /* WHY: Default selected fill uses accent so radio is visually distinct
4231
- without needing intent="accent". Unchecked borders stay neutral.
4232
- When an explicit [intent] is set, the intent selector's --_surface wins. */
4233
4391
 
4234
4392
  :where(ui-radio)[aria-checked="true"]::before {
4235
- background: var(--accent-surface);
4236
- border-color: var(--accent-surface);
4237
- }
4238
-
4239
- :where(ui-radio)[aria-checked="true"]::after {
4240
- background: var(--accent-surface-ink);
4241
- transform: translateY(-50%) scale(1);
4242
- }
4243
-
4244
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::before {
4245
4393
  background: var(--_surface);
4246
4394
  border-color: var(--_surface);
4247
4395
  }
4248
4396
 
4249
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::after {
4397
+ :where(ui-radio)[aria-checked="true"]::after {
4250
4398
  background: var(--_surface-ink);
4399
+ transform: translateY(-50%) scale(1);
4251
4400
  }
4252
4401
 
4253
4402
  /* ── Hover ── */
@@ -4262,11 +4411,6 @@
4262
4411
  }
4263
4412
 
4264
4413
  :where(ui-radio)[aria-checked="true"]:hover::before{
4265
- background: var(--accent-surface-hover);
4266
- border-color: var(--accent-surface-hover);
4267
- }
4268
-
4269
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]:hover::before{
4270
4414
  background: var(--_surface-hover);
4271
4415
  border-color: var(--_surface-hover);
4272
4416
  }
@@ -4279,11 +4423,6 @@
4279
4423
  }
4280
4424
 
4281
4425
  :where(ui-radio)[aria-checked="true"][pressed]::before {
4282
- background: var(--accent-surface-active);
4283
- border-color: var(--accent-surface-active);
4284
- }
4285
-
4286
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"][pressed]::before {
4287
4426
  background: var(--_surface-active);
4288
4427
  border-color: var(--_surface-active);
4289
4428
  }
@@ -4381,16 +4520,12 @@
4381
4520
  /* 0% = track-height (circle), 100% = full width */
4382
4521
  width: calc(var(--_track-height) + (100% - var(--_track-height)) * var(--_progress));
4383
4522
  border-radius: calc(var(--_track-height) / 2);
4384
- background: var(--accent-surface);
4523
+ background: var(--_surface);
4385
4524
 
4386
4525
  transition:
4387
4526
  background var(--_duration) var(--_easing);
4388
4527
  }
4389
4528
 
4390
- :where(ui-range)[intent]::after {
4391
- background: var(--_surface);
4392
- }
4393
-
4394
4529
  /* ── Thumb ── */
4395
4530
 
4396
4531
  :where(ui-range) > :where(.ui-range-thumb) {
@@ -4402,8 +4537,8 @@
4402
4537
  width: var(--_thumb-size);
4403
4538
  height: var(--_thumb-size);
4404
4539
  border-radius: 50%;
4405
- background: var(--accent-surface-ink);
4406
- border: 2px solid var(--accent-surface);
4540
+ background: var(--_surface-ink);
4541
+ border: 2px solid var(--_surface);
4407
4542
  box-shadow: var(--ui-shadow-sm);
4408
4543
  pointer-events: none;
4409
4544
 
@@ -4413,11 +4548,6 @@
4413
4548
  border-color var(--_duration) var(--_easing);
4414
4549
  }
4415
4550
 
4416
- :where(ui-range)[intent] > :where(.ui-range-thumb) {
4417
- background: var(--_surface-ink);
4418
- border-color: var(--_surface);
4419
- }
4420
-
4421
4551
  /* ── Focus ── */
4422
4552
 
4423
4553
  :where(ui-range):focus-visible > :where(.ui-range-thumb){
@@ -4428,19 +4558,10 @@
4428
4558
  /* ── Hover ── */
4429
4559
 
4430
4560
  :where(ui-range):hover::after{
4431
- background: var(--accent-surface-hover);
4432
- }
4433
-
4434
- :where(ui-range)[intent]:hover::after{
4435
4561
  background: var(--_surface-hover);
4436
4562
  }
4437
4563
 
4438
4564
  :where(ui-range):hover > :where(.ui-range-thumb){
4439
- border-color: var(--accent-surface-hover);
4440
- box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--accent-surface);
4441
- }
4442
-
4443
- :where(ui-range)[intent]:hover > :where(.ui-range-thumb){
4444
4565
  border-color: var(--_surface-hover);
4445
4566
  box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--_surface);
4446
4567
  }
@@ -4448,19 +4569,10 @@
4448
4569
  /* ── Active (dragging) ── */
4449
4570
 
4450
4571
  :where(ui-range)[pressed]::after {
4451
- background: var(--accent-surface-active);
4452
- }
4453
-
4454
- :where(ui-range)[intent][pressed]::after {
4455
4572
  background: var(--_surface-active);
4456
4573
  }
4457
4574
 
4458
4575
  :where(ui-range)[pressed] > :where(.ui-range-thumb) {
4459
- border-color: var(--accent-surface-active);
4460
- box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--accent-surface);
4461
- }
4462
-
4463
- :where(ui-range)[intent][pressed] > :where(.ui-range-thumb) {
4464
4576
  border-color: var(--_surface-active);
4465
4577
  box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--_surface);
4466
4578
  }
@@ -5007,22 +5119,13 @@
5007
5119
  When an explicit [intent] is set, the intent selector's --_surface wins. */
5008
5120
 
5009
5121
  :where(ui-switch)[aria-checked="true"]::before {
5010
- background: var(--accent-surface);
5011
- border-color: var(--accent-surface);
5122
+ background: var(--_surface);
5123
+ border-color: var(--_surface);
5012
5124
  }
5013
5125
 
5014
5126
  :where(ui-switch)[aria-checked="true"]::after {
5015
5127
  /* Slide thumb to the right via translateX — GPU composited */
5016
5128
  transform: translateY(-50%) translateX(calc(var(--_track-width) - var(--_thumb-size) - var(--_thumb-offset) * 2));
5017
- background: var(--accent-surface-ink);
5018
- }
5019
-
5020
- :where(ui-switch)[intent][aria-checked="true"]::before {
5021
- background: var(--_surface);
5022
- border-color: var(--_surface);
5023
- }
5024
-
5025
- :where(ui-switch)[intent][aria-checked="true"]::after {
5026
5129
  background: var(--_surface-ink);
5027
5130
  }
5028
5131
 
@@ -5038,11 +5141,6 @@
5038
5141
  }
5039
5142
 
5040
5143
  :where(ui-switch)[aria-checked="true"]:hover::before{
5041
- background: var(--accent-surface-hover);
5042
- border-color: var(--accent-surface-hover);
5043
- }
5044
-
5045
- :where(ui-switch)[intent][aria-checked="true"]:hover::before{
5046
5144
  background: var(--_surface-hover);
5047
5145
  border-color: var(--_surface-hover);
5048
5146
  }
@@ -5055,11 +5153,6 @@
5055
5153
  }
5056
5154
 
5057
5155
  :where(ui-switch)[aria-checked="true"][pressed]::before {
5058
- background: var(--accent-surface-active);
5059
- border-color: var(--accent-surface-active);
5060
- }
5061
-
5062
- :where(ui-switch)[intent][aria-checked="true"][pressed]::before {
5063
5156
  background: var(--_surface-active);
5064
5157
  border-color: var(--_surface-active);
5065
5158
  }
@@ -6498,7 +6591,7 @@
6498
6591
 
6499
6592
  :where(ui-layout-chat) :where(.layout-resize-handle):hover,
6500
6593
  :where(ui-layout-chat[resizing]) :where(.layout-resize-handle) {
6501
- background: var(--neutral-border-muted);
6594
+ background: var(--_border-muted);
6502
6595
  }
6503
6596
 
6504
6597
  }
@@ -6560,7 +6653,7 @@
6560
6653
 
6561
6654
  :where(ui-layout-inspector) :where(.layout-resize-handle):hover,
6562
6655
  :where(ui-layout-inspector[resizing]) :where(.layout-resize-handle) {
6563
- background: var(--neutral-border-muted);
6656
+ background: var(--_border-muted);
6564
6657
  }
6565
6658
 
6566
6659
  }
@@ -6587,16 +6680,16 @@
6587
6680
  content: '';
6588
6681
  width: var(--ui-layout-sidebar-width);
6589
6682
  flex-shrink: 0;
6590
- background: var(--neutral-body);
6591
- border-right: 1px solid var(--neutral-border-muted);
6683
+ background: var(--_body);
6684
+ border-right: 1px solid var(--_border-muted);
6592
6685
  }
6593
6686
 
6594
6687
  :where(ui-layout-sidebar):not([data-ready])[collapsed]::before {
6595
6688
  content: '';
6596
6689
  width: 48px;
6597
6690
  flex-shrink: 0;
6598
- background: var(--neutral-body);
6599
- border-right: 1px solid var(--neutral-border-muted);
6691
+ background: var(--_body);
6692
+ border-right: 1px solid var(--_border-muted);
6600
6693
  }
6601
6694
 
6602
6695
  /* ── Content Column ── */
@@ -6612,16 +6705,23 @@
6612
6705
  }
6613
6706
 
6614
6707
  /* ── Sidebar Aside ── */
6708
+ /* WHY: position: relative creates the containing block for absolute
6709
+ header/footer overlays. Content fills the full height and scrolls
6710
+ underneath the pinned header/footer. */
6615
6711
 
6616
6712
  :where(ui-layout-sidebar) > :where([slot="sidebar"]) {
6713
+ --_sidebar-header-height: 0px;
6714
+ --_sidebar-footer-height: 0px;
6715
+
6716
+ container-name: sidebar;
6717
+ container-type: inline-size;
6718
+ position: sticky;
6719
+ top: 0;
6617
6720
  width: var(--ui-layout-sidebar-width);
6618
6721
  min-width: 160px;
6619
6722
  max-width: 400px;
6620
6723
  height: 100dvh;
6621
- position: sticky;
6622
- top: 0;
6623
- display: flex;
6624
- flex-direction: column;
6724
+ display: block;
6625
6725
  transition: width var(--_duration) var(--_easing), min-width var(--_duration) var(--_easing);
6626
6726
  overflow: hidden;
6627
6727
  z-index: 10;
@@ -6633,7 +6733,9 @@
6633
6733
  }
6634
6734
 
6635
6735
  /* ── Collapsed Icon Rail ── */
6636
- /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding. */
6736
+ /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding.
6737
+ [collapsed] drives the aside to 48px which triggers @container sidebar queries
6738
+ in this file and in child component CSS (ui-nav.css, etc.). */
6637
6739
 
6638
6740
  :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) {
6639
6741
  width: 48px;
@@ -6654,11 +6756,7 @@
6654
6756
 
6655
6757
  :where(ui-layout-sidebar) :where(.layout-resize-handle):hover,
6656
6758
  :where(ui-layout-sidebar) > :where([slot="sidebar"][resizing]) :where(.layout-resize-handle) {
6657
- background: var(--neutral-border-muted);
6658
- }
6659
-
6660
- :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) :where(.layout-resize-handle) {
6661
- display: none;
6759
+ background: var(--_border-muted);
6662
6760
  }
6663
6761
 
6664
6762
  /* WHY: When collapsed to icon rail, the sidebar edge meets content — drop left padding. */
@@ -6671,101 +6769,89 @@
6671
6769
  padding-left: 0;
6672
6770
  }
6673
6771
 
6674
- /* ── Collapsed Icon-Rail Content ── */
6675
- /* WHY: When collapsed, sidebar slot becomes a narrow icon rail.
6676
- Text and trailing slots are hidden; only icons remain visible.
6677
- Nav items hide entirely — only group headers (with icons) stay. */
6678
-
6679
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-content) {
6680
- align-items: center;
6681
- padding: 0.5rem;
6682
- }
6683
-
6684
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-header),
6685
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-footer) {
6686
- justify-content: center;
6687
- padding-inline: 0.5rem;
6688
- }
6689
-
6690
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="label"]),
6691
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="trailing"]) {
6692
- display: none;
6693
- }
6694
-
6695
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-item) {
6696
- display: none;
6697
- }
6698
-
6699
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary) {
6700
- justify-content: center;
6701
- }
6702
-
6703
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary)::after {
6704
- display: none;
6705
- }
6706
-
6707
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::after,
6708
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::before {
6709
- display: none;
6710
- }
6711
-
6712
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) {
6713
- justify-content: center;
6714
- font-size: 0;
6715
- gap: 0;
6716
- }
6717
-
6718
- /* WHY: Icon keeps its size even though parent font-size is 0. */
6719
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) :where(ui-icon) {
6720
- font-size: var(--_font-size, 1rem);
6721
- }
6722
-
6723
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) + :where(ui-nav-group) {
6724
- margin-block-start: 0;
6725
- }
6726
-
6727
6772
  /* ── Sidebar Header ── */
6773
+ /* WHY: Absolute-positioned overlay pinned to top of aside. Content scrolls
6774
+ underneath it. z-index: 2 sits above content (z-index: 0). */
6728
6775
 
6729
6776
  :where(ui-layout-sidebar-header) {
6777
+ position: absolute;
6778
+ top: 0;
6779
+ left: 0;
6780
+ right: 0;
6781
+ z-index: 2;
6730
6782
  display: flex;
6731
- align-items: center;
6732
- min-height: var(--ui-layout-bar-height);
6733
- gap: calc(var(--_space) * 2);
6734
- padding-block: var(--_space);
6735
- padding-inline: calc(var(--_space-k) * var(--_space));
6736
- flex-shrink: 0;
6783
+ flex-direction: column;
6737
6784
  }
6738
6785
 
6739
6786
  /* ── Sidebar Content ── */
6787
+ /* WHY: Full height of the aside, scrollable. Padding-block offsets keep
6788
+ content from starting behind header / ending behind footer.
6789
+ Fade-out alpha mask dissolves content edges under the overlays. */
6740
6790
 
6741
6791
  :where(ui-layout-sidebar-content) {
6742
6792
  display: flex;
6743
6793
  flex-direction: column;
6744
- gap: calc(var(--_space) * 2);
6745
- padding-block: var(--_space);
6746
- padding-inline: calc(var(--_space-k) * var(--_space));
6747
- flex: 1;
6748
- min-height: 0;
6794
+ padding-block-start: var(--_sidebar-header-height);
6795
+ padding-block-end: var(--_sidebar-footer-height);
6749
6796
  width: 100%;
6797
+ height: 100%;
6798
+ overflow-y: auto;
6799
+ scrollbar-width: none;
6800
+ }
6801
+
6802
+ /* WHY: When header is present, fade top edge. Content dissolves as it
6803
+ scrolls under the header — transparent at top, fully visible by 1.25×
6804
+ the header height. No opaque header background needed. */
6805
+ :where(ui-layout-sidebar-content)[data-has-header] {
6806
+ mask-image: linear-gradient(
6807
+ to bottom,
6808
+ transparent 0,
6809
+ black calc(var(--_sidebar-header-height) * 1.25),
6810
+ black 100%
6811
+ );
6812
+ }
6813
+
6814
+ /* WHY: When footer is present, fade bottom edge. */
6815
+ :where(ui-layout-sidebar-content)[data-has-footer] {
6816
+ mask-image: linear-gradient(
6817
+ to bottom,
6818
+ black 0,
6819
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
6820
+ transparent 100%
6821
+ );
6822
+ }
6823
+
6824
+ /* WHY: When both header AND footer are present, fade both edges. */
6825
+ :where(ui-layout-sidebar-content)[data-has-header][data-has-footer] {
6826
+ mask-image: linear-gradient(
6827
+ to bottom,
6828
+ transparent 0,
6829
+ black calc(var(--_sidebar-header-height) * 1.25),
6830
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
6831
+ transparent 100%
6832
+ );
6750
6833
  }
6751
6834
 
6752
6835
  /* ── Sidebar Footer ── */
6836
+ /* WHY: Absolute-positioned overlay pinned to bottom of aside. */
6753
6837
 
6754
6838
  :where(ui-layout-sidebar-footer) {
6839
+ position: absolute;
6840
+ bottom: 0;
6841
+ left: 0;
6842
+ right: 0;
6843
+ z-index: 2;
6755
6844
  display: flex;
6756
- align-items: center;
6757
- min-height: var(--ui-layout-bar-height);
6758
- gap: calc(var(--_space) * 2);
6759
- padding-block: var(--_space);
6760
- padding-inline: calc(var(--_space-k) * var(--_space));
6761
- flex-shrink: 0;
6845
+ flex-direction: column;
6762
6846
  }
6763
6847
 
6764
- /* ── Sidebar Trigger ── */
6765
- /* WHY: Menu trigger for sidebar header/footer. Styled like ui-nav-group-header:
6766
- bold text, icon support, hover color. Opens a popover on click. */
6848
+ /* ── Sidebar Item ── */
6849
+ /* WHY: Universal sidebar row element. Provides inline padding for any content
6850
+ (buttons, links, icons). When a child ui-listbox[popover] is present, JS
6851
+ wires PopoverController for click-to-toggle menu behavior.
6852
+ Absorbs the old ui-layout-sidebar-trigger role — one element for all rows. */
6767
6853
 
6768
- :where(ui-layout-sidebar-trigger) {
6854
+ :where(ui-layout-sidebar-item) {
6769
6855
  display: flex;
6770
6856
  align-items: center;
6771
6857
  flex: 1;
@@ -6781,29 +6867,45 @@
6781
6867
  user-select: none;
6782
6868
  border: none;
6783
6869
  background: none;
6784
- padding: var(--_space);
6870
+ padding-block: var(--_space);
6871
+ padding-inline: calc(var(--_space-k) * var(--_space));
6785
6872
  border-radius: var(--_radius);
6786
6873
  transition: color var(--_duration) var(--_easing);
6787
6874
  }
6788
6875
 
6789
- :where(ui-layout-sidebar-trigger):hover{
6876
+ /* WHY: Items in header/footer match the breadcrumb bar height so the
6877
+ first sidebar row aligns horizontally with the breadcrumb. */
6878
+ :where(ui-layout-sidebar-header) > :where(ui-layout-sidebar-item),
6879
+ :where(ui-layout-sidebar-footer) > :where(ui-layout-sidebar-item) {
6880
+ min-height: var(--ui-layout-bar-height);
6881
+ }
6882
+
6883
+ :where(ui-layout-sidebar-item):hover{
6790
6884
  color: var(--_ink-strong);
6791
6885
  }
6792
6886
 
6793
- :where(ui-layout-sidebar-trigger):focus-visible{
6887
+ :where(ui-layout-sidebar-item):focus-visible{
6794
6888
  outline: 2px solid var(--ui-focus-ring);
6795
6889
  outline-offset: -2px;
6796
6890
  }
6797
6891
 
6798
6892
  /* WHY: Trailing caret pushes to end — same pattern as button justify="spread". */
6799
- :where(ui-layout-sidebar-trigger) > :where([slot="trailing"]) {
6893
+ :where(ui-layout-sidebar-item) > :where([slot="trailing"]) {
6800
6894
  margin-inline-start: auto;
6801
6895
  flex-shrink: 0;
6802
6896
  color: var(--_ink-muted);
6803
6897
  }
6804
6898
 
6899
+ /* WHY: When the item wraps a sub-component (e.g. ui-button) that provides
6900
+ its own icon, hide [slot="icon"] in expanded mode — it only serves as the
6901
+ bare collapsed-rail representation. Items without sub-components (just
6902
+ icon + label + trailing) keep [slot="icon"] visible in both modes. */
6903
+ :where(ui-layout-sidebar-item:has(> ui-button)) > :where([slot="icon"]) {
6904
+ display: none;
6905
+ }
6906
+
6805
6907
  /* WHY: Title text truncates when sidebar narrows during resize. */
6806
- :where(ui-layout-sidebar-trigger) > :where([slot="label"]) {
6908
+ :where(ui-layout-sidebar-item) > :where([slot="label"]) {
6807
6909
  flex: 1;
6808
6910
  min-width: 0;
6809
6911
  white-space: nowrap;
@@ -6811,25 +6913,67 @@
6811
6913
  text-overflow: ellipsis;
6812
6914
  }
6813
6915
 
6814
- /* ── Trigger Popover ── */
6815
- /* WHY: Popover opens to the right of the sidebar trigger.
6916
+ /* ── Item Popover ── */
6917
+ /* WHY: Popover opens to the right of the sidebar item.
6816
6918
  Default: top-aligned, grows downward (span-block-end).
6817
6919
  Flip: bottom-aligned, grows upward (span-block-start). */
6818
6920
 
6819
- :where(ui-layout-sidebar-trigger) > :where(ui-listbox[popover]) {
6921
+ :where(ui-layout-sidebar-item) > :where(ui-listbox[popover]) {
6820
6922
  position: fixed;
6821
6923
  position-area: inline-end span-block-end;
6822
- position-try-fallbacks: --flip-up;
6924
+ position-try-fallbacks: --sidebar-item-flip-up;
6823
6925
  margin: 0 0 0 var(--ui-popover-gap);
6824
6926
  min-width: 200px;
6825
6927
  max-height: var(--ui-popover-max-height);
6826
6928
  overflow-y: auto;
6827
6929
  }
6828
6930
 
6829
- @position-try --flip-up {
6931
+ @position-try --sidebar-item-flip-up {
6830
6932
  position-area: inline-end span-block-start;
6831
6933
  }
6832
6934
 
6935
+ /* ── Container Query: Collapsed Sidebar ── */
6936
+ /* WHY: Each component owns its own collapsed behavior via @container.
6937
+ The aside is the container (container-name: sidebar). When it shrinks
6938
+ to 48px (icon rail), components respond to the width, not to [collapsed].
6939
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
6940
+
6941
+ @container sidebar (max-width: 80px) {
6942
+
6943
+ /* Resize handle — no dragging in icon rail */
6944
+ :where(.layout-resize-handle) {
6945
+ display: none;
6946
+ }
6947
+
6948
+ /* Header/footer — center content horizontally */
6949
+ :where(ui-layout-sidebar-header),
6950
+ :where(ui-layout-sidebar-footer) {
6951
+ align-items: center;
6952
+ }
6953
+
6954
+ /* Content — center items in the 48px rail */
6955
+ :where(ui-layout-sidebar-content) {
6956
+ align-items: center;
6957
+ }
6958
+
6959
+ /* Item — shrink to icon-only. Show [slot="icon"], hide everything else.
6960
+ WHY: Sidebar items can contain complex children (buttons with slots,
6961
+ links, etc.). Rather than overriding sub-component styles, the item
6962
+ provides its own bare icon via [slot="icon"] for the collapsed rail. */
6963
+ :where(ui-layout-sidebar-item) {
6964
+ flex: 0 0 auto;
6965
+ padding-inline: var(--_space);
6966
+ }
6967
+
6968
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
6969
+ display: block;
6970
+ }
6971
+
6972
+ :where(ui-layout-sidebar-item) > :where(:not([slot="icon"]):not(ui-listbox[popover]):not(.nav-group-flyout)) {
6973
+ display: none;
6974
+ }
6975
+ }
6976
+
6833
6977
  }
6834
6978
 
6835
6979
  @layer ui {