@nonoun/native-ui 0.2.4 → 0.2.6

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 (35) 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/ui-nav/ui-nav-group-header-element.d.ts.map +1 -1
  10. package/dist/components-lean.css +319 -226
  11. package/dist/components.css +321 -233
  12. package/dist/containers/ui-layout-sidebar/index.d.ts +1 -1
  13. package/dist/containers/ui-layout-sidebar/index.d.ts.map +1 -1
  14. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-element.d.ts +4 -1
  15. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-element.d.ts.map +1 -1
  16. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item-element.d.ts +10 -0
  17. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item-element.d.ts.map +1 -0
  18. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item.d.ts +3 -0
  19. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item.d.ts.map +1 -0
  20. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar.d.ts +1 -1
  21. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar.d.ts.map +1 -1
  22. package/dist/custom-elements.json +2507 -2099
  23. package/dist/foundation.css +79 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/native-ui-lean.css +398 -227
  27. package/dist/native-ui.css +400 -234
  28. package/dist/native-ui.js +3 -6
  29. package/dist/register-all.js +1 -1
  30. package/dist/ui-icon-element.js +100 -7
  31. package/package.json +1 -1
  32. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger-element.d.ts +0 -8
  33. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger-element.d.ts.map +0 -1
  34. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger.d.ts +0 -3
  35. 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.5em;
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;
@@ -1985,6 +1998,18 @@
1985
1998
  user-select: none;
1986
1999
  }
1987
2000
 
2001
+ /* WHY: Fixed-size icon well — matches ui-layout-sidebar-item [slot="icon"].
2002
+ All sidebar icons align on the same 1.5rem column regardless of icon size.
2003
+ JS wraps the first <ui-icon> child in a .icon-well span during setup(). */
2004
+ :where(ui-nav-group-header) > :where(.icon-well) {
2005
+ display: inline-flex;
2006
+ align-items: center;
2007
+ justify-content: center;
2008
+ width: 1.5rem;
2009
+ height: 1.5rem;
2010
+ flex-shrink: 0;
2011
+ }
2012
+
1988
2013
  /* ── Chevron icon (right side of summary) ── */
1989
2014
 
1990
2015
  :where(ui-nav-group) > :where(details) > :where(summary)::after {
@@ -2036,12 +2061,16 @@
2036
2061
  /* WHY: Connector draws from below the header to the bottom of the group.
2037
2062
  Only shown when header contains an icon. Scoped via :has(). */
2038
2063
 
2039
- :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)) {
2040
- --_group-line-inset: calc(var(--_icon-size) / 2);
2041
- --_group-child-inset: calc(var(--_icon-size) + var(--_space) * 2);
2064
+ /* WHY: Insets account for the summary's padding-inline so the connector
2065
+ line passes through the icon center and child text aligns with header text. */
2066
+ :where(ui-nav-group):has(:where(ui-nav-group-header) :where(.icon-well)) {
2067
+ --_group-pad: calc(var(--_space-k) * var(--_space));
2068
+ --_group-icon-well: 1.5rem;
2069
+ --_group-line-inset: calc(var(--_group-pad) + var(--_group-icon-well) / 2);
2070
+ --_group-child-inset: calc(var(--_group-icon-well) + var(--_space) * 2);
2042
2071
  }
2043
2072
 
2044
- :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)):has(:where(details[open]))::after {
2073
+ :where(ui-nav-group):has(:where(ui-nav-group-header) :where(.icon-well)):has(:where(details[open]))::after {
2045
2074
  content: '';
2046
2075
  position: absolute;
2047
2076
  inset-inline-start: var(--_group-line-inset);
@@ -2056,7 +2085,7 @@
2056
2085
  /* WHY: A ::before pseudo on the group slides along the connector line.
2057
2086
  JS sets --_indicator-index; :state(has-selection) gates visibility. */
2058
2087
 
2059
- :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)):has(:where(details[open])):state(has-selection)::before {
2088
+ :where(ui-nav-group):has(:where(ui-nav-group-header) :where(.icon-well)):has(:where(details[open])):state(has-selection)::before {
2060
2089
  content: '';
2061
2090
  position: absolute;
2062
2091
  z-index: 1;
@@ -2083,6 +2112,73 @@
2083
2112
  margin-inline-start: var(--_group-child-inset, 0);
2084
2113
  }
2085
2114
 
2115
+ /* ── Nav Group Flyout (collapsed sidebar) ── */
2116
+ /* WHY: In collapsed mode, summary click opens a ui-listbox popover to the right
2117
+ instead of expanding the <details>. Same pattern as sidebar-trigger menus. */
2118
+
2119
+ :where(ui-nav-group) > :where(ui-listbox.nav-group-flyout[popover]) {
2120
+ position: fixed;
2121
+ position-area: inline-end span-block-end;
2122
+ position-try-fallbacks: --nav-flyout-flip-up;
2123
+ margin: 0 0 0 var(--ui-popover-gap);
2124
+ min-width: 200px;
2125
+ max-height: var(--ui-popover-max-height);
2126
+ overflow-y: auto;
2127
+ }
2128
+
2129
+ @position-try --nav-flyout-flip-up {
2130
+ position-area: inline-end span-block-start;
2131
+ }
2132
+
2133
+ /* ── Container Query: Collapsed Sidebar ── */
2134
+ /* WHY: Nav components own their own collapsed behavior via @container.
2135
+ The sidebar aside declares container-name: sidebar. When it shrinks
2136
+ to 48px (icon rail), nav responds to the width — not to [collapsed].
2137
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
2138
+
2139
+ @container sidebar (max-width: 80px) {
2140
+
2141
+ /* Nav items hide entirely — only group headers (with icons) remain. */
2142
+ :where(ui-nav-item) {
2143
+ display: none;
2144
+ }
2145
+
2146
+ /* Summary shrinks to icon-only. Reduced inline padding wraps tightly
2147
+ around the icon; parent align-items: center handles horizontal centering. */
2148
+ :where(ui-nav-group) > :where(details) > :where(summary) {
2149
+ padding-inline: var(--_space);
2150
+ border-radius: var(--_radius);
2151
+ }
2152
+
2153
+ /* Hide chevron — no expand/collapse in icon rail (flyout instead). */
2154
+ :where(ui-nav-group) > :where(details) > :where(summary)::after {
2155
+ display: none;
2156
+ }
2157
+
2158
+ /* Hide vertical connector line and sliding indicator. */
2159
+ :where(ui-nav-group)::after,
2160
+ :where(ui-nav-group)::before {
2161
+ display: none;
2162
+ }
2163
+
2164
+ /* Header collapses to icon-only. font-size: 0 hides text nodes
2165
+ (can't be targeted by CSS). Icon overrides back to normal below. */
2166
+ :where(ui-nav-group-header) {
2167
+ flex: 0 0 auto;
2168
+ font-size: 0;
2169
+ gap: 0;
2170
+ }
2171
+
2172
+ :where(ui-nav-group-header) :where(.icon-well) :where(ui-icon) {
2173
+ font-size: var(--_font-size, 1rem);
2174
+ }
2175
+
2176
+ /* Collapse inter-group spacing in icon rail. */
2177
+ :where(ui-nav-group) + :where(ui-nav-group) {
2178
+ margin-block-start: 0;
2179
+ }
2180
+ }
2181
+
2086
2182
  }
2087
2183
 
2088
2184
  @layer ui {
@@ -2223,27 +2319,15 @@
2223
2319
  }
2224
2320
 
2225
2321
  /* ── 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
2322
 
2230
2323
  :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
2324
  background: var(--_surface);
2242
2325
  border-color: var(--_surface);
2243
2326
  }
2244
2327
 
2245
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::after {
2328
+ :where(ui-radio)[aria-checked="true"]::after {
2246
2329
  background: var(--_surface-ink);
2330
+ transform: translateY(-50%) scale(1);
2247
2331
  }
2248
2332
 
2249
2333
  /* ── Hover ── */
@@ -2258,11 +2342,6 @@
2258
2342
  }
2259
2343
 
2260
2344
  :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
2345
  background: var(--_surface-hover);
2267
2346
  border-color: var(--_surface-hover);
2268
2347
  }
@@ -2275,11 +2354,6 @@
2275
2354
  }
2276
2355
 
2277
2356
  :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
2357
  background: var(--_surface-active);
2284
2358
  border-color: var(--_surface-active);
2285
2359
  }
@@ -2377,16 +2451,12 @@
2377
2451
  /* 0% = track-height (circle), 100% = full width */
2378
2452
  width: calc(var(--_track-height) + (100% - var(--_track-height)) * var(--_progress));
2379
2453
  border-radius: calc(var(--_track-height) / 2);
2380
- background: var(--accent-surface);
2454
+ background: var(--_surface);
2381
2455
 
2382
2456
  transition:
2383
2457
  background var(--_duration) var(--_easing);
2384
2458
  }
2385
2459
 
2386
- :where(ui-range)[intent]::after {
2387
- background: var(--_surface);
2388
- }
2389
-
2390
2460
  /* ── Thumb ── */
2391
2461
 
2392
2462
  :where(ui-range) > :where(.ui-range-thumb) {
@@ -2398,8 +2468,8 @@
2398
2468
  width: var(--_thumb-size);
2399
2469
  height: var(--_thumb-size);
2400
2470
  border-radius: 50%;
2401
- background: var(--accent-surface-ink);
2402
- border: 2px solid var(--accent-surface);
2471
+ background: var(--_surface-ink);
2472
+ border: 2px solid var(--_surface);
2403
2473
  box-shadow: var(--ui-shadow-sm);
2404
2474
  pointer-events: none;
2405
2475
 
@@ -2409,11 +2479,6 @@
2409
2479
  border-color var(--_duration) var(--_easing);
2410
2480
  }
2411
2481
 
2412
- :where(ui-range)[intent] > :where(.ui-range-thumb) {
2413
- background: var(--_surface-ink);
2414
- border-color: var(--_surface);
2415
- }
2416
-
2417
2482
  /* ── Focus ── */
2418
2483
 
2419
2484
  :where(ui-range):focus-visible > :where(.ui-range-thumb){
@@ -2424,19 +2489,10 @@
2424
2489
  /* ── Hover ── */
2425
2490
 
2426
2491
  :where(ui-range):hover::after{
2427
- background: var(--accent-surface-hover);
2428
- }
2429
-
2430
- :where(ui-range)[intent]:hover::after{
2431
2492
  background: var(--_surface-hover);
2432
2493
  }
2433
2494
 
2434
2495
  :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
2496
  border-color: var(--_surface-hover);
2441
2497
  box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--_surface);
2442
2498
  }
@@ -2444,19 +2500,10 @@
2444
2500
  /* ── Active (dragging) ── */
2445
2501
 
2446
2502
  :where(ui-range)[pressed]::after {
2447
- background: var(--accent-surface-active);
2448
- }
2449
-
2450
- :where(ui-range)[intent][pressed]::after {
2451
2503
  background: var(--_surface-active);
2452
2504
  }
2453
2505
 
2454
2506
  :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
2507
  border-color: var(--_surface-active);
2461
2508
  box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--_surface);
2462
2509
  }
@@ -3003,22 +3050,13 @@
3003
3050
  When an explicit [intent] is set, the intent selector's --_surface wins. */
3004
3051
 
3005
3052
  :where(ui-switch)[aria-checked="true"]::before {
3006
- background: var(--accent-surface);
3007
- border-color: var(--accent-surface);
3053
+ background: var(--_surface);
3054
+ border-color: var(--_surface);
3008
3055
  }
3009
3056
 
3010
3057
  :where(ui-switch)[aria-checked="true"]::after {
3011
3058
  /* Slide thumb to the right via translateX — GPU composited */
3012
3059
  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
3060
  background: var(--_surface-ink);
3023
3061
  }
3024
3062
 
@@ -3034,11 +3072,6 @@
3034
3072
  }
3035
3073
 
3036
3074
  :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
3075
  background: var(--_surface-hover);
3043
3076
  border-color: var(--_surface-hover);
3044
3077
  }
@@ -3051,11 +3084,6 @@
3051
3084
  }
3052
3085
 
3053
3086
  :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
3087
  background: var(--_surface-active);
3060
3088
  border-color: var(--_surface-active);
3061
3089
  }
@@ -4494,7 +4522,7 @@
4494
4522
 
4495
4523
  :where(ui-layout-chat) :where(.layout-resize-handle):hover,
4496
4524
  :where(ui-layout-chat[resizing]) :where(.layout-resize-handle) {
4497
- background: var(--neutral-border-muted);
4525
+ background: var(--_border-muted);
4498
4526
  }
4499
4527
 
4500
4528
  }
@@ -4556,7 +4584,7 @@
4556
4584
 
4557
4585
  :where(ui-layout-inspector) :where(.layout-resize-handle):hover,
4558
4586
  :where(ui-layout-inspector[resizing]) :where(.layout-resize-handle) {
4559
- background: var(--neutral-border-muted);
4587
+ background: var(--_border-muted);
4560
4588
  }
4561
4589
 
4562
4590
  }
@@ -4583,16 +4611,16 @@
4583
4611
  content: '';
4584
4612
  width: var(--ui-layout-sidebar-width);
4585
4613
  flex-shrink: 0;
4586
- background: var(--neutral-body);
4587
- border-right: 1px solid var(--neutral-border-muted);
4614
+ background: var(--_body);
4615
+ border-right: 1px solid var(--_border-muted);
4588
4616
  }
4589
4617
 
4590
4618
  :where(ui-layout-sidebar):not([data-ready])[collapsed]::before {
4591
4619
  content: '';
4592
4620
  width: 48px;
4593
4621
  flex-shrink: 0;
4594
- background: var(--neutral-body);
4595
- border-right: 1px solid var(--neutral-border-muted);
4622
+ background: var(--_body);
4623
+ border-right: 1px solid var(--_border-muted);
4596
4624
  }
4597
4625
 
4598
4626
  /* ── Content Column ── */
@@ -4608,16 +4636,23 @@
4608
4636
  }
4609
4637
 
4610
4638
  /* ── Sidebar Aside ── */
4639
+ /* WHY: position: relative creates the containing block for absolute
4640
+ header/footer overlays. Content fills the full height and scrolls
4641
+ underneath the pinned header/footer. */
4611
4642
 
4612
4643
  :where(ui-layout-sidebar) > :where([slot="sidebar"]) {
4644
+ --_sidebar-header-height: 0px;
4645
+ --_sidebar-footer-height: 0px;
4646
+
4647
+ container-name: sidebar;
4648
+ container-type: inline-size;
4649
+ position: sticky;
4650
+ top: 0;
4613
4651
  width: var(--ui-layout-sidebar-width);
4614
4652
  min-width: 160px;
4615
4653
  max-width: 400px;
4616
4654
  height: 100dvh;
4617
- position: sticky;
4618
- top: 0;
4619
- display: flex;
4620
- flex-direction: column;
4655
+ display: block;
4621
4656
  transition: width var(--_duration) var(--_easing), min-width var(--_duration) var(--_easing);
4622
4657
  overflow: hidden;
4623
4658
  z-index: 10;
@@ -4629,7 +4664,9 @@
4629
4664
  }
4630
4665
 
4631
4666
  /* ── Collapsed Icon Rail ── */
4632
- /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding. */
4667
+ /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding.
4668
+ [collapsed] drives the aside to 48px which triggers @container sidebar queries
4669
+ in this file and in child component CSS (ui-nav.css, etc.). */
4633
4670
 
4634
4671
  :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) {
4635
4672
  width: 48px;
@@ -4650,11 +4687,7 @@
4650
4687
 
4651
4688
  :where(ui-layout-sidebar) :where(.layout-resize-handle):hover,
4652
4689
  :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;
4690
+ background: var(--_border-muted);
4658
4691
  }
4659
4692
 
4660
4693
  /* WHY: When collapsed to icon rail, the sidebar edge meets content — drop left padding. */
@@ -4667,101 +4700,89 @@
4667
4700
  padding-left: 0;
4668
4701
  }
4669
4702
 
4670
- /* ── Collapsed Icon-Rail Content ── */
4671
- /* WHY: When collapsed, sidebar slot becomes a narrow icon rail.
4672
- Text and trailing slots are hidden; only icons remain visible.
4673
- Nav items hide entirely — only group headers (with icons) stay. */
4674
-
4675
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-content) {
4676
- align-items: center;
4677
- padding: 0.5rem;
4678
- }
4679
-
4680
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-header),
4681
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-footer) {
4682
- justify-content: center;
4683
- padding-inline: 0.5rem;
4684
- }
4685
-
4686
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="label"]),
4687
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="trailing"]) {
4688
- display: none;
4689
- }
4690
-
4691
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-item) {
4692
- display: none;
4693
- }
4694
-
4695
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary) {
4696
- justify-content: center;
4697
- }
4698
-
4699
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary)::after {
4700
- display: none;
4701
- }
4702
-
4703
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::after,
4704
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::before {
4705
- display: none;
4706
- }
4707
-
4708
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) {
4709
- justify-content: center;
4710
- font-size: 0;
4711
- gap: 0;
4712
- }
4713
-
4714
- /* WHY: Icon keeps its size even though parent font-size is 0. */
4715
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) :where(ui-icon) {
4716
- font-size: var(--_font-size, 1rem);
4717
- }
4718
-
4719
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) + :where(ui-nav-group) {
4720
- margin-block-start: 0;
4721
- }
4722
-
4723
4703
  /* ── Sidebar Header ── */
4704
+ /* WHY: Absolute-positioned overlay pinned to top of aside. Content scrolls
4705
+ underneath it. z-index: 2 sits above content (z-index: 0). */
4724
4706
 
4725
4707
  :where(ui-layout-sidebar-header) {
4708
+ position: absolute;
4709
+ top: 0;
4710
+ left: 0;
4711
+ right: 0;
4712
+ z-index: 2;
4726
4713
  display: flex;
4727
- align-items: center;
4728
- min-height: var(--ui-layout-bar-height);
4729
- gap: calc(var(--_space) * 2);
4730
- padding-block: var(--_space);
4731
- padding-inline: calc(var(--_space-k) * var(--_space));
4732
- flex-shrink: 0;
4714
+ flex-direction: column;
4733
4715
  }
4734
4716
 
4735
4717
  /* ── Sidebar Content ── */
4718
+ /* WHY: Full height of the aside, scrollable. Padding-block offsets keep
4719
+ content from starting behind header / ending behind footer.
4720
+ Fade-out alpha mask dissolves content edges under the overlays. */
4736
4721
 
4737
4722
  :where(ui-layout-sidebar-content) {
4738
4723
  display: flex;
4739
4724
  flex-direction: column;
4740
- gap: calc(var(--_space) * 2);
4741
- padding-block: var(--_space);
4742
- padding-inline: calc(var(--_space-k) * var(--_space));
4743
- flex: 1;
4744
- min-height: 0;
4725
+ padding-block-start: var(--_sidebar-header-height);
4726
+ padding-block-end: var(--_sidebar-footer-height);
4745
4727
  width: 100%;
4728
+ height: 100%;
4729
+ overflow-y: auto;
4730
+ scrollbar-width: none;
4731
+ }
4732
+
4733
+ /* WHY: When header is present, fade top edge. Content dissolves as it
4734
+ scrolls under the header — transparent at top, fully visible by 1.25×
4735
+ the header height. No opaque header background needed. */
4736
+ :where(ui-layout-sidebar-content)[data-has-header] {
4737
+ mask-image: linear-gradient(
4738
+ to bottom,
4739
+ transparent 0,
4740
+ black calc(var(--_sidebar-header-height) * 1.25),
4741
+ black 100%
4742
+ );
4743
+ }
4744
+
4745
+ /* WHY: When footer is present, fade bottom edge. */
4746
+ :where(ui-layout-sidebar-content)[data-has-footer] {
4747
+ mask-image: linear-gradient(
4748
+ to bottom,
4749
+ black 0,
4750
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
4751
+ transparent 100%
4752
+ );
4753
+ }
4754
+
4755
+ /* WHY: When both header AND footer are present, fade both edges. */
4756
+ :where(ui-layout-sidebar-content)[data-has-header][data-has-footer] {
4757
+ mask-image: linear-gradient(
4758
+ to bottom,
4759
+ transparent 0,
4760
+ black calc(var(--_sidebar-header-height) * 1.25),
4761
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
4762
+ transparent 100%
4763
+ );
4746
4764
  }
4747
4765
 
4748
4766
  /* ── Sidebar Footer ── */
4767
+ /* WHY: Absolute-positioned overlay pinned to bottom of aside. */
4749
4768
 
4750
4769
  :where(ui-layout-sidebar-footer) {
4770
+ position: absolute;
4771
+ bottom: 0;
4772
+ left: 0;
4773
+ right: 0;
4774
+ z-index: 2;
4751
4775
  display: flex;
4752
- align-items: center;
4753
- min-height: var(--ui-layout-bar-height);
4754
- gap: calc(var(--_space) * 2);
4755
- padding-block: var(--_space);
4756
- padding-inline: calc(var(--_space-k) * var(--_space));
4757
- flex-shrink: 0;
4776
+ flex-direction: column;
4758
4777
  }
4759
4778
 
4760
- /* ── Sidebar Trigger ── */
4761
- /* WHY: Menu trigger for sidebar header/footer. Styled like ui-nav-group-header:
4762
- bold text, icon support, hover color. Opens a popover on click. */
4779
+ /* ── Sidebar Item ── */
4780
+ /* WHY: Universal sidebar row element. Provides inline padding for any content
4781
+ (buttons, links, icons). When a child ui-listbox[popover] is present, JS
4782
+ wires PopoverController for click-to-toggle menu behavior.
4783
+ Absorbs the old ui-layout-sidebar-trigger role — one element for all rows. */
4763
4784
 
4764
- :where(ui-layout-sidebar-trigger) {
4785
+ :where(ui-layout-sidebar-item) {
4765
4786
  display: flex;
4766
4787
  align-items: center;
4767
4788
  flex: 1;
@@ -4777,29 +4798,59 @@
4777
4798
  user-select: none;
4778
4799
  border: none;
4779
4800
  background: none;
4780
- padding: var(--_space);
4801
+ padding-block: var(--_space);
4802
+ padding-inline: calc(var(--_space-k) * var(--_space));
4781
4803
  border-radius: var(--_radius);
4782
4804
  transition: color var(--_duration) var(--_easing);
4783
4805
  }
4784
4806
 
4785
- :where(ui-layout-sidebar-trigger):hover{
4807
+ /* WHY: Items in header/footer match the breadcrumb bar height so the
4808
+ first sidebar row aligns horizontally with the breadcrumb. */
4809
+ :where(ui-layout-sidebar-header) > :where(ui-layout-sidebar-item),
4810
+ :where(ui-layout-sidebar-footer) > :where(ui-layout-sidebar-item) {
4811
+ min-height: var(--ui-layout-bar-height);
4812
+ }
4813
+
4814
+ :where(ui-layout-sidebar-item):hover{
4786
4815
  color: var(--_ink-strong);
4787
4816
  }
4788
4817
 
4789
- :where(ui-layout-sidebar-trigger):focus-visible{
4818
+ :where(ui-layout-sidebar-item):focus-visible{
4790
4819
  outline: 2px solid var(--ui-focus-ring);
4791
4820
  outline-offset: -2px;
4792
4821
  }
4793
4822
 
4823
+ /* WHY: Fixed-size icon well so all sidebar icons (header logo, nav group icons,
4824
+ standalone icons) center-align on the same column width regardless of intrinsic
4825
+ icon size. 1.5rem matches the sidebar's visual rhythm at default density.
4826
+ Consumer wraps the icon: <span slot="icon"><ui-icon name="..."></ui-icon></span>
4827
+ The wrapper is the well; the icon keeps its own --ui-icon-size dimensions. */
4828
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
4829
+ display: inline-flex;
4830
+ align-items: center;
4831
+ justify-content: center;
4832
+ width: 1.5rem;
4833
+ height: 1.5rem;
4834
+ flex-shrink: 0;
4835
+ }
4836
+
4794
4837
  /* WHY: Trailing caret pushes to end — same pattern as button justify="spread". */
4795
- :where(ui-layout-sidebar-trigger) > :where([slot="trailing"]) {
4838
+ :where(ui-layout-sidebar-item) > :where([slot="trailing"]) {
4796
4839
  margin-inline-start: auto;
4797
4840
  flex-shrink: 0;
4798
4841
  color: var(--_ink-muted);
4799
4842
  }
4800
4843
 
4844
+ /* WHY: When the item wraps a sub-component (e.g. ui-button) that provides
4845
+ its own icon, hide [slot="icon"] in expanded mode — it only serves as the
4846
+ bare collapsed-rail representation. Items without sub-components (just
4847
+ icon + label + trailing) keep [slot="icon"] visible in both modes. */
4848
+ :where(ui-layout-sidebar-item:has(> ui-button)) > :where([slot="icon"]) {
4849
+ display: none;
4850
+ }
4851
+
4801
4852
  /* WHY: Title text truncates when sidebar narrows during resize. */
4802
- :where(ui-layout-sidebar-trigger) > :where([slot="label"]) {
4853
+ :where(ui-layout-sidebar-item) > :where([slot="label"]) {
4803
4854
  flex: 1;
4804
4855
  min-width: 0;
4805
4856
  white-space: nowrap;
@@ -4807,25 +4858,67 @@
4807
4858
  text-overflow: ellipsis;
4808
4859
  }
4809
4860
 
4810
- /* ── Trigger Popover ── */
4811
- /* WHY: Popover opens to the right of the sidebar trigger.
4861
+ /* ── Item Popover ── */
4862
+ /* WHY: Popover opens to the right of the sidebar item.
4812
4863
  Default: top-aligned, grows downward (span-block-end).
4813
4864
  Flip: bottom-aligned, grows upward (span-block-start). */
4814
4865
 
4815
- :where(ui-layout-sidebar-trigger) > :where(ui-listbox[popover]) {
4866
+ :where(ui-layout-sidebar-item) > :where(ui-listbox[popover]) {
4816
4867
  position: fixed;
4817
4868
  position-area: inline-end span-block-end;
4818
- position-try-fallbacks: --flip-up;
4869
+ position-try-fallbacks: --sidebar-item-flip-up;
4819
4870
  margin: 0 0 0 var(--ui-popover-gap);
4820
4871
  min-width: 200px;
4821
4872
  max-height: var(--ui-popover-max-height);
4822
4873
  overflow-y: auto;
4823
4874
  }
4824
4875
 
4825
- @position-try --flip-up {
4876
+ @position-try --sidebar-item-flip-up {
4826
4877
  position-area: inline-end span-block-start;
4827
4878
  }
4828
4879
 
4880
+ /* ── Container Query: Collapsed Sidebar ── */
4881
+ /* WHY: Each component owns its own collapsed behavior via @container.
4882
+ The aside is the container (container-name: sidebar). When it shrinks
4883
+ to 48px (icon rail), components respond to the width, not to [collapsed].
4884
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
4885
+
4886
+ @container sidebar (max-width: 80px) {
4887
+
4888
+ /* Resize handle — no dragging in icon rail */
4889
+ :where(.layout-resize-handle) {
4890
+ display: none;
4891
+ }
4892
+
4893
+ /* Header/footer — center content horizontally */
4894
+ :where(ui-layout-sidebar-header),
4895
+ :where(ui-layout-sidebar-footer) {
4896
+ align-items: center;
4897
+ }
4898
+
4899
+ /* Content — center items in the 48px rail */
4900
+ :where(ui-layout-sidebar-content) {
4901
+ align-items: center;
4902
+ }
4903
+
4904
+ /* Item — shrink to icon-only. Show [slot="icon"], hide everything else.
4905
+ WHY: Sidebar items can contain complex children (buttons with slots,
4906
+ links, etc.). Rather than overriding sub-component styles, the item
4907
+ provides its own bare icon via [slot="icon"] for the collapsed rail. */
4908
+ :where(ui-layout-sidebar-item) {
4909
+ flex: 0 0 auto;
4910
+ padding-inline: var(--_space);
4911
+ }
4912
+
4913
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
4914
+ display: inline-flex;
4915
+ }
4916
+
4917
+ :where(ui-layout-sidebar-item) > :where(:not([slot="icon"]):not(ui-listbox[popover]):not(.nav-group-flyout)) {
4918
+ display: none;
4919
+ }
4920
+ }
4921
+
4829
4922
  }
4830
4923
 
4831
4924
  @layer ui {