@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
@@ -365,7 +365,7 @@
365
365
  font-weight: var(--_font-weight);
366
366
 
367
367
  border-radius: var(--_radius);
368
- border: 1px solid var(--_border-color, var(--neutral-border-muted));
368
+ border: 1px solid var(--_border-color, var(--_border-muted));
369
369
 
370
370
  background: var(--_background, var(--_button));
371
371
  color: var(--_color, var(--_ink));
@@ -382,17 +382,17 @@
382
382
 
383
383
  :where(ui-button):hover,
384
384
  :where(ui-button)[force-hover] {
385
- background: var(--_background-hover, var(--neutral-button-hover));
385
+ background: var(--_background-hover, var(--_button-hover));
386
386
  color: var(--_color-hover, var(--_ink-hover));
387
- border-color: var(--_border-color-hover, var(--neutral-border-hover));
387
+ border-color: var(--_border-color-hover, var(--_border-hover));
388
388
  }
389
389
 
390
390
  :where(ui-button):active,
391
391
  :where(ui-button)[pressed],
392
392
  :where(ui-button)[force-active] {
393
- background: var(--_background-active, var(--neutral-button-active));
393
+ background: var(--_background-active, var(--_button-active));
394
394
  color: var(--_color-active, var(--_ink-active));
395
- border-color: var(--_border-color-active, var(--neutral-border-active));
395
+ border-color: var(--_border-color-active, var(--_border-active));
396
396
  }
397
397
 
398
398
  :where(ui-button):focus-visible,
@@ -402,9 +402,9 @@
402
402
  }
403
403
 
404
404
  :where(ui-button)[aria-disabled="true"] {
405
- background: var(--_background-disabled, var(--neutral-button-disabled));
405
+ background: var(--_background-disabled, var(--_button-disabled));
406
406
  color: var(--_color-disabled, var(--_ink-disabled));
407
- border-color: var(--_border-color-disabled, var(--neutral-border-muted));
407
+ border-color: var(--_border-color-disabled, var(--_border-muted));
408
408
  cursor: not-allowed;
409
409
  pointer-events: none;
410
410
  }
@@ -977,34 +977,25 @@
977
977
  }
978
978
 
979
979
  /* ── Checked State ── */
980
- /* WHY: Default checked fill uses accent so checkbox is visually distinct
981
- without needing intent="accent". Unchecked borders stay neutral.
980
+ /* WHY: Default checked fill uses --_surface (accent by default via
981
+ intent="accent" being the implicit default for toggle controls).
982
982
  When an explicit [intent] is set, the intent selector's --_surface wins. */
983
983
 
984
984
  :where(ui-checkbox)[aria-checked="true"]::before {
985
- background: var(--accent-surface);
986
- border-color: var(--accent-surface);
987
- }
988
-
989
- :where(ui-checkbox)[aria-checked="true"]::after {
990
- background: var(--accent-surface-ink);
991
- transform: translateY(-50%) scale(1);
992
- }
993
-
994
- :where(ui-checkbox)[intent][aria-checked="true"]::before {
995
985
  background: var(--_surface);
996
986
  border-color: var(--_surface);
997
987
  }
998
988
 
999
- :where(ui-checkbox)[intent][aria-checked="true"]::after {
989
+ :where(ui-checkbox)[aria-checked="true"]::after {
1000
990
  background: var(--_surface-ink);
991
+ transform: translateY(-50%) scale(1);
1001
992
  }
1002
993
 
1003
994
  /* ── Indeterminate State ── */
1004
995
 
1005
996
  :where(ui-checkbox)[aria-checked="mixed"]::before {
1006
- background: var(--accent-surface);
1007
- border-color: var(--accent-surface);
997
+ background: var(--_surface);
998
+ border-color: var(--_surface);
1008
999
  }
1009
1000
 
1010
1001
  :where(ui-checkbox)[aria-checked="mixed"]::after {
@@ -1012,17 +1003,8 @@
1012
1003
  -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");
1013
1004
  mask-size: 75%;
1014
1005
  -webkit-mask-size: 75%;
1015
- background: var(--accent-surface-ink);
1016
- transform: translateY(-50%) scale(1);
1017
- }
1018
-
1019
- :where(ui-checkbox)[intent][aria-checked="mixed"]::before {
1020
- background: var(--_surface);
1021
- border-color: var(--_surface);
1022
- }
1023
-
1024
- :where(ui-checkbox)[intent][aria-checked="mixed"]::after {
1025
1006
  background: var(--_surface-ink);
1007
+ transform: translateY(-50%) scale(1);
1026
1008
  }
1027
1009
 
1028
1010
  /* ── Hover ── */
@@ -1042,14 +1024,6 @@
1042
1024
  :where(ui-checkbox)[aria-checked="mixed"]:hover::before,
1043
1025
  :where(ui-checkbox)[aria-checked="true"][force-hover]::before,
1044
1026
  :where(ui-checkbox)[aria-checked="mixed"][force-hover]::before {
1045
- background: var(--accent-surface-hover);
1046
- border-color: var(--accent-surface-hover);
1047
- }
1048
-
1049
- :where(ui-checkbox)[intent][aria-checked="true"]:hover::before,
1050
- :where(ui-checkbox)[intent][aria-checked="mixed"]:hover::before,
1051
- :where(ui-checkbox)[intent][aria-checked="true"][force-hover]::before,
1052
- :where(ui-checkbox)[intent][aria-checked="mixed"][force-hover]::before {
1053
1027
  background: var(--_surface-hover);
1054
1028
  border-color: var(--_surface-hover);
1055
1029
  }
@@ -1063,12 +1037,6 @@
1063
1037
 
1064
1038
  :where(ui-checkbox)[aria-checked="true"][pressed]::before,
1065
1039
  :where(ui-checkbox)[aria-checked="mixed"][pressed]::before {
1066
- background: var(--accent-surface-active);
1067
- border-color: var(--accent-surface-active);
1068
- }
1069
-
1070
- :where(ui-checkbox)[intent][aria-checked="true"][pressed]::before,
1071
- :where(ui-checkbox)[intent][aria-checked="mixed"][pressed]::before {
1072
1040
  background: var(--_surface-active);
1073
1041
  border-color: var(--_surface-active);
1074
1042
  }
@@ -1722,6 +1690,45 @@
1722
1690
 
1723
1691
  }
1724
1692
 
1693
+ @layer ui {
1694
+
1695
+ /* ╭──────────────────────────────────────────────────────────╮
1696
+ │ ui-kbd │
1697
+ │ Keyboard shortcut indicator. Own font/size scale │
1698
+ │ (like ui-badge) — zero-attribute = md. │
1699
+ ╰──────────────────────────────────────────────────────────╯ */
1700
+
1701
+ :where(ui-kbd) {
1702
+ --_icon-size: 1.125em;
1703
+
1704
+ display: inline-flex;
1705
+ align-items: center;
1706
+ justify-content: center;
1707
+ font-family: ui-monospace, monospace;
1708
+ font-size: var(--ui-kbd-font-md);
1709
+ line-height: 1;
1710
+ min-height: var(--ui-kbd-size-md);
1711
+ padding-inline: 0.4em;
1712
+ border-radius: 0.25rem;
1713
+ background: var(--_ground, var(--_body));
1714
+ border: 1px solid var(--_border-muted);
1715
+ color: var(--_ink-muted);
1716
+ white-space: nowrap;
1717
+ user-select: none;
1718
+ flex-shrink: 0;
1719
+ vertical-align: baseline;
1720
+ }
1721
+
1722
+ /* ── Sizes ── */
1723
+
1724
+ :where(ui-kbd[size="xs"]) { font-size: var(--ui-kbd-font-xs); min-height: var(--ui-kbd-size-xs); }
1725
+ :where(ui-kbd[size="sm"]) { font-size: var(--ui-kbd-font-sm); min-height: var(--ui-kbd-size-sm); }
1726
+ :where(ui-kbd[size="md"]) { font-size: var(--ui-kbd-font-md); min-height: var(--ui-kbd-size-md); }
1727
+ :where(ui-kbd[size="lg"]) { font-size: var(--ui-kbd-font-lg); min-height: var(--ui-kbd-size-lg); }
1728
+ :where(ui-kbd[size="xl"]) { font-size: var(--ui-kbd-font-xl); min-height: var(--ui-kbd-size-xl); }
1729
+
1730
+ }
1731
+
1725
1732
  @layer ui {
1726
1733
 
1727
1734
  /* ── Listbox Base ── */
@@ -1862,11 +1869,14 @@
1862
1869
  │ Uses ui-nav-item / ui-nav-group for children. │
1863
1870
  ╰──────────────────────────────────────────────────────────╯ */
1864
1871
 
1872
+ /* WHY: ui-nav is a transparent flex wrapper — no inline padding.
1873
+ Inline padding is owned by leaf items (nav-item, summary).
1874
+ Block padding is owned by nav-group margins. */
1865
1875
  :where(ui-nav) {
1866
1876
  display: flex;
1867
1877
  flex-direction: column;
1868
1878
  gap: 0;
1869
- padding: var(--_space);
1879
+ padding: 0;
1870
1880
  outline: none;
1871
1881
  }
1872
1882
 
@@ -1881,6 +1891,7 @@
1881
1891
  display: flex;
1882
1892
  align-items: center;
1883
1893
  gap: calc(var(--_space) * 2);
1894
+ padding-inline: calc(var(--_space-k) * var(--_space));
1884
1895
 
1885
1896
  min-height: var(--_min-height);
1886
1897
 
@@ -1980,6 +1991,7 @@
1980
1991
  display: flex;
1981
1992
  align-items: center;
1982
1993
  gap: calc(var(--_space) * 2);
1994
+ padding-inline: calc(var(--_space-k) * var(--_space));
1983
1995
  cursor: pointer;
1984
1996
  user-select: none;
1985
1997
  list-style: none;
@@ -2067,8 +2079,11 @@
2067
2079
  /* WHY: Connector draws from below the header to the bottom of the group.
2068
2080
  Only shown when header contains an icon. Scoped via :has(). */
2069
2081
 
2082
+ /* WHY: Insets account for the summary's padding-inline so the connector
2083
+ line passes through the icon center and child text aligns with header text. */
2070
2084
  :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)) {
2071
- --_group-line-inset: calc(var(--_icon-size) / 2);
2085
+ --_group-pad: calc(var(--_space-k) * var(--_space));
2086
+ --_group-line-inset: calc(var(--_group-pad) + var(--_icon-size) / 2);
2072
2087
  --_group-child-inset: calc(var(--_icon-size) + var(--_space) * 2);
2073
2088
  }
2074
2089
 
@@ -2114,6 +2129,73 @@
2114
2129
  margin-inline-start: var(--_group-child-inset, 0);
2115
2130
  }
2116
2131
 
2132
+ /* ── Nav Group Flyout (collapsed sidebar) ── */
2133
+ /* WHY: In collapsed mode, summary click opens a ui-listbox popover to the right
2134
+ instead of expanding the <details>. Same pattern as sidebar-trigger menus. */
2135
+
2136
+ :where(ui-nav-group) > :where(ui-listbox.nav-group-flyout[popover]) {
2137
+ position: fixed;
2138
+ position-area: inline-end span-block-end;
2139
+ position-try-fallbacks: --nav-flyout-flip-up;
2140
+ margin: 0 0 0 var(--ui-popover-gap);
2141
+ min-width: 200px;
2142
+ max-height: var(--ui-popover-max-height);
2143
+ overflow-y: auto;
2144
+ }
2145
+
2146
+ @position-try --nav-flyout-flip-up {
2147
+ position-area: inline-end span-block-start;
2148
+ }
2149
+
2150
+ /* ── Container Query: Collapsed Sidebar ── */
2151
+ /* WHY: Nav components own their own collapsed behavior via @container.
2152
+ The sidebar aside declares container-name: sidebar. When it shrinks
2153
+ to 48px (icon rail), nav responds to the width — not to [collapsed].
2154
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
2155
+
2156
+ @container sidebar (max-width: 80px) {
2157
+
2158
+ /* Nav items hide entirely — only group headers (with icons) remain. */
2159
+ :where(ui-nav-item) {
2160
+ display: none;
2161
+ }
2162
+
2163
+ /* Summary shrinks to icon-only. Reduced inline padding wraps tightly
2164
+ around the icon; parent align-items: center handles horizontal centering. */
2165
+ :where(ui-nav-group) > :where(details) > :where(summary) {
2166
+ padding-inline: var(--_space);
2167
+ border-radius: var(--_radius);
2168
+ }
2169
+
2170
+ /* Hide chevron — no expand/collapse in icon rail (flyout instead). */
2171
+ :where(ui-nav-group) > :where(details) > :where(summary)::after {
2172
+ display: none;
2173
+ }
2174
+
2175
+ /* Hide vertical connector line and sliding indicator. */
2176
+ :where(ui-nav-group)::after,
2177
+ :where(ui-nav-group)::before {
2178
+ display: none;
2179
+ }
2180
+
2181
+ /* Header collapses to icon-only. font-size: 0 hides text nodes
2182
+ (can't be targeted by CSS). Icon overrides back to normal below. */
2183
+ :where(ui-nav-group-header) {
2184
+ flex: 0 0 auto;
2185
+ font-size: 0;
2186
+ gap: 0;
2187
+ }
2188
+
2189
+ :where(ui-nav-group-header) :where(ui-icon) {
2190
+ font-size: var(--_font-size, 1rem);
2191
+ }
2192
+
2193
+ /* Collapse inter-group spacing in icon rail. */
2194
+ :where(ui-nav-group) + :where(ui-nav-group) {
2195
+ margin-block-start: 0;
2196
+ }
2197
+ }
2198
+
2117
2199
  }
2118
2200
 
2119
2201
  @layer ui {
@@ -2254,27 +2336,15 @@
2254
2336
  }
2255
2337
 
2256
2338
  /* ── Selected ── */
2257
- /* WHY: Default selected fill uses accent so radio is visually distinct
2258
- without needing intent="accent". Unchecked borders stay neutral.
2259
- When an explicit [intent] is set, the intent selector's --_surface wins. */
2260
2339
 
2261
2340
  :where(ui-radio)[aria-checked="true"]::before {
2262
- background: var(--accent-surface);
2263
- border-color: var(--accent-surface);
2264
- }
2265
-
2266
- :where(ui-radio)[aria-checked="true"]::after {
2267
- background: var(--accent-surface-ink);
2268
- transform: translateY(-50%) scale(1);
2269
- }
2270
-
2271
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::before {
2272
2341
  background: var(--_surface);
2273
2342
  border-color: var(--_surface);
2274
2343
  }
2275
2344
 
2276
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::after {
2345
+ :where(ui-radio)[aria-checked="true"]::after {
2277
2346
  background: var(--_surface-ink);
2347
+ transform: translateY(-50%) scale(1);
2278
2348
  }
2279
2349
 
2280
2350
  /* ── Hover ── */
@@ -2292,12 +2362,6 @@
2292
2362
 
2293
2363
  :where(ui-radio)[aria-checked="true"]:hover::before,
2294
2364
  :where(ui-radio)[aria-checked="true"][force-hover]::before {
2295
- background: var(--accent-surface-hover);
2296
- border-color: var(--accent-surface-hover);
2297
- }
2298
-
2299
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]:hover::before,
2300
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"][force-hover]::before {
2301
2365
  background: var(--_surface-hover);
2302
2366
  border-color: var(--_surface-hover);
2303
2367
  }
@@ -2310,11 +2374,6 @@
2310
2374
  }
2311
2375
 
2312
2376
  :where(ui-radio)[aria-checked="true"][pressed]::before {
2313
- background: var(--accent-surface-active);
2314
- border-color: var(--accent-surface-active);
2315
- }
2316
-
2317
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"][pressed]::before {
2318
2377
  background: var(--_surface-active);
2319
2378
  border-color: var(--_surface-active);
2320
2379
  }
@@ -2413,16 +2472,12 @@
2413
2472
  /* 0% = track-height (circle), 100% = full width */
2414
2473
  width: calc(var(--_track-height) + (100% - var(--_track-height)) * var(--_progress));
2415
2474
  border-radius: calc(var(--_track-height) / 2);
2416
- background: var(--accent-surface);
2475
+ background: var(--_surface);
2417
2476
 
2418
2477
  transition:
2419
2478
  background var(--_duration) var(--_easing);
2420
2479
  }
2421
2480
 
2422
- :where(ui-range)[intent]::after {
2423
- background: var(--_surface);
2424
- }
2425
-
2426
2481
  /* ── Thumb ── */
2427
2482
 
2428
2483
  :where(ui-range) > :where(.ui-range-thumb) {
@@ -2434,8 +2489,8 @@
2434
2489
  width: var(--_thumb-size);
2435
2490
  height: var(--_thumb-size);
2436
2491
  border-radius: 50%;
2437
- background: var(--accent-surface-ink);
2438
- border: 2px solid var(--accent-surface);
2492
+ background: var(--_surface-ink);
2493
+ border: 2px solid var(--_surface);
2439
2494
  box-shadow: var(--ui-shadow-sm);
2440
2495
  pointer-events: none;
2441
2496
 
@@ -2445,11 +2500,6 @@
2445
2500
  border-color var(--_duration) var(--_easing);
2446
2501
  }
2447
2502
 
2448
- :where(ui-range)[intent] > :where(.ui-range-thumb) {
2449
- background: var(--_surface-ink);
2450
- border-color: var(--_surface);
2451
- }
2452
-
2453
2503
  /* ── Focus ── */
2454
2504
 
2455
2505
  :where(ui-range):focus-visible > :where(.ui-range-thumb),
@@ -2462,22 +2512,11 @@
2462
2512
 
2463
2513
  :where(ui-range):hover::after,
2464
2514
  :where(ui-range)[force-hover]::after {
2465
- background: var(--accent-surface-hover);
2466
- }
2467
-
2468
- :where(ui-range)[intent]:hover::after,
2469
- :where(ui-range)[intent][force-hover]::after {
2470
2515
  background: var(--_surface-hover);
2471
2516
  }
2472
2517
 
2473
2518
  :where(ui-range):hover > :where(.ui-range-thumb),
2474
2519
  :where(ui-range)[force-hover] > :where(.ui-range-thumb) {
2475
- border-color: var(--accent-surface-hover);
2476
- box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--accent-surface);
2477
- }
2478
-
2479
- :where(ui-range)[intent]:hover > :where(.ui-range-thumb),
2480
- :where(ui-range)[intent][force-hover] > :where(.ui-range-thumb) {
2481
2520
  border-color: var(--_surface-hover);
2482
2521
  box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--_surface);
2483
2522
  }
@@ -2485,19 +2524,10 @@
2485
2524
  /* ── Active (dragging) ── */
2486
2525
 
2487
2526
  :where(ui-range)[pressed]::after {
2488
- background: var(--accent-surface-active);
2489
- }
2490
-
2491
- :where(ui-range)[intent][pressed]::after {
2492
2527
  background: var(--_surface-active);
2493
2528
  }
2494
2529
 
2495
2530
  :where(ui-range)[pressed] > :where(.ui-range-thumb) {
2496
- border-color: var(--accent-surface-active);
2497
- box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--accent-surface);
2498
- }
2499
-
2500
- :where(ui-range)[intent][pressed] > :where(.ui-range-thumb) {
2501
2531
  border-color: var(--_surface-active);
2502
2532
  box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--_surface);
2503
2533
  }
@@ -3052,22 +3082,13 @@
3052
3082
  When an explicit [intent] is set, the intent selector's --_surface wins. */
3053
3083
 
3054
3084
  :where(ui-switch)[aria-checked="true"]::before {
3055
- background: var(--accent-surface);
3056
- border-color: var(--accent-surface);
3085
+ background: var(--_surface);
3086
+ border-color: var(--_surface);
3057
3087
  }
3058
3088
 
3059
3089
  :where(ui-switch)[aria-checked="true"]::after {
3060
3090
  /* Slide thumb to the right via translateX — GPU composited */
3061
3091
  transform: translateY(-50%) translateX(calc(var(--_track-width) - var(--_thumb-size) - var(--_thumb-offset) * 2));
3062
- background: var(--accent-surface-ink);
3063
- }
3064
-
3065
- :where(ui-switch)[intent][aria-checked="true"]::before {
3066
- background: var(--_surface);
3067
- border-color: var(--_surface);
3068
- }
3069
-
3070
- :where(ui-switch)[intent][aria-checked="true"]::after {
3071
3092
  background: var(--_surface-ink);
3072
3093
  }
3073
3094
 
@@ -3086,12 +3107,6 @@
3086
3107
 
3087
3108
  :where(ui-switch)[aria-checked="true"]:hover::before,
3088
3109
  :where(ui-switch)[aria-checked="true"][force-hover]::before {
3089
- background: var(--accent-surface-hover);
3090
- border-color: var(--accent-surface-hover);
3091
- }
3092
-
3093
- :where(ui-switch)[intent][aria-checked="true"]:hover::before,
3094
- :where(ui-switch)[intent][aria-checked="true"][force-hover]::before {
3095
3110
  background: var(--_surface-hover);
3096
3111
  border-color: var(--_surface-hover);
3097
3112
  }
@@ -3104,11 +3119,6 @@
3104
3119
  }
3105
3120
 
3106
3121
  :where(ui-switch)[aria-checked="true"][pressed]::before {
3107
- background: var(--accent-surface-active);
3108
- border-color: var(--accent-surface-active);
3109
- }
3110
-
3111
- :where(ui-switch)[intent][aria-checked="true"][pressed]::before {
3112
3122
  background: var(--_surface-active);
3113
3123
  border-color: var(--_surface-active);
3114
3124
  }
@@ -4568,7 +4578,7 @@
4568
4578
 
4569
4579
  :where(ui-layout-chat) :where(.layout-resize-handle):hover,
4570
4580
  :where(ui-layout-chat[resizing]) :where(.layout-resize-handle) {
4571
- background: var(--neutral-border-muted);
4581
+ background: var(--_border-muted);
4572
4582
  }
4573
4583
 
4574
4584
  }
@@ -4630,7 +4640,7 @@
4630
4640
 
4631
4641
  :where(ui-layout-inspector) :where(.layout-resize-handle):hover,
4632
4642
  :where(ui-layout-inspector[resizing]) :where(.layout-resize-handle) {
4633
- background: var(--neutral-border-muted);
4643
+ background: var(--_border-muted);
4634
4644
  }
4635
4645
 
4636
4646
  }
@@ -4657,16 +4667,16 @@
4657
4667
  content: '';
4658
4668
  width: var(--ui-layout-sidebar-width);
4659
4669
  flex-shrink: 0;
4660
- background: var(--neutral-body);
4661
- border-right: 1px solid var(--neutral-border-muted);
4670
+ background: var(--_body);
4671
+ border-right: 1px solid var(--_border-muted);
4662
4672
  }
4663
4673
 
4664
4674
  :where(ui-layout-sidebar):not([data-ready])[collapsed]::before {
4665
4675
  content: '';
4666
4676
  width: 48px;
4667
4677
  flex-shrink: 0;
4668
- background: var(--neutral-body);
4669
- border-right: 1px solid var(--neutral-border-muted);
4678
+ background: var(--_body);
4679
+ border-right: 1px solid var(--_border-muted);
4670
4680
  }
4671
4681
 
4672
4682
  /* ── Content Column ── */
@@ -4682,16 +4692,23 @@
4682
4692
  }
4683
4693
 
4684
4694
  /* ── Sidebar Aside ── */
4695
+ /* WHY: position: relative creates the containing block for absolute
4696
+ header/footer overlays. Content fills the full height and scrolls
4697
+ underneath the pinned header/footer. */
4685
4698
 
4686
4699
  :where(ui-layout-sidebar) > :where([slot="sidebar"]) {
4700
+ --_sidebar-header-height: 0px;
4701
+ --_sidebar-footer-height: 0px;
4702
+
4703
+ container-name: sidebar;
4704
+ container-type: inline-size;
4705
+ position: sticky;
4706
+ top: 0;
4687
4707
  width: var(--ui-layout-sidebar-width);
4688
4708
  min-width: 160px;
4689
4709
  max-width: 400px;
4690
4710
  height: 100dvh;
4691
- position: sticky;
4692
- top: 0;
4693
- display: flex;
4694
- flex-direction: column;
4711
+ display: block;
4695
4712
  transition: width var(--_duration) var(--_easing), min-width var(--_duration) var(--_easing);
4696
4713
  overflow: hidden;
4697
4714
  z-index: 10;
@@ -4703,7 +4720,9 @@
4703
4720
  }
4704
4721
 
4705
4722
  /* ── Collapsed Icon Rail ── */
4706
- /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding. */
4723
+ /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding.
4724
+ [collapsed] drives the aside to 48px which triggers @container sidebar queries
4725
+ in this file and in child component CSS (ui-nav.css, etc.). */
4707
4726
 
4708
4727
  :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) {
4709
4728
  width: 48px;
@@ -4724,11 +4743,7 @@
4724
4743
 
4725
4744
  :where(ui-layout-sidebar) :where(.layout-resize-handle):hover,
4726
4745
  :where(ui-layout-sidebar) > :where([slot="sidebar"][resizing]) :where(.layout-resize-handle) {
4727
- background: var(--neutral-border-muted);
4728
- }
4729
-
4730
- :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) :where(.layout-resize-handle) {
4731
- display: none;
4746
+ background: var(--_border-muted);
4732
4747
  }
4733
4748
 
4734
4749
  /* WHY: When collapsed to icon rail, the sidebar edge meets content — drop left padding. */
@@ -4741,101 +4756,89 @@
4741
4756
  padding-left: 0;
4742
4757
  }
4743
4758
 
4744
- /* ── Collapsed Icon-Rail Content ── */
4745
- /* WHY: When collapsed, sidebar slot becomes a narrow icon rail.
4746
- Text and trailing slots are hidden; only icons remain visible.
4747
- Nav items hide entirely — only group headers (with icons) stay. */
4748
-
4749
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-content) {
4750
- align-items: center;
4751
- padding: 0.5rem;
4752
- }
4753
-
4754
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-header),
4755
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-footer) {
4756
- justify-content: center;
4757
- padding-inline: 0.5rem;
4758
- }
4759
-
4760
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="label"]),
4761
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="trailing"]) {
4762
- display: none;
4763
- }
4764
-
4765
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-item) {
4766
- display: none;
4767
- }
4768
-
4769
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary) {
4770
- justify-content: center;
4771
- }
4772
-
4773
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary)::after {
4774
- display: none;
4775
- }
4776
-
4777
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::after,
4778
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::before {
4779
- display: none;
4780
- }
4781
-
4782
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) {
4783
- justify-content: center;
4784
- font-size: 0;
4785
- gap: 0;
4786
- }
4787
-
4788
- /* WHY: Icon keeps its size even though parent font-size is 0. */
4789
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) :where(ui-icon) {
4790
- font-size: var(--_font-size, 1rem);
4791
- }
4792
-
4793
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) + :where(ui-nav-group) {
4794
- margin-block-start: 0;
4795
- }
4796
-
4797
4759
  /* ── Sidebar Header ── */
4760
+ /* WHY: Absolute-positioned overlay pinned to top of aside. Content scrolls
4761
+ underneath it. z-index: 2 sits above content (z-index: 0). */
4798
4762
 
4799
4763
  :where(ui-layout-sidebar-header) {
4764
+ position: absolute;
4765
+ top: 0;
4766
+ left: 0;
4767
+ right: 0;
4768
+ z-index: 2;
4800
4769
  display: flex;
4801
- align-items: center;
4802
- min-height: var(--ui-layout-bar-height);
4803
- gap: calc(var(--_space) * 2);
4804
- padding-block: var(--_space);
4805
- padding-inline: calc(var(--_space-k) * var(--_space));
4806
- flex-shrink: 0;
4770
+ flex-direction: column;
4807
4771
  }
4808
4772
 
4809
4773
  /* ── Sidebar Content ── */
4774
+ /* WHY: Full height of the aside, scrollable. Padding-block offsets keep
4775
+ content from starting behind header / ending behind footer.
4776
+ Fade-out alpha mask dissolves content edges under the overlays. */
4810
4777
 
4811
4778
  :where(ui-layout-sidebar-content) {
4812
4779
  display: flex;
4813
4780
  flex-direction: column;
4814
- gap: calc(var(--_space) * 2);
4815
- padding-block: var(--_space);
4816
- padding-inline: calc(var(--_space-k) * var(--_space));
4817
- flex: 1;
4818
- min-height: 0;
4781
+ padding-block-start: var(--_sidebar-header-height);
4782
+ padding-block-end: var(--_sidebar-footer-height);
4819
4783
  width: 100%;
4784
+ height: 100%;
4785
+ overflow-y: auto;
4786
+ scrollbar-width: none;
4787
+ }
4788
+
4789
+ /* WHY: When header is present, fade top edge. Content dissolves as it
4790
+ scrolls under the header — transparent at top, fully visible by 1.25×
4791
+ the header height. No opaque header background needed. */
4792
+ :where(ui-layout-sidebar-content)[data-has-header] {
4793
+ mask-image: linear-gradient(
4794
+ to bottom,
4795
+ transparent 0,
4796
+ black calc(var(--_sidebar-header-height) * 1.25),
4797
+ black 100%
4798
+ );
4799
+ }
4800
+
4801
+ /* WHY: When footer is present, fade bottom edge. */
4802
+ :where(ui-layout-sidebar-content)[data-has-footer] {
4803
+ mask-image: linear-gradient(
4804
+ to bottom,
4805
+ black 0,
4806
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
4807
+ transparent 100%
4808
+ );
4809
+ }
4810
+
4811
+ /* WHY: When both header AND footer are present, fade both edges. */
4812
+ :where(ui-layout-sidebar-content)[data-has-header][data-has-footer] {
4813
+ mask-image: linear-gradient(
4814
+ to bottom,
4815
+ transparent 0,
4816
+ black calc(var(--_sidebar-header-height) * 1.25),
4817
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
4818
+ transparent 100%
4819
+ );
4820
4820
  }
4821
4821
 
4822
4822
  /* ── Sidebar Footer ── */
4823
+ /* WHY: Absolute-positioned overlay pinned to bottom of aside. */
4823
4824
 
4824
4825
  :where(ui-layout-sidebar-footer) {
4826
+ position: absolute;
4827
+ bottom: 0;
4828
+ left: 0;
4829
+ right: 0;
4830
+ z-index: 2;
4825
4831
  display: flex;
4826
- align-items: center;
4827
- min-height: var(--ui-layout-bar-height);
4828
- gap: calc(var(--_space) * 2);
4829
- padding-block: var(--_space);
4830
- padding-inline: calc(var(--_space-k) * var(--_space));
4831
- flex-shrink: 0;
4832
+ flex-direction: column;
4832
4833
  }
4833
4834
 
4834
- /* ── Sidebar Trigger ── */
4835
- /* WHY: Menu trigger for sidebar header/footer. Styled like ui-nav-group-header:
4836
- bold text, icon support, hover color. Opens a popover on click. */
4835
+ /* ── Sidebar Item ── */
4836
+ /* WHY: Universal sidebar row element. Provides inline padding for any content
4837
+ (buttons, links, icons). When a child ui-listbox[popover] is present, JS
4838
+ wires PopoverController for click-to-toggle menu behavior.
4839
+ Absorbs the old ui-layout-sidebar-trigger role — one element for all rows. */
4837
4840
 
4838
- :where(ui-layout-sidebar-trigger) {
4841
+ :where(ui-layout-sidebar-item) {
4839
4842
  display: flex;
4840
4843
  align-items: center;
4841
4844
  flex: 1;
@@ -4851,31 +4854,47 @@
4851
4854
  user-select: none;
4852
4855
  border: none;
4853
4856
  background: none;
4854
- padding: var(--_space);
4857
+ padding-block: var(--_space);
4858
+ padding-inline: calc(var(--_space-k) * var(--_space));
4855
4859
  border-radius: var(--_radius);
4856
4860
  transition: color var(--_duration) var(--_easing);
4857
4861
  }
4858
4862
 
4859
- :where(ui-layout-sidebar-trigger):hover,
4860
- :where(ui-layout-sidebar-trigger)[force-hover] {
4863
+ /* WHY: Items in header/footer match the breadcrumb bar height so the
4864
+ first sidebar row aligns horizontally with the breadcrumb. */
4865
+ :where(ui-layout-sidebar-header) > :where(ui-layout-sidebar-item),
4866
+ :where(ui-layout-sidebar-footer) > :where(ui-layout-sidebar-item) {
4867
+ min-height: var(--ui-layout-bar-height);
4868
+ }
4869
+
4870
+ :where(ui-layout-sidebar-item):hover,
4871
+ :where(ui-layout-sidebar-item)[force-hover] {
4861
4872
  color: var(--_ink-strong);
4862
4873
  }
4863
4874
 
4864
- :where(ui-layout-sidebar-trigger):focus-visible,
4865
- :where(ui-layout-sidebar-trigger)[force-focus-visible] {
4875
+ :where(ui-layout-sidebar-item):focus-visible,
4876
+ :where(ui-layout-sidebar-item)[force-focus-visible] {
4866
4877
  outline: 2px solid var(--ui-focus-ring);
4867
4878
  outline-offset: -2px;
4868
4879
  }
4869
4880
 
4870
4881
  /* WHY: Trailing caret pushes to end — same pattern as button justify="spread". */
4871
- :where(ui-layout-sidebar-trigger) > :where([slot="trailing"]) {
4882
+ :where(ui-layout-sidebar-item) > :where([slot="trailing"]) {
4872
4883
  margin-inline-start: auto;
4873
4884
  flex-shrink: 0;
4874
4885
  color: var(--_ink-muted);
4875
4886
  }
4876
4887
 
4888
+ /* WHY: When the item wraps a sub-component (e.g. ui-button) that provides
4889
+ its own icon, hide [slot="icon"] in expanded mode — it only serves as the
4890
+ bare collapsed-rail representation. Items without sub-components (just
4891
+ icon + label + trailing) keep [slot="icon"] visible in both modes. */
4892
+ :where(ui-layout-sidebar-item:has(> ui-button)) > :where([slot="icon"]) {
4893
+ display: none;
4894
+ }
4895
+
4877
4896
  /* WHY: Title text truncates when sidebar narrows during resize. */
4878
- :where(ui-layout-sidebar-trigger) > :where([slot="label"]) {
4897
+ :where(ui-layout-sidebar-item) > :where([slot="label"]) {
4879
4898
  flex: 1;
4880
4899
  min-width: 0;
4881
4900
  white-space: nowrap;
@@ -4883,25 +4902,67 @@
4883
4902
  text-overflow: ellipsis;
4884
4903
  }
4885
4904
 
4886
- /* ── Trigger Popover ── */
4887
- /* WHY: Popover opens to the right of the sidebar trigger.
4905
+ /* ── Item Popover ── */
4906
+ /* WHY: Popover opens to the right of the sidebar item.
4888
4907
  Default: top-aligned, grows downward (span-block-end).
4889
4908
  Flip: bottom-aligned, grows upward (span-block-start). */
4890
4909
 
4891
- :where(ui-layout-sidebar-trigger) > :where(ui-listbox[popover]) {
4910
+ :where(ui-layout-sidebar-item) > :where(ui-listbox[popover]) {
4892
4911
  position: fixed;
4893
4912
  position-area: inline-end span-block-end;
4894
- position-try-fallbacks: --flip-up;
4913
+ position-try-fallbacks: --sidebar-item-flip-up;
4895
4914
  margin: 0 0 0 var(--ui-popover-gap);
4896
4915
  min-width: 200px;
4897
4916
  max-height: var(--ui-popover-max-height);
4898
4917
  overflow-y: auto;
4899
4918
  }
4900
4919
 
4901
- @position-try --flip-up {
4920
+ @position-try --sidebar-item-flip-up {
4902
4921
  position-area: inline-end span-block-start;
4903
4922
  }
4904
4923
 
4924
+ /* ── Container Query: Collapsed Sidebar ── */
4925
+ /* WHY: Each component owns its own collapsed behavior via @container.
4926
+ The aside is the container (container-name: sidebar). When it shrinks
4927
+ to 48px (icon rail), components respond to the width, not to [collapsed].
4928
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
4929
+
4930
+ @container sidebar (max-width: 80px) {
4931
+
4932
+ /* Resize handle — no dragging in icon rail */
4933
+ :where(.layout-resize-handle) {
4934
+ display: none;
4935
+ }
4936
+
4937
+ /* Header/footer — center content horizontally */
4938
+ :where(ui-layout-sidebar-header),
4939
+ :where(ui-layout-sidebar-footer) {
4940
+ align-items: center;
4941
+ }
4942
+
4943
+ /* Content — center items in the 48px rail */
4944
+ :where(ui-layout-sidebar-content) {
4945
+ align-items: center;
4946
+ }
4947
+
4948
+ /* Item — shrink to icon-only. Show [slot="icon"], hide everything else.
4949
+ WHY: Sidebar items can contain complex children (buttons with slots,
4950
+ links, etc.). Rather than overriding sub-component styles, the item
4951
+ provides its own bare icon via [slot="icon"] for the collapsed rail. */
4952
+ :where(ui-layout-sidebar-item) {
4953
+ flex: 0 0 auto;
4954
+ padding-inline: var(--_space);
4955
+ }
4956
+
4957
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
4958
+ display: block;
4959
+ }
4960
+
4961
+ :where(ui-layout-sidebar-item) > :where(:not([slot="icon"]):not(ui-listbox[popover]):not(.nav-group-flyout)) {
4962
+ display: none;
4963
+ }
4964
+ }
4965
+
4905
4966
  }
4906
4967
 
4907
4968
  @layer ui {