@nonoun/native-ui 0.2.3 → 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 -169
  10. package/dist/components.css +290 -176
  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 +2892 -2484
  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 -170
  26. package/dist/native-ui.css +369 -177
  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
@@ -361,7 +361,7 @@
361
361
  font-weight: var(--_font-weight);
362
362
 
363
363
  border-radius: var(--_radius);
364
- border: 1px solid var(--_border-color, var(--neutral-border-muted));
364
+ border: 1px solid var(--_border-color, var(--_border-muted));
365
365
 
366
366
  background: var(--_background, var(--_button));
367
367
  color: var(--_color, var(--_ink));
@@ -377,16 +377,16 @@
377
377
  /* ── States ── */
378
378
 
379
379
  :where(ui-button):hover{
380
- background: var(--_background-hover, var(--neutral-button-hover));
380
+ background: var(--_background-hover, var(--_button-hover));
381
381
  color: var(--_color-hover, var(--_ink-hover));
382
- border-color: var(--_border-color-hover, var(--neutral-border-hover));
382
+ border-color: var(--_border-color-hover, var(--_border-hover));
383
383
  }
384
384
 
385
385
  :where(ui-button):active,
386
386
  :where(ui-button)[pressed]{
387
- background: var(--_background-active, var(--neutral-button-active));
387
+ background: var(--_background-active, var(--_button-active));
388
388
  color: var(--_color-active, var(--_ink-active));
389
- border-color: var(--_border-color-active, var(--neutral-border-active));
389
+ border-color: var(--_border-color-active, var(--_border-active));
390
390
  }
391
391
 
392
392
  :where(ui-button):focus-visible{
@@ -395,9 +395,9 @@
395
395
  }
396
396
 
397
397
  :where(ui-button)[aria-disabled="true"] {
398
- background: var(--_background-disabled, var(--neutral-button-disabled));
398
+ background: var(--_background-disabled, var(--_button-disabled));
399
399
  color: var(--_color-disabled, var(--_ink-disabled));
400
- border-color: var(--_border-color-disabled, var(--neutral-border-muted));
400
+ border-color: var(--_border-color-disabled, var(--_border-muted));
401
401
  cursor: not-allowed;
402
402
  pointer-events: none;
403
403
  }
@@ -965,34 +965,25 @@
965
965
  }
966
966
 
967
967
  /* ── Checked State ── */
968
- /* WHY: Default checked fill uses accent so checkbox is visually distinct
969
- without needing intent="accent". Unchecked borders stay neutral.
968
+ /* WHY: Default checked fill uses --_surface (accent by default via
969
+ intent="accent" being the implicit default for toggle controls).
970
970
  When an explicit [intent] is set, the intent selector's --_surface wins. */
971
971
 
972
972
  :where(ui-checkbox)[aria-checked="true"]::before {
973
- background: var(--accent-surface);
974
- border-color: var(--accent-surface);
975
- }
976
-
977
- :where(ui-checkbox)[aria-checked="true"]::after {
978
- background: var(--accent-surface-ink);
979
- transform: translateY(-50%) scale(1);
980
- }
981
-
982
- :where(ui-checkbox)[intent][aria-checked="true"]::before {
983
973
  background: var(--_surface);
984
974
  border-color: var(--_surface);
985
975
  }
986
976
 
987
- :where(ui-checkbox)[intent][aria-checked="true"]::after {
977
+ :where(ui-checkbox)[aria-checked="true"]::after {
988
978
  background: var(--_surface-ink);
979
+ transform: translateY(-50%) scale(1);
989
980
  }
990
981
 
991
982
  /* ── Indeterminate State ── */
992
983
 
993
984
  :where(ui-checkbox)[aria-checked="mixed"]::before {
994
- background: var(--accent-surface);
995
- border-color: var(--accent-surface);
985
+ background: var(--_surface);
986
+ border-color: var(--_surface);
996
987
  }
997
988
 
998
989
  :where(ui-checkbox)[aria-checked="mixed"]::after {
@@ -1000,17 +991,8 @@
1000
991
  -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");
1001
992
  mask-size: 75%;
1002
993
  -webkit-mask-size: 75%;
1003
- background: var(--accent-surface-ink);
1004
- transform: translateY(-50%) scale(1);
1005
- }
1006
-
1007
- :where(ui-checkbox)[intent][aria-checked="mixed"]::before {
1008
- background: var(--_surface);
1009
- border-color: var(--_surface);
1010
- }
1011
-
1012
- :where(ui-checkbox)[intent][aria-checked="mixed"]::after {
1013
994
  background: var(--_surface-ink);
995
+ transform: translateY(-50%) scale(1);
1014
996
  }
1015
997
 
1016
998
  /* ── Hover ── */
@@ -1027,13 +1009,6 @@
1027
1009
  :where(ui-checkbox)[aria-checked="true"]:hover::before,
1028
1010
  :where(ui-checkbox)[aria-checked="mixed"]:hover::before
1029
1011
  :where(ui-checkbox)[aria-checked="mixed"][force-hover]::before {
1030
- background: var(--accent-surface-hover);
1031
- border-color: var(--accent-surface-hover);
1032
- }
1033
-
1034
- :where(ui-checkbox)[intent][aria-checked="true"]:hover::before,
1035
- :where(ui-checkbox)[intent][aria-checked="mixed"]:hover::before
1036
- :where(ui-checkbox)[intent][aria-checked="mixed"][force-hover]::before {
1037
1012
  background: var(--_surface-hover);
1038
1013
  border-color: var(--_surface-hover);
1039
1014
  }
@@ -1047,12 +1022,6 @@
1047
1022
 
1048
1023
  :where(ui-checkbox)[aria-checked="true"][pressed]::before,
1049
1024
  :where(ui-checkbox)[aria-checked="mixed"][pressed]::before {
1050
- background: var(--accent-surface-active);
1051
- border-color: var(--accent-surface-active);
1052
- }
1053
-
1054
- :where(ui-checkbox)[intent][aria-checked="true"][pressed]::before,
1055
- :where(ui-checkbox)[intent][aria-checked="mixed"][pressed]::before {
1056
1025
  background: var(--_surface-active);
1057
1026
  border-color: var(--_surface-active);
1058
1027
  }
@@ -1699,6 +1668,45 @@
1699
1668
 
1700
1669
  }
1701
1670
 
1671
+ @layer ui {
1672
+
1673
+ /* ╭──────────────────────────────────────────────────────────╮
1674
+ │ ui-kbd │
1675
+ │ Keyboard shortcut indicator. Own font/size scale │
1676
+ │ (like ui-badge) — zero-attribute = md. │
1677
+ ╰──────────────────────────────────────────────────────────╯ */
1678
+
1679
+ :where(ui-kbd) {
1680
+ --_icon-size: 1.125em;
1681
+
1682
+ display: inline-flex;
1683
+ align-items: center;
1684
+ justify-content: center;
1685
+ font-family: ui-monospace, monospace;
1686
+ font-size: var(--ui-kbd-font-md);
1687
+ line-height: 1;
1688
+ min-height: var(--ui-kbd-size-md);
1689
+ padding-inline: 0.4em;
1690
+ border-radius: 0.25rem;
1691
+ background: var(--_ground, var(--_body));
1692
+ border: 1px solid var(--_border-muted);
1693
+ color: var(--_ink-muted);
1694
+ white-space: nowrap;
1695
+ user-select: none;
1696
+ flex-shrink: 0;
1697
+ vertical-align: baseline;
1698
+ }
1699
+
1700
+ /* ── Sizes ── */
1701
+
1702
+ :where(ui-kbd[size="xs"]) { font-size: var(--ui-kbd-font-xs); min-height: var(--ui-kbd-size-xs); }
1703
+ :where(ui-kbd[size="sm"]) { font-size: var(--ui-kbd-font-sm); min-height: var(--ui-kbd-size-sm); }
1704
+ :where(ui-kbd[size="md"]) { font-size: var(--ui-kbd-font-md); min-height: var(--ui-kbd-size-md); }
1705
+ :where(ui-kbd[size="lg"]) { font-size: var(--ui-kbd-font-lg); min-height: var(--ui-kbd-size-lg); }
1706
+ :where(ui-kbd[size="xl"]) { font-size: var(--ui-kbd-font-xl); min-height: var(--ui-kbd-size-xl); }
1707
+
1708
+ }
1709
+
1702
1710
  @layer ui {
1703
1711
 
1704
1712
  /* ── Listbox Base ── */
@@ -1836,11 +1844,14 @@
1836
1844
  │ Uses ui-nav-item / ui-nav-group for children. │
1837
1845
  ╰──────────────────────────────────────────────────────────╯ */
1838
1846
 
1847
+ /* WHY: ui-nav is a transparent flex wrapper — no inline padding.
1848
+ Inline padding is owned by leaf items (nav-item, summary).
1849
+ Block padding is owned by nav-group margins. */
1839
1850
  :where(ui-nav) {
1840
1851
  display: flex;
1841
1852
  flex-direction: column;
1842
1853
  gap: 0;
1843
- padding: var(--_space);
1854
+ padding: 0;
1844
1855
  outline: none;
1845
1856
  }
1846
1857
 
@@ -1855,6 +1866,7 @@
1855
1866
  display: flex;
1856
1867
  align-items: center;
1857
1868
  gap: calc(var(--_space) * 2);
1869
+ padding-inline: calc(var(--_space-k) * var(--_space));
1858
1870
 
1859
1871
  min-height: var(--_min-height);
1860
1872
 
@@ -1951,6 +1963,7 @@
1951
1963
  display: flex;
1952
1964
  align-items: center;
1953
1965
  gap: calc(var(--_space) * 2);
1966
+ padding-inline: calc(var(--_space-k) * var(--_space));
1954
1967
  cursor: pointer;
1955
1968
  user-select: none;
1956
1969
  list-style: none;
@@ -2036,8 +2049,11 @@
2036
2049
  /* WHY: Connector draws from below the header to the bottom of the group.
2037
2050
  Only shown when header contains an icon. Scoped via :has(). */
2038
2051
 
2052
+ /* WHY: Insets account for the summary's padding-inline so the connector
2053
+ line passes through the icon center and child text aligns with header text. */
2039
2054
  :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)) {
2040
- --_group-line-inset: calc(var(--_icon-size) / 2);
2055
+ --_group-pad: calc(var(--_space-k) * var(--_space));
2056
+ --_group-line-inset: calc(var(--_group-pad) + var(--_icon-size) / 2);
2041
2057
  --_group-child-inset: calc(var(--_icon-size) + var(--_space) * 2);
2042
2058
  }
2043
2059
 
@@ -2083,6 +2099,73 @@
2083
2099
  margin-inline-start: var(--_group-child-inset, 0);
2084
2100
  }
2085
2101
 
2102
+ /* ── Nav Group Flyout (collapsed sidebar) ── */
2103
+ /* WHY: In collapsed mode, summary click opens a ui-listbox popover to the right
2104
+ instead of expanding the <details>. Same pattern as sidebar-trigger menus. */
2105
+
2106
+ :where(ui-nav-group) > :where(ui-listbox.nav-group-flyout[popover]) {
2107
+ position: fixed;
2108
+ position-area: inline-end span-block-end;
2109
+ position-try-fallbacks: --nav-flyout-flip-up;
2110
+ margin: 0 0 0 var(--ui-popover-gap);
2111
+ min-width: 200px;
2112
+ max-height: var(--ui-popover-max-height);
2113
+ overflow-y: auto;
2114
+ }
2115
+
2116
+ @position-try --nav-flyout-flip-up {
2117
+ position-area: inline-end span-block-start;
2118
+ }
2119
+
2120
+ /* ── Container Query: Collapsed Sidebar ── */
2121
+ /* WHY: Nav components own their own collapsed behavior via @container.
2122
+ The sidebar aside declares container-name: sidebar. When it shrinks
2123
+ to 48px (icon rail), nav responds to the width — not to [collapsed].
2124
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
2125
+
2126
+ @container sidebar (max-width: 80px) {
2127
+
2128
+ /* Nav items hide entirely — only group headers (with icons) remain. */
2129
+ :where(ui-nav-item) {
2130
+ display: none;
2131
+ }
2132
+
2133
+ /* Summary shrinks to icon-only. Reduced inline padding wraps tightly
2134
+ around the icon; parent align-items: center handles horizontal centering. */
2135
+ :where(ui-nav-group) > :where(details) > :where(summary) {
2136
+ padding-inline: var(--_space);
2137
+ border-radius: var(--_radius);
2138
+ }
2139
+
2140
+ /* Hide chevron — no expand/collapse in icon rail (flyout instead). */
2141
+ :where(ui-nav-group) > :where(details) > :where(summary)::after {
2142
+ display: none;
2143
+ }
2144
+
2145
+ /* Hide vertical connector line and sliding indicator. */
2146
+ :where(ui-nav-group)::after,
2147
+ :where(ui-nav-group)::before {
2148
+ display: none;
2149
+ }
2150
+
2151
+ /* Header collapses to icon-only. font-size: 0 hides text nodes
2152
+ (can't be targeted by CSS). Icon overrides back to normal below. */
2153
+ :where(ui-nav-group-header) {
2154
+ flex: 0 0 auto;
2155
+ font-size: 0;
2156
+ gap: 0;
2157
+ }
2158
+
2159
+ :where(ui-nav-group-header) :where(ui-icon) {
2160
+ font-size: var(--_font-size, 1rem);
2161
+ }
2162
+
2163
+ /* Collapse inter-group spacing in icon rail. */
2164
+ :where(ui-nav-group) + :where(ui-nav-group) {
2165
+ margin-block-start: 0;
2166
+ }
2167
+ }
2168
+
2086
2169
  }
2087
2170
 
2088
2171
  @layer ui {
@@ -2223,27 +2306,15 @@
2223
2306
  }
2224
2307
 
2225
2308
  /* ── Selected ── */
2226
- /* WHY: Default selected fill uses accent so radio is visually distinct
2227
- without needing intent="accent". Unchecked borders stay neutral.
2228
- When an explicit [intent] is set, the intent selector's --_surface wins. */
2229
2309
 
2230
2310
  :where(ui-radio)[aria-checked="true"]::before {
2231
- background: var(--accent-surface);
2232
- border-color: var(--accent-surface);
2233
- }
2234
-
2235
- :where(ui-radio)[aria-checked="true"]::after {
2236
- background: var(--accent-surface-ink);
2237
- transform: translateY(-50%) scale(1);
2238
- }
2239
-
2240
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::before {
2241
2311
  background: var(--_surface);
2242
2312
  border-color: var(--_surface);
2243
2313
  }
2244
2314
 
2245
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::after {
2315
+ :where(ui-radio)[aria-checked="true"]::after {
2246
2316
  background: var(--_surface-ink);
2317
+ transform: translateY(-50%) scale(1);
2247
2318
  }
2248
2319
 
2249
2320
  /* ── Hover ── */
@@ -2258,11 +2329,6 @@
2258
2329
  }
2259
2330
 
2260
2331
  :where(ui-radio)[aria-checked="true"]:hover::before{
2261
- background: var(--accent-surface-hover);
2262
- border-color: var(--accent-surface-hover);
2263
- }
2264
-
2265
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]:hover::before{
2266
2332
  background: var(--_surface-hover);
2267
2333
  border-color: var(--_surface-hover);
2268
2334
  }
@@ -2275,11 +2341,6 @@
2275
2341
  }
2276
2342
 
2277
2343
  :where(ui-radio)[aria-checked="true"][pressed]::before {
2278
- background: var(--accent-surface-active);
2279
- border-color: var(--accent-surface-active);
2280
- }
2281
-
2282
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"][pressed]::before {
2283
2344
  background: var(--_surface-active);
2284
2345
  border-color: var(--_surface-active);
2285
2346
  }
@@ -2377,16 +2438,12 @@
2377
2438
  /* 0% = track-height (circle), 100% = full width */
2378
2439
  width: calc(var(--_track-height) + (100% - var(--_track-height)) * var(--_progress));
2379
2440
  border-radius: calc(var(--_track-height) / 2);
2380
- background: var(--accent-surface);
2441
+ background: var(--_surface);
2381
2442
 
2382
2443
  transition:
2383
2444
  background var(--_duration) var(--_easing);
2384
2445
  }
2385
2446
 
2386
- :where(ui-range)[intent]::after {
2387
- background: var(--_surface);
2388
- }
2389
-
2390
2447
  /* ── Thumb ── */
2391
2448
 
2392
2449
  :where(ui-range) > :where(.ui-range-thumb) {
@@ -2398,8 +2455,8 @@
2398
2455
  width: var(--_thumb-size);
2399
2456
  height: var(--_thumb-size);
2400
2457
  border-radius: 50%;
2401
- background: var(--accent-surface-ink);
2402
- border: 2px solid var(--accent-surface);
2458
+ background: var(--_surface-ink);
2459
+ border: 2px solid var(--_surface);
2403
2460
  box-shadow: var(--ui-shadow-sm);
2404
2461
  pointer-events: none;
2405
2462
 
@@ -2409,11 +2466,6 @@
2409
2466
  border-color var(--_duration) var(--_easing);
2410
2467
  }
2411
2468
 
2412
- :where(ui-range)[intent] > :where(.ui-range-thumb) {
2413
- background: var(--_surface-ink);
2414
- border-color: var(--_surface);
2415
- }
2416
-
2417
2469
  /* ── Focus ── */
2418
2470
 
2419
2471
  :where(ui-range):focus-visible > :where(.ui-range-thumb){
@@ -2424,19 +2476,10 @@
2424
2476
  /* ── Hover ── */
2425
2477
 
2426
2478
  :where(ui-range):hover::after{
2427
- background: var(--accent-surface-hover);
2428
- }
2429
-
2430
- :where(ui-range)[intent]:hover::after{
2431
2479
  background: var(--_surface-hover);
2432
2480
  }
2433
2481
 
2434
2482
  :where(ui-range):hover > :where(.ui-range-thumb){
2435
- border-color: var(--accent-surface-hover);
2436
- box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--accent-surface);
2437
- }
2438
-
2439
- :where(ui-range)[intent]:hover > :where(.ui-range-thumb){
2440
2483
  border-color: var(--_surface-hover);
2441
2484
  box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--_surface);
2442
2485
  }
@@ -2444,19 +2487,10 @@
2444
2487
  /* ── Active (dragging) ── */
2445
2488
 
2446
2489
  :where(ui-range)[pressed]::after {
2447
- background: var(--accent-surface-active);
2448
- }
2449
-
2450
- :where(ui-range)[intent][pressed]::after {
2451
2490
  background: var(--_surface-active);
2452
2491
  }
2453
2492
 
2454
2493
  :where(ui-range)[pressed] > :where(.ui-range-thumb) {
2455
- border-color: var(--accent-surface-active);
2456
- box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--accent-surface);
2457
- }
2458
-
2459
- :where(ui-range)[intent][pressed] > :where(.ui-range-thumb) {
2460
2494
  border-color: var(--_surface-active);
2461
2495
  box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--_surface);
2462
2496
  }
@@ -3003,22 +3037,13 @@
3003
3037
  When an explicit [intent] is set, the intent selector's --_surface wins. */
3004
3038
 
3005
3039
  :where(ui-switch)[aria-checked="true"]::before {
3006
- background: var(--accent-surface);
3007
- border-color: var(--accent-surface);
3040
+ background: var(--_surface);
3041
+ border-color: var(--_surface);
3008
3042
  }
3009
3043
 
3010
3044
  :where(ui-switch)[aria-checked="true"]::after {
3011
3045
  /* Slide thumb to the right via translateX — GPU composited */
3012
3046
  transform: translateY(-50%) translateX(calc(var(--_track-width) - var(--_thumb-size) - var(--_thumb-offset) * 2));
3013
- background: var(--accent-surface-ink);
3014
- }
3015
-
3016
- :where(ui-switch)[intent][aria-checked="true"]::before {
3017
- background: var(--_surface);
3018
- border-color: var(--_surface);
3019
- }
3020
-
3021
- :where(ui-switch)[intent][aria-checked="true"]::after {
3022
3047
  background: var(--_surface-ink);
3023
3048
  }
3024
3049
 
@@ -3034,11 +3059,6 @@
3034
3059
  }
3035
3060
 
3036
3061
  :where(ui-switch)[aria-checked="true"]:hover::before{
3037
- background: var(--accent-surface-hover);
3038
- border-color: var(--accent-surface-hover);
3039
- }
3040
-
3041
- :where(ui-switch)[intent][aria-checked="true"]:hover::before{
3042
3062
  background: var(--_surface-hover);
3043
3063
  border-color: var(--_surface-hover);
3044
3064
  }
@@ -3051,11 +3071,6 @@
3051
3071
  }
3052
3072
 
3053
3073
  :where(ui-switch)[aria-checked="true"][pressed]::before {
3054
- background: var(--accent-surface-active);
3055
- border-color: var(--accent-surface-active);
3056
- }
3057
-
3058
- :where(ui-switch)[intent][aria-checked="true"][pressed]::before {
3059
3074
  background: var(--_surface-active);
3060
3075
  border-color: var(--_surface-active);
3061
3076
  }
@@ -4494,7 +4509,7 @@
4494
4509
 
4495
4510
  :where(ui-layout-chat) :where(.layout-resize-handle):hover,
4496
4511
  :where(ui-layout-chat[resizing]) :where(.layout-resize-handle) {
4497
- background: var(--neutral-border-muted);
4512
+ background: var(--_border-muted);
4498
4513
  }
4499
4514
 
4500
4515
  }
@@ -4556,7 +4571,7 @@
4556
4571
 
4557
4572
  :where(ui-layout-inspector) :where(.layout-resize-handle):hover,
4558
4573
  :where(ui-layout-inspector[resizing]) :where(.layout-resize-handle) {
4559
- background: var(--neutral-border-muted);
4574
+ background: var(--_border-muted);
4560
4575
  }
4561
4576
 
4562
4577
  }
@@ -4583,16 +4598,16 @@
4583
4598
  content: '';
4584
4599
  width: var(--ui-layout-sidebar-width);
4585
4600
  flex-shrink: 0;
4586
- background: var(--neutral-body);
4587
- border-right: 1px solid var(--neutral-border-muted);
4601
+ background: var(--_body);
4602
+ border-right: 1px solid var(--_border-muted);
4588
4603
  }
4589
4604
 
4590
4605
  :where(ui-layout-sidebar):not([data-ready])[collapsed]::before {
4591
4606
  content: '';
4592
4607
  width: 48px;
4593
4608
  flex-shrink: 0;
4594
- background: var(--neutral-body);
4595
- border-right: 1px solid var(--neutral-border-muted);
4609
+ background: var(--_body);
4610
+ border-right: 1px solid var(--_border-muted);
4596
4611
  }
4597
4612
 
4598
4613
  /* ── Content Column ── */
@@ -4608,16 +4623,23 @@
4608
4623
  }
4609
4624
 
4610
4625
  /* ── Sidebar Aside ── */
4626
+ /* WHY: position: relative creates the containing block for absolute
4627
+ header/footer overlays. Content fills the full height and scrolls
4628
+ underneath the pinned header/footer. */
4611
4629
 
4612
4630
  :where(ui-layout-sidebar) > :where([slot="sidebar"]) {
4631
+ --_sidebar-header-height: 0px;
4632
+ --_sidebar-footer-height: 0px;
4633
+
4634
+ container-name: sidebar;
4635
+ container-type: inline-size;
4636
+ position: sticky;
4637
+ top: 0;
4613
4638
  width: var(--ui-layout-sidebar-width);
4614
4639
  min-width: 160px;
4615
4640
  max-width: 400px;
4616
4641
  height: 100dvh;
4617
- position: sticky;
4618
- top: 0;
4619
- display: flex;
4620
- flex-direction: column;
4642
+ display: block;
4621
4643
  transition: width var(--_duration) var(--_easing), min-width var(--_duration) var(--_easing);
4622
4644
  overflow: hidden;
4623
4645
  z-index: 10;
@@ -4629,7 +4651,9 @@
4629
4651
  }
4630
4652
 
4631
4653
  /* ── Collapsed Icon Rail ── */
4632
- /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding. */
4654
+ /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding.
4655
+ [collapsed] drives the aside to 48px which triggers @container sidebar queries
4656
+ in this file and in child component CSS (ui-nav.css, etc.). */
4633
4657
 
4634
4658
  :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) {
4635
4659
  width: 48px;
@@ -4650,11 +4674,7 @@
4650
4674
 
4651
4675
  :where(ui-layout-sidebar) :where(.layout-resize-handle):hover,
4652
4676
  :where(ui-layout-sidebar) > :where([slot="sidebar"][resizing]) :where(.layout-resize-handle) {
4653
- background: var(--neutral-border-muted);
4654
- }
4655
-
4656
- :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) :where(.layout-resize-handle) {
4657
- display: none;
4677
+ background: var(--_border-muted);
4658
4678
  }
4659
4679
 
4660
4680
  /* WHY: When collapsed to icon rail, the sidebar edge meets content — drop left padding. */
@@ -4668,47 +4688,88 @@
4668
4688
  }
4669
4689
 
4670
4690
  /* ── Sidebar Header ── */
4691
+ /* WHY: Absolute-positioned overlay pinned to top of aside. Content scrolls
4692
+ underneath it. z-index: 2 sits above content (z-index: 0). */
4671
4693
 
4672
4694
  :where(ui-layout-sidebar-header) {
4695
+ position: absolute;
4696
+ top: 0;
4697
+ left: 0;
4698
+ right: 0;
4699
+ z-index: 2;
4673
4700
  display: flex;
4674
- align-items: center;
4675
- min-height: var(--ui-layout-bar-height);
4676
- gap: calc(var(--_space) * 2);
4677
- padding-block: var(--_space);
4678
- padding-inline: calc(var(--_space-k) * var(--_space));
4679
- flex-shrink: 0;
4701
+ flex-direction: column;
4680
4702
  }
4681
4703
 
4682
4704
  /* ── Sidebar Content ── */
4705
+ /* WHY: Full height of the aside, scrollable. Padding-block offsets keep
4706
+ content from starting behind header / ending behind footer.
4707
+ Fade-out alpha mask dissolves content edges under the overlays. */
4683
4708
 
4684
4709
  :where(ui-layout-sidebar-content) {
4685
4710
  display: flex;
4686
4711
  flex-direction: column;
4687
- gap: calc(var(--_space) * 2);
4688
- padding-block: var(--_space);
4689
- padding-inline: calc(var(--_space-k) * var(--_space));
4690
- flex: 1;
4691
- min-height: 0;
4712
+ padding-block-start: var(--_sidebar-header-height);
4713
+ padding-block-end: var(--_sidebar-footer-height);
4692
4714
  width: 100%;
4715
+ height: 100%;
4716
+ overflow-y: auto;
4717
+ scrollbar-width: none;
4718
+ }
4719
+
4720
+ /* WHY: When header is present, fade top edge. Content dissolves as it
4721
+ scrolls under the header — transparent at top, fully visible by 1.25×
4722
+ the header height. No opaque header background needed. */
4723
+ :where(ui-layout-sidebar-content)[data-has-header] {
4724
+ mask-image: linear-gradient(
4725
+ to bottom,
4726
+ transparent 0,
4727
+ black calc(var(--_sidebar-header-height) * 1.25),
4728
+ black 100%
4729
+ );
4730
+ }
4731
+
4732
+ /* WHY: When footer is present, fade bottom edge. */
4733
+ :where(ui-layout-sidebar-content)[data-has-footer] {
4734
+ mask-image: linear-gradient(
4735
+ to bottom,
4736
+ black 0,
4737
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
4738
+ transparent 100%
4739
+ );
4740
+ }
4741
+
4742
+ /* WHY: When both header AND footer are present, fade both edges. */
4743
+ :where(ui-layout-sidebar-content)[data-has-header][data-has-footer] {
4744
+ mask-image: linear-gradient(
4745
+ to bottom,
4746
+ transparent 0,
4747
+ black calc(var(--_sidebar-header-height) * 1.25),
4748
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
4749
+ transparent 100%
4750
+ );
4693
4751
  }
4694
4752
 
4695
4753
  /* ── Sidebar Footer ── */
4754
+ /* WHY: Absolute-positioned overlay pinned to bottom of aside. */
4696
4755
 
4697
4756
  :where(ui-layout-sidebar-footer) {
4757
+ position: absolute;
4758
+ bottom: 0;
4759
+ left: 0;
4760
+ right: 0;
4761
+ z-index: 2;
4698
4762
  display: flex;
4699
- align-items: center;
4700
- min-height: var(--ui-layout-bar-height);
4701
- gap: calc(var(--_space) * 2);
4702
- padding-block: var(--_space);
4703
- padding-inline: calc(var(--_space-k) * var(--_space));
4704
- flex-shrink: 0;
4763
+ flex-direction: column;
4705
4764
  }
4706
4765
 
4707
- /* ── Sidebar Trigger ── */
4708
- /* WHY: Menu trigger for sidebar header/footer. Styled like ui-nav-group-header:
4709
- bold text, icon support, hover color. Opens a popover on click. */
4766
+ /* ── Sidebar Item ── */
4767
+ /* WHY: Universal sidebar row element. Provides inline padding for any content
4768
+ (buttons, links, icons). When a child ui-listbox[popover] is present, JS
4769
+ wires PopoverController for click-to-toggle menu behavior.
4770
+ Absorbs the old ui-layout-sidebar-trigger role — one element for all rows. */
4710
4771
 
4711
- :where(ui-layout-sidebar-trigger) {
4772
+ :where(ui-layout-sidebar-item) {
4712
4773
  display: flex;
4713
4774
  align-items: center;
4714
4775
  flex: 1;
@@ -4724,29 +4785,45 @@
4724
4785
  user-select: none;
4725
4786
  border: none;
4726
4787
  background: none;
4727
- padding: var(--_space);
4788
+ padding-block: var(--_space);
4789
+ padding-inline: calc(var(--_space-k) * var(--_space));
4728
4790
  border-radius: var(--_radius);
4729
4791
  transition: color var(--_duration) var(--_easing);
4730
4792
  }
4731
4793
 
4732
- :where(ui-layout-sidebar-trigger):hover{
4794
+ /* WHY: Items in header/footer match the breadcrumb bar height so the
4795
+ first sidebar row aligns horizontally with the breadcrumb. */
4796
+ :where(ui-layout-sidebar-header) > :where(ui-layout-sidebar-item),
4797
+ :where(ui-layout-sidebar-footer) > :where(ui-layout-sidebar-item) {
4798
+ min-height: var(--ui-layout-bar-height);
4799
+ }
4800
+
4801
+ :where(ui-layout-sidebar-item):hover{
4733
4802
  color: var(--_ink-strong);
4734
4803
  }
4735
4804
 
4736
- :where(ui-layout-sidebar-trigger):focus-visible{
4805
+ :where(ui-layout-sidebar-item):focus-visible{
4737
4806
  outline: 2px solid var(--ui-focus-ring);
4738
4807
  outline-offset: -2px;
4739
4808
  }
4740
4809
 
4741
4810
  /* WHY: Trailing caret pushes to end — same pattern as button justify="spread". */
4742
- :where(ui-layout-sidebar-trigger) > :where([slot="trailing"]) {
4811
+ :where(ui-layout-sidebar-item) > :where([slot="trailing"]) {
4743
4812
  margin-inline-start: auto;
4744
4813
  flex-shrink: 0;
4745
4814
  color: var(--_ink-muted);
4746
4815
  }
4747
4816
 
4817
+ /* WHY: When the item wraps a sub-component (e.g. ui-button) that provides
4818
+ its own icon, hide [slot="icon"] in expanded mode — it only serves as the
4819
+ bare collapsed-rail representation. Items without sub-components (just
4820
+ icon + label + trailing) keep [slot="icon"] visible in both modes. */
4821
+ :where(ui-layout-sidebar-item:has(> ui-button)) > :where([slot="icon"]) {
4822
+ display: none;
4823
+ }
4824
+
4748
4825
  /* WHY: Title text truncates when sidebar narrows during resize. */
4749
- :where(ui-layout-sidebar-trigger) > :where([slot="label"]) {
4826
+ :where(ui-layout-sidebar-item) > :where([slot="label"]) {
4750
4827
  flex: 1;
4751
4828
  min-width: 0;
4752
4829
  white-space: nowrap;
@@ -4754,25 +4831,67 @@
4754
4831
  text-overflow: ellipsis;
4755
4832
  }
4756
4833
 
4757
- /* ── Trigger Popover ── */
4758
- /* WHY: Popover opens to the right of the sidebar trigger.
4834
+ /* ── Item Popover ── */
4835
+ /* WHY: Popover opens to the right of the sidebar item.
4759
4836
  Default: top-aligned, grows downward (span-block-end).
4760
4837
  Flip: bottom-aligned, grows upward (span-block-start). */
4761
4838
 
4762
- :where(ui-layout-sidebar-trigger) > :where(ui-listbox[popover]) {
4839
+ :where(ui-layout-sidebar-item) > :where(ui-listbox[popover]) {
4763
4840
  position: fixed;
4764
4841
  position-area: inline-end span-block-end;
4765
- position-try-fallbacks: --flip-up;
4842
+ position-try-fallbacks: --sidebar-item-flip-up;
4766
4843
  margin: 0 0 0 var(--ui-popover-gap);
4767
4844
  min-width: 200px;
4768
4845
  max-height: var(--ui-popover-max-height);
4769
4846
  overflow-y: auto;
4770
4847
  }
4771
4848
 
4772
- @position-try --flip-up {
4849
+ @position-try --sidebar-item-flip-up {
4773
4850
  position-area: inline-end span-block-start;
4774
4851
  }
4775
4852
 
4853
+ /* ── Container Query: Collapsed Sidebar ── */
4854
+ /* WHY: Each component owns its own collapsed behavior via @container.
4855
+ The aside is the container (container-name: sidebar). When it shrinks
4856
+ to 48px (icon rail), components respond to the width, not to [collapsed].
4857
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
4858
+
4859
+ @container sidebar (max-width: 80px) {
4860
+
4861
+ /* Resize handle — no dragging in icon rail */
4862
+ :where(.layout-resize-handle) {
4863
+ display: none;
4864
+ }
4865
+
4866
+ /* Header/footer — center content horizontally */
4867
+ :where(ui-layout-sidebar-header),
4868
+ :where(ui-layout-sidebar-footer) {
4869
+ align-items: center;
4870
+ }
4871
+
4872
+ /* Content — center items in the 48px rail */
4873
+ :where(ui-layout-sidebar-content) {
4874
+ align-items: center;
4875
+ }
4876
+
4877
+ /* Item — shrink to icon-only. Show [slot="icon"], hide everything else.
4878
+ WHY: Sidebar items can contain complex children (buttons with slots,
4879
+ links, etc.). Rather than overriding sub-component styles, the item
4880
+ provides its own bare icon via [slot="icon"] for the collapsed rail. */
4881
+ :where(ui-layout-sidebar-item) {
4882
+ flex: 0 0 auto;
4883
+ padding-inline: var(--_space);
4884
+ }
4885
+
4886
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
4887
+ display: block;
4888
+ }
4889
+
4890
+ :where(ui-layout-sidebar-item) > :where(:not([slot="icon"]):not(ui-listbox[popover]):not(.nav-group-flyout)) {
4891
+ display: none;
4892
+ }
4893
+ }
4894
+
4776
4895
  }
4777
4896
 
4778
4897
  @layer ui {