@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
@@ -1267,6 +1267,54 @@
1267
1267
  display: none;
1268
1268
  }
1269
1269
 
1270
+ /* WHY: Custom elements render as unstyled inline text until define() runs.
1271
+ visibility:hidden preserves layout — no reflow when the element upgrades.
1272
+ Only targets elements that call define(); CSS-only containers are excluded
1273
+ (they never become :defined so would be permanently hidden). */
1274
+ :where(
1275
+ ui-accordion, ui-accordion-item,
1276
+ ui-avatar,
1277
+ ui-badge,
1278
+ ui-breadcrumb, ui-breadcrumb-item,
1279
+ ui-button,
1280
+ ui-calendar,
1281
+ ui-card,
1282
+ ui-chat-input,
1283
+ ui-checkbox,
1284
+ ui-combobox,
1285
+ ui-command, ui-command-empty, ui-command-group, ui-command-input, ui-command-item, ui-command-list,
1286
+ ui-controller,
1287
+ ui-dialog,
1288
+ ui-drawer,
1289
+ ui-field,
1290
+ ui-icon,
1291
+ ui-input,
1292
+ ui-input-otp,
1293
+ ui-kbd,
1294
+ ui-layout-chat,
1295
+ ui-layout-inspector,
1296
+ ui-layout-sidebar, ui-layout-sidebar-item,
1297
+ ui-listbox,
1298
+ ui-nav, ui-nav-group, ui-nav-group-header, ui-nav-item,
1299
+ ui-option, ui-option-group, ui-option-group-header,
1300
+ ui-pagination,
1301
+ ui-radio, ui-radio-group,
1302
+ ui-range,
1303
+ ui-section,
1304
+ ui-segment, ui-segmented-control,
1305
+ ui-select,
1306
+ ui-slide, ui-slideshow,
1307
+ ui-switch,
1308
+ ui-tab, ui-tab-panel, ui-tab-panels, ui-tabs,
1309
+ ui-table, ui-table-body, ui-table-cell, ui-table-head, ui-table-header, ui-table-row,
1310
+ ui-textarea,
1311
+ ui-toolbar,
1312
+ ui-tooltip,
1313
+ ui-tree, ui-tree-item
1314
+ ):not(:defined) {
1315
+ visibility: hidden;
1316
+ }
1317
+
1270
1318
  /* ── Document Defaults ── */
1271
1319
 
1272
1320
  :where(:root) {
@@ -1460,7 +1508,7 @@
1460
1508
  --ui-tooltip-max-width: 20rem;
1461
1509
 
1462
1510
  /* Popover (select / combobox / command) */
1463
- --ui-popover-max-height: 18rem;
1511
+ --ui-popover-max-height: calc(100dvh - 2rem);
1464
1512
  --ui-popover-gap: 0.25rem;
1465
1513
 
1466
1514
  /* Drawer */
@@ -1493,6 +1541,18 @@
1493
1541
  --ui-badge-size-xl: 1.375rem;
1494
1542
  --ui-badge-dot: 0.5rem;
1495
1543
 
1544
+ /* Kbd */
1545
+ --ui-kbd-font-xs: 0.625rem;
1546
+ --ui-kbd-font-sm: 0.6875rem;
1547
+ --ui-kbd-font-md: 0.6875rem;
1548
+ --ui-kbd-font-lg: 0.75rem;
1549
+ --ui-kbd-font-xl: 0.8125rem;
1550
+ --ui-kbd-size-xs: 1rem;
1551
+ --ui-kbd-size-sm: 1.125rem;
1552
+ --ui-kbd-size-md: 1.25rem;
1553
+ --ui-kbd-size-lg: 1.375rem;
1554
+ --ui-kbd-size-xl: 1.5rem;
1555
+
1496
1556
  /* Group header (option-group, table category) */
1497
1557
  --ui-group-header-font: 0.625rem;
1498
1558
 
@@ -1606,6 +1666,8 @@
1606
1666
  /* ── Intent (maps to color token families) ── */
1607
1667
 
1608
1668
  :root {
1669
+ --_body: var(--neutral-body);
1670
+
1609
1671
  --_card: var(--neutral-card);
1610
1672
  --_card-hover: var(--neutral-card-hover);
1611
1673
 
@@ -1657,6 +1719,22 @@
1657
1719
  --_ink-disabled: var(--neutral-ink-disabled);
1658
1720
  }
1659
1721
 
1722
+ /* ── Toggle control defaults ── */
1723
+ /* WHY: Checkboxes, radios, switches, and range sliders use accent-colored
1724
+ surface fills for their checked/active states by default.
1725
+ [intent] selectors below override --_surface when an explicit intent is set. */
1726
+
1727
+ :where(ui-checkbox, ui-radio, ui-switch, ui-range) {
1728
+ --_surface: var(--accent-surface);
1729
+ --_surface-hover: var(--accent-surface-hover);
1730
+ --_surface-active: var(--accent-surface-active);
1731
+ --_surface-disabled: var(--accent-surface-disabled);
1732
+ --_surface-ink: var(--accent-surface-ink);
1733
+ --_surface-ink-hover: var(--accent-surface-ink-hover);
1734
+ --_surface-ink-active: var(--accent-surface-ink-active);
1735
+ --_surface-ink-disabled: var(--accent-surface-ink-disabled);
1736
+ }
1737
+
1660
1738
  :where([intent="neutral"]) {
1661
1739
  --_card: var(--neutral-card);
1662
1740
  --_card-hover: var(--neutral-card-hover);
@@ -2369,7 +2447,7 @@
2369
2447
  font-weight: var(--_font-weight);
2370
2448
 
2371
2449
  border-radius: var(--_radius);
2372
- border: 1px solid var(--_border-color, var(--neutral-border-muted));
2450
+ border: 1px solid var(--_border-color, var(--_border-muted));
2373
2451
 
2374
2452
  background: var(--_background, var(--_button));
2375
2453
  color: var(--_color, var(--_ink));
@@ -2386,17 +2464,17 @@
2386
2464
 
2387
2465
  :where(ui-button):hover,
2388
2466
  :where(ui-button)[force-hover] {
2389
- background: var(--_background-hover, var(--neutral-button-hover));
2467
+ background: var(--_background-hover, var(--_button-hover));
2390
2468
  color: var(--_color-hover, var(--_ink-hover));
2391
- border-color: var(--_border-color-hover, var(--neutral-border-hover));
2469
+ border-color: var(--_border-color-hover, var(--_border-hover));
2392
2470
  }
2393
2471
 
2394
2472
  :where(ui-button):active,
2395
2473
  :where(ui-button)[pressed],
2396
2474
  :where(ui-button)[force-active] {
2397
- background: var(--_background-active, var(--neutral-button-active));
2475
+ background: var(--_background-active, var(--_button-active));
2398
2476
  color: var(--_color-active, var(--_ink-active));
2399
- border-color: var(--_border-color-active, var(--neutral-border-active));
2477
+ border-color: var(--_border-color-active, var(--_border-active));
2400
2478
  }
2401
2479
 
2402
2480
  :where(ui-button):focus-visible,
@@ -2406,9 +2484,9 @@
2406
2484
  }
2407
2485
 
2408
2486
  :where(ui-button)[aria-disabled="true"] {
2409
- background: var(--_background-disabled, var(--neutral-button-disabled));
2487
+ background: var(--_background-disabled, var(--_button-disabled));
2410
2488
  color: var(--_color-disabled, var(--_ink-disabled));
2411
- border-color: var(--_border-color-disabled, var(--neutral-border-muted));
2489
+ border-color: var(--_border-color-disabled, var(--_border-muted));
2412
2490
  cursor: not-allowed;
2413
2491
  pointer-events: none;
2414
2492
  }
@@ -2981,34 +3059,25 @@
2981
3059
  }
2982
3060
 
2983
3061
  /* ── Checked State ── */
2984
- /* WHY: Default checked fill uses accent so checkbox is visually distinct
2985
- without needing intent="accent". Unchecked borders stay neutral.
3062
+ /* WHY: Default checked fill uses --_surface (accent by default via
3063
+ intent="accent" being the implicit default for toggle controls).
2986
3064
  When an explicit [intent] is set, the intent selector's --_surface wins. */
2987
3065
 
2988
3066
  :where(ui-checkbox)[aria-checked="true"]::before {
2989
- background: var(--accent-surface);
2990
- border-color: var(--accent-surface);
2991
- }
2992
-
2993
- :where(ui-checkbox)[aria-checked="true"]::after {
2994
- background: var(--accent-surface-ink);
2995
- transform: translateY(-50%) scale(1);
2996
- }
2997
-
2998
- :where(ui-checkbox)[intent][aria-checked="true"]::before {
2999
3067
  background: var(--_surface);
3000
3068
  border-color: var(--_surface);
3001
3069
  }
3002
3070
 
3003
- :where(ui-checkbox)[intent][aria-checked="true"]::after {
3071
+ :where(ui-checkbox)[aria-checked="true"]::after {
3004
3072
  background: var(--_surface-ink);
3073
+ transform: translateY(-50%) scale(1);
3005
3074
  }
3006
3075
 
3007
3076
  /* ── Indeterminate State ── */
3008
3077
 
3009
3078
  :where(ui-checkbox)[aria-checked="mixed"]::before {
3010
- background: var(--accent-surface);
3011
- border-color: var(--accent-surface);
3079
+ background: var(--_surface);
3080
+ border-color: var(--_surface);
3012
3081
  }
3013
3082
 
3014
3083
  :where(ui-checkbox)[aria-checked="mixed"]::after {
@@ -3016,17 +3085,8 @@
3016
3085
  -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");
3017
3086
  mask-size: 75%;
3018
3087
  -webkit-mask-size: 75%;
3019
- background: var(--accent-surface-ink);
3020
- transform: translateY(-50%) scale(1);
3021
- }
3022
-
3023
- :where(ui-checkbox)[intent][aria-checked="mixed"]::before {
3024
- background: var(--_surface);
3025
- border-color: var(--_surface);
3026
- }
3027
-
3028
- :where(ui-checkbox)[intent][aria-checked="mixed"]::after {
3029
3088
  background: var(--_surface-ink);
3089
+ transform: translateY(-50%) scale(1);
3030
3090
  }
3031
3091
 
3032
3092
  /* ── Hover ── */
@@ -3046,14 +3106,6 @@
3046
3106
  :where(ui-checkbox)[aria-checked="mixed"]:hover::before,
3047
3107
  :where(ui-checkbox)[aria-checked="true"][force-hover]::before,
3048
3108
  :where(ui-checkbox)[aria-checked="mixed"][force-hover]::before {
3049
- background: var(--accent-surface-hover);
3050
- border-color: var(--accent-surface-hover);
3051
- }
3052
-
3053
- :where(ui-checkbox)[intent][aria-checked="true"]:hover::before,
3054
- :where(ui-checkbox)[intent][aria-checked="mixed"]:hover::before,
3055
- :where(ui-checkbox)[intent][aria-checked="true"][force-hover]::before,
3056
- :where(ui-checkbox)[intent][aria-checked="mixed"][force-hover]::before {
3057
3109
  background: var(--_surface-hover);
3058
3110
  border-color: var(--_surface-hover);
3059
3111
  }
@@ -3067,12 +3119,6 @@
3067
3119
 
3068
3120
  :where(ui-checkbox)[aria-checked="true"][pressed]::before,
3069
3121
  :where(ui-checkbox)[aria-checked="mixed"][pressed]::before {
3070
- background: var(--accent-surface-active);
3071
- border-color: var(--accent-surface-active);
3072
- }
3073
-
3074
- :where(ui-checkbox)[intent][aria-checked="true"][pressed]::before,
3075
- :where(ui-checkbox)[intent][aria-checked="mixed"][pressed]::before {
3076
3122
  background: var(--_surface-active);
3077
3123
  border-color: var(--_surface-active);
3078
3124
  }
@@ -3726,6 +3772,45 @@
3726
3772
 
3727
3773
  }
3728
3774
 
3775
+ @layer ui {
3776
+
3777
+ /* ╭──────────────────────────────────────────────────────────╮
3778
+ │ ui-kbd │
3779
+ │ Keyboard shortcut indicator. Own font/size scale │
3780
+ │ (like ui-badge) — zero-attribute = md. │
3781
+ ╰──────────────────────────────────────────────────────────╯ */
3782
+
3783
+ :where(ui-kbd) {
3784
+ --_icon-size: 1.125em;
3785
+
3786
+ display: inline-flex;
3787
+ align-items: center;
3788
+ justify-content: center;
3789
+ font-family: ui-monospace, monospace;
3790
+ font-size: var(--ui-kbd-font-md);
3791
+ line-height: 1;
3792
+ min-height: var(--ui-kbd-size-md);
3793
+ padding-inline: 0.4em;
3794
+ border-radius: 0.5em;
3795
+ background: var(--_ground, var(--_body));
3796
+ border: 1px solid var(--_border-muted);
3797
+ color: var(--_ink-muted);
3798
+ white-space: nowrap;
3799
+ user-select: none;
3800
+ flex-shrink: 0;
3801
+ vertical-align: baseline;
3802
+ }
3803
+
3804
+ /* ── Sizes ── */
3805
+
3806
+ :where(ui-kbd[size="xs"]) { font-size: var(--ui-kbd-font-xs); min-height: var(--ui-kbd-size-xs); }
3807
+ :where(ui-kbd[size="sm"]) { font-size: var(--ui-kbd-font-sm); min-height: var(--ui-kbd-size-sm); }
3808
+ :where(ui-kbd[size="md"]) { font-size: var(--ui-kbd-font-md); min-height: var(--ui-kbd-size-md); }
3809
+ :where(ui-kbd[size="lg"]) { font-size: var(--ui-kbd-font-lg); min-height: var(--ui-kbd-size-lg); }
3810
+ :where(ui-kbd[size="xl"]) { font-size: var(--ui-kbd-font-xl); min-height: var(--ui-kbd-size-xl); }
3811
+
3812
+ }
3813
+
3729
3814
  @layer ui {
3730
3815
 
3731
3816
  /* ── Listbox Base ── */
@@ -3866,11 +3951,14 @@
3866
3951
  │ Uses ui-nav-item / ui-nav-group for children. │
3867
3952
  ╰──────────────────────────────────────────────────────────╯ */
3868
3953
 
3954
+ /* WHY: ui-nav is a transparent flex wrapper — no inline padding.
3955
+ Inline padding is owned by leaf items (nav-item, summary).
3956
+ Block padding is owned by nav-group margins. */
3869
3957
  :where(ui-nav) {
3870
3958
  display: flex;
3871
3959
  flex-direction: column;
3872
3960
  gap: 0;
3873
- padding: var(--_space);
3961
+ padding: 0;
3874
3962
  outline: none;
3875
3963
  }
3876
3964
 
@@ -3885,6 +3973,7 @@
3885
3973
  display: flex;
3886
3974
  align-items: center;
3887
3975
  gap: calc(var(--_space) * 2);
3976
+ padding-inline: calc(var(--_space-k) * var(--_space));
3888
3977
 
3889
3978
  min-height: var(--_min-height);
3890
3979
 
@@ -3984,6 +4073,7 @@
3984
4073
  display: flex;
3985
4074
  align-items: center;
3986
4075
  gap: calc(var(--_space) * 2);
4076
+ padding-inline: calc(var(--_space-k) * var(--_space));
3987
4077
  cursor: pointer;
3988
4078
  user-select: none;
3989
4079
  list-style: none;
@@ -4018,6 +4108,18 @@
4018
4108
  user-select: none;
4019
4109
  }
4020
4110
 
4111
+ /* WHY: Fixed-size icon well — matches ui-layout-sidebar-item [slot="icon"].
4112
+ All sidebar icons align on the same 1.5rem column regardless of icon size.
4113
+ JS wraps the first <ui-icon> child in a .icon-well span during setup(). */
4114
+ :where(ui-nav-group-header) > :where(.icon-well) {
4115
+ display: inline-flex;
4116
+ align-items: center;
4117
+ justify-content: center;
4118
+ width: 1.5rem;
4119
+ height: 1.5rem;
4120
+ flex-shrink: 0;
4121
+ }
4122
+
4021
4123
  /* ── Chevron icon (right side of summary) ── */
4022
4124
 
4023
4125
  :where(ui-nav-group) > :where(details) > :where(summary)::after {
@@ -4071,12 +4173,16 @@
4071
4173
  /* WHY: Connector draws from below the header to the bottom of the group.
4072
4174
  Only shown when header contains an icon. Scoped via :has(). */
4073
4175
 
4074
- :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)) {
4075
- --_group-line-inset: calc(var(--_icon-size) / 2);
4076
- --_group-child-inset: calc(var(--_icon-size) + var(--_space) * 2);
4176
+ /* WHY: Insets account for the summary's padding-inline so the connector
4177
+ line passes through the icon center and child text aligns with header text. */
4178
+ :where(ui-nav-group):has(:where(ui-nav-group-header) :where(.icon-well)) {
4179
+ --_group-pad: calc(var(--_space-k) * var(--_space));
4180
+ --_group-icon-well: 1.5rem;
4181
+ --_group-line-inset: calc(var(--_group-pad) + var(--_group-icon-well) / 2);
4182
+ --_group-child-inset: calc(var(--_group-icon-well) + var(--_space) * 2);
4077
4183
  }
4078
4184
 
4079
- :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)):has(:where(details[open]))::after {
4185
+ :where(ui-nav-group):has(:where(ui-nav-group-header) :where(.icon-well)):has(:where(details[open]))::after {
4080
4186
  content: '';
4081
4187
  position: absolute;
4082
4188
  inset-inline-start: var(--_group-line-inset);
@@ -4091,7 +4197,7 @@
4091
4197
  /* WHY: A ::before pseudo on the group slides along the connector line.
4092
4198
  JS sets --_indicator-index; :state(has-selection) gates visibility. */
4093
4199
 
4094
- :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)):has(:where(details[open])):state(has-selection)::before {
4200
+ :where(ui-nav-group):has(:where(ui-nav-group-header) :where(.icon-well)):has(:where(details[open])):state(has-selection)::before {
4095
4201
  content: '';
4096
4202
  position: absolute;
4097
4203
  z-index: 1;
@@ -4118,6 +4224,73 @@
4118
4224
  margin-inline-start: var(--_group-child-inset, 0);
4119
4225
  }
4120
4226
 
4227
+ /* ── Nav Group Flyout (collapsed sidebar) ── */
4228
+ /* WHY: In collapsed mode, summary click opens a ui-listbox popover to the right
4229
+ instead of expanding the <details>. Same pattern as sidebar-trigger menus. */
4230
+
4231
+ :where(ui-nav-group) > :where(ui-listbox.nav-group-flyout[popover]) {
4232
+ position: fixed;
4233
+ position-area: inline-end span-block-end;
4234
+ position-try-fallbacks: --nav-flyout-flip-up;
4235
+ margin: 0 0 0 var(--ui-popover-gap);
4236
+ min-width: 200px;
4237
+ max-height: var(--ui-popover-max-height);
4238
+ overflow-y: auto;
4239
+ }
4240
+
4241
+ @position-try --nav-flyout-flip-up {
4242
+ position-area: inline-end span-block-start;
4243
+ }
4244
+
4245
+ /* ── Container Query: Collapsed Sidebar ── */
4246
+ /* WHY: Nav components own their own collapsed behavior via @container.
4247
+ The sidebar aside declares container-name: sidebar. When it shrinks
4248
+ to 48px (icon rail), nav responds to the width — not to [collapsed].
4249
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
4250
+
4251
+ @container sidebar (max-width: 80px) {
4252
+
4253
+ /* Nav items hide entirely — only group headers (with icons) remain. */
4254
+ :where(ui-nav-item) {
4255
+ display: none;
4256
+ }
4257
+
4258
+ /* Summary shrinks to icon-only. Reduced inline padding wraps tightly
4259
+ around the icon; parent align-items: center handles horizontal centering. */
4260
+ :where(ui-nav-group) > :where(details) > :where(summary) {
4261
+ padding-inline: var(--_space);
4262
+ border-radius: var(--_radius);
4263
+ }
4264
+
4265
+ /* Hide chevron — no expand/collapse in icon rail (flyout instead). */
4266
+ :where(ui-nav-group) > :where(details) > :where(summary)::after {
4267
+ display: none;
4268
+ }
4269
+
4270
+ /* Hide vertical connector line and sliding indicator. */
4271
+ :where(ui-nav-group)::after,
4272
+ :where(ui-nav-group)::before {
4273
+ display: none;
4274
+ }
4275
+
4276
+ /* Header collapses to icon-only. font-size: 0 hides text nodes
4277
+ (can't be targeted by CSS). Icon overrides back to normal below. */
4278
+ :where(ui-nav-group-header) {
4279
+ flex: 0 0 auto;
4280
+ font-size: 0;
4281
+ gap: 0;
4282
+ }
4283
+
4284
+ :where(ui-nav-group-header) :where(.icon-well) :where(ui-icon) {
4285
+ font-size: var(--_font-size, 1rem);
4286
+ }
4287
+
4288
+ /* Collapse inter-group spacing in icon rail. */
4289
+ :where(ui-nav-group) + :where(ui-nav-group) {
4290
+ margin-block-start: 0;
4291
+ }
4292
+ }
4293
+
4121
4294
  }
4122
4295
 
4123
4296
  @layer ui {
@@ -4258,27 +4431,15 @@
4258
4431
  }
4259
4432
 
4260
4433
  /* ── Selected ── */
4261
- /* WHY: Default selected fill uses accent so radio is visually distinct
4262
- without needing intent="accent". Unchecked borders stay neutral.
4263
- When an explicit [intent] is set, the intent selector's --_surface wins. */
4264
4434
 
4265
4435
  :where(ui-radio)[aria-checked="true"]::before {
4266
- background: var(--accent-surface);
4267
- border-color: var(--accent-surface);
4268
- }
4269
-
4270
- :where(ui-radio)[aria-checked="true"]::after {
4271
- background: var(--accent-surface-ink);
4272
- transform: translateY(-50%) scale(1);
4273
- }
4274
-
4275
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::before {
4276
4436
  background: var(--_surface);
4277
4437
  border-color: var(--_surface);
4278
4438
  }
4279
4439
 
4280
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::after {
4440
+ :where(ui-radio)[aria-checked="true"]::after {
4281
4441
  background: var(--_surface-ink);
4442
+ transform: translateY(-50%) scale(1);
4282
4443
  }
4283
4444
 
4284
4445
  /* ── Hover ── */
@@ -4296,12 +4457,6 @@
4296
4457
 
4297
4458
  :where(ui-radio)[aria-checked="true"]:hover::before,
4298
4459
  :where(ui-radio)[aria-checked="true"][force-hover]::before {
4299
- background: var(--accent-surface-hover);
4300
- border-color: var(--accent-surface-hover);
4301
- }
4302
-
4303
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]:hover::before,
4304
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"][force-hover]::before {
4305
4460
  background: var(--_surface-hover);
4306
4461
  border-color: var(--_surface-hover);
4307
4462
  }
@@ -4314,11 +4469,6 @@
4314
4469
  }
4315
4470
 
4316
4471
  :where(ui-radio)[aria-checked="true"][pressed]::before {
4317
- background: var(--accent-surface-active);
4318
- border-color: var(--accent-surface-active);
4319
- }
4320
-
4321
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"][pressed]::before {
4322
4472
  background: var(--_surface-active);
4323
4473
  border-color: var(--_surface-active);
4324
4474
  }
@@ -4417,16 +4567,12 @@
4417
4567
  /* 0% = track-height (circle), 100% = full width */
4418
4568
  width: calc(var(--_track-height) + (100% - var(--_track-height)) * var(--_progress));
4419
4569
  border-radius: calc(var(--_track-height) / 2);
4420
- background: var(--accent-surface);
4570
+ background: var(--_surface);
4421
4571
 
4422
4572
  transition:
4423
4573
  background var(--_duration) var(--_easing);
4424
4574
  }
4425
4575
 
4426
- :where(ui-range)[intent]::after {
4427
- background: var(--_surface);
4428
- }
4429
-
4430
4576
  /* ── Thumb ── */
4431
4577
 
4432
4578
  :where(ui-range) > :where(.ui-range-thumb) {
@@ -4438,8 +4584,8 @@
4438
4584
  width: var(--_thumb-size);
4439
4585
  height: var(--_thumb-size);
4440
4586
  border-radius: 50%;
4441
- background: var(--accent-surface-ink);
4442
- border: 2px solid var(--accent-surface);
4587
+ background: var(--_surface-ink);
4588
+ border: 2px solid var(--_surface);
4443
4589
  box-shadow: var(--ui-shadow-sm);
4444
4590
  pointer-events: none;
4445
4591
 
@@ -4449,11 +4595,6 @@
4449
4595
  border-color var(--_duration) var(--_easing);
4450
4596
  }
4451
4597
 
4452
- :where(ui-range)[intent] > :where(.ui-range-thumb) {
4453
- background: var(--_surface-ink);
4454
- border-color: var(--_surface);
4455
- }
4456
-
4457
4598
  /* ── Focus ── */
4458
4599
 
4459
4600
  :where(ui-range):focus-visible > :where(.ui-range-thumb),
@@ -4466,22 +4607,11 @@
4466
4607
 
4467
4608
  :where(ui-range):hover::after,
4468
4609
  :where(ui-range)[force-hover]::after {
4469
- background: var(--accent-surface-hover);
4470
- }
4471
-
4472
- :where(ui-range)[intent]:hover::after,
4473
- :where(ui-range)[intent][force-hover]::after {
4474
4610
  background: var(--_surface-hover);
4475
4611
  }
4476
4612
 
4477
4613
  :where(ui-range):hover > :where(.ui-range-thumb),
4478
4614
  :where(ui-range)[force-hover] > :where(.ui-range-thumb) {
4479
- border-color: var(--accent-surface-hover);
4480
- box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--accent-surface);
4481
- }
4482
-
4483
- :where(ui-range)[intent]:hover > :where(.ui-range-thumb),
4484
- :where(ui-range)[intent][force-hover] > :where(.ui-range-thumb) {
4485
4615
  border-color: var(--_surface-hover);
4486
4616
  box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--_surface);
4487
4617
  }
@@ -4489,19 +4619,10 @@
4489
4619
  /* ── Active (dragging) ── */
4490
4620
 
4491
4621
  :where(ui-range)[pressed]::after {
4492
- background: var(--accent-surface-active);
4493
- }
4494
-
4495
- :where(ui-range)[intent][pressed]::after {
4496
4622
  background: var(--_surface-active);
4497
4623
  }
4498
4624
 
4499
4625
  :where(ui-range)[pressed] > :where(.ui-range-thumb) {
4500
- border-color: var(--accent-surface-active);
4501
- box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--accent-surface);
4502
- }
4503
-
4504
- :where(ui-range)[intent][pressed] > :where(.ui-range-thumb) {
4505
4626
  border-color: var(--_surface-active);
4506
4627
  box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--_surface);
4507
4628
  }
@@ -5056,22 +5177,13 @@
5056
5177
  When an explicit [intent] is set, the intent selector's --_surface wins. */
5057
5178
 
5058
5179
  :where(ui-switch)[aria-checked="true"]::before {
5059
- background: var(--accent-surface);
5060
- border-color: var(--accent-surface);
5180
+ background: var(--_surface);
5181
+ border-color: var(--_surface);
5061
5182
  }
5062
5183
 
5063
5184
  :where(ui-switch)[aria-checked="true"]::after {
5064
5185
  /* Slide thumb to the right via translateX — GPU composited */
5065
5186
  transform: translateY(-50%) translateX(calc(var(--_track-width) - var(--_thumb-size) - var(--_thumb-offset) * 2));
5066
- background: var(--accent-surface-ink);
5067
- }
5068
-
5069
- :where(ui-switch)[intent][aria-checked="true"]::before {
5070
- background: var(--_surface);
5071
- border-color: var(--_surface);
5072
- }
5073
-
5074
- :where(ui-switch)[intent][aria-checked="true"]::after {
5075
5187
  background: var(--_surface-ink);
5076
5188
  }
5077
5189
 
@@ -5090,12 +5202,6 @@
5090
5202
 
5091
5203
  :where(ui-switch)[aria-checked="true"]:hover::before,
5092
5204
  :where(ui-switch)[aria-checked="true"][force-hover]::before {
5093
- background: var(--accent-surface-hover);
5094
- border-color: var(--accent-surface-hover);
5095
- }
5096
-
5097
- :where(ui-switch)[intent][aria-checked="true"]:hover::before,
5098
- :where(ui-switch)[intent][aria-checked="true"][force-hover]::before {
5099
5205
  background: var(--_surface-hover);
5100
5206
  border-color: var(--_surface-hover);
5101
5207
  }
@@ -5108,11 +5214,6 @@
5108
5214
  }
5109
5215
 
5110
5216
  :where(ui-switch)[aria-checked="true"][pressed]::before {
5111
- background: var(--accent-surface-active);
5112
- border-color: var(--accent-surface-active);
5113
- }
5114
-
5115
- :where(ui-switch)[intent][aria-checked="true"][pressed]::before {
5116
5217
  background: var(--_surface-active);
5117
5218
  border-color: var(--_surface-active);
5118
5219
  }
@@ -6572,7 +6673,7 @@
6572
6673
 
6573
6674
  :where(ui-layout-chat) :where(.layout-resize-handle):hover,
6574
6675
  :where(ui-layout-chat[resizing]) :where(.layout-resize-handle) {
6575
- background: var(--neutral-border-muted);
6676
+ background: var(--_border-muted);
6576
6677
  }
6577
6678
 
6578
6679
  }
@@ -6634,7 +6735,7 @@
6634
6735
 
6635
6736
  :where(ui-layout-inspector) :where(.layout-resize-handle):hover,
6636
6737
  :where(ui-layout-inspector[resizing]) :where(.layout-resize-handle) {
6637
- background: var(--neutral-border-muted);
6738
+ background: var(--_border-muted);
6638
6739
  }
6639
6740
 
6640
6741
  }
@@ -6661,16 +6762,16 @@
6661
6762
  content: '';
6662
6763
  width: var(--ui-layout-sidebar-width);
6663
6764
  flex-shrink: 0;
6664
- background: var(--neutral-body);
6665
- border-right: 1px solid var(--neutral-border-muted);
6765
+ background: var(--_body);
6766
+ border-right: 1px solid var(--_border-muted);
6666
6767
  }
6667
6768
 
6668
6769
  :where(ui-layout-sidebar):not([data-ready])[collapsed]::before {
6669
6770
  content: '';
6670
6771
  width: 48px;
6671
6772
  flex-shrink: 0;
6672
- background: var(--neutral-body);
6673
- border-right: 1px solid var(--neutral-border-muted);
6773
+ background: var(--_body);
6774
+ border-right: 1px solid var(--_border-muted);
6674
6775
  }
6675
6776
 
6676
6777
  /* ── Content Column ── */
@@ -6686,16 +6787,23 @@
6686
6787
  }
6687
6788
 
6688
6789
  /* ── Sidebar Aside ── */
6790
+ /* WHY: position: relative creates the containing block for absolute
6791
+ header/footer overlays. Content fills the full height and scrolls
6792
+ underneath the pinned header/footer. */
6689
6793
 
6690
6794
  :where(ui-layout-sidebar) > :where([slot="sidebar"]) {
6795
+ --_sidebar-header-height: 0px;
6796
+ --_sidebar-footer-height: 0px;
6797
+
6798
+ container-name: sidebar;
6799
+ container-type: inline-size;
6800
+ position: sticky;
6801
+ top: 0;
6691
6802
  width: var(--ui-layout-sidebar-width);
6692
6803
  min-width: 160px;
6693
6804
  max-width: 400px;
6694
6805
  height: 100dvh;
6695
- position: sticky;
6696
- top: 0;
6697
- display: flex;
6698
- flex-direction: column;
6806
+ display: block;
6699
6807
  transition: width var(--_duration) var(--_easing), min-width var(--_duration) var(--_easing);
6700
6808
  overflow: hidden;
6701
6809
  z-index: 10;
@@ -6707,7 +6815,9 @@
6707
6815
  }
6708
6816
 
6709
6817
  /* ── Collapsed Icon Rail ── */
6710
- /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding. */
6818
+ /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding.
6819
+ [collapsed] drives the aside to 48px which triggers @container sidebar queries
6820
+ in this file and in child component CSS (ui-nav.css, etc.). */
6711
6821
 
6712
6822
  :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) {
6713
6823
  width: 48px;
@@ -6728,11 +6838,7 @@
6728
6838
 
6729
6839
  :where(ui-layout-sidebar) :where(.layout-resize-handle):hover,
6730
6840
  :where(ui-layout-sidebar) > :where([slot="sidebar"][resizing]) :where(.layout-resize-handle) {
6731
- background: var(--neutral-border-muted);
6732
- }
6733
-
6734
- :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) :where(.layout-resize-handle) {
6735
- display: none;
6841
+ background: var(--_border-muted);
6736
6842
  }
6737
6843
 
6738
6844
  /* WHY: When collapsed to icon rail, the sidebar edge meets content — drop left padding. */
@@ -6745,101 +6851,89 @@
6745
6851
  padding-left: 0;
6746
6852
  }
6747
6853
 
6748
- /* ── Collapsed Icon-Rail Content ── */
6749
- /* WHY: When collapsed, sidebar slot becomes a narrow icon rail.
6750
- Text and trailing slots are hidden; only icons remain visible.
6751
- Nav items hide entirely — only group headers (with icons) stay. */
6752
-
6753
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-content) {
6754
- align-items: center;
6755
- padding: 0.5rem;
6756
- }
6757
-
6758
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-header),
6759
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-footer) {
6760
- justify-content: center;
6761
- padding-inline: 0.5rem;
6762
- }
6763
-
6764
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="label"]),
6765
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-layout-sidebar-trigger) :where([slot="trailing"]) {
6766
- display: none;
6767
- }
6768
-
6769
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-item) {
6770
- display: none;
6771
- }
6772
-
6773
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary) {
6774
- justify-content: center;
6775
- }
6776
-
6777
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) > :where(details) > :where(summary)::after {
6778
- display: none;
6779
- }
6780
-
6781
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::after,
6782
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group)::before {
6783
- display: none;
6784
- }
6785
-
6786
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) {
6787
- justify-content: center;
6788
- font-size: 0;
6789
- gap: 0;
6790
- }
6791
-
6792
- /* WHY: Icon keeps its size even though parent font-size is 0. */
6793
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group-header) :where(ui-icon) {
6794
- font-size: var(--_font-size, 1rem);
6795
- }
6796
-
6797
- :where(ui-layout-sidebar)[collapsed] :where([slot="sidebar"]) :where(ui-nav-group) + :where(ui-nav-group) {
6798
- margin-block-start: 0;
6799
- }
6800
-
6801
6854
  /* ── Sidebar Header ── */
6855
+ /* WHY: Absolute-positioned overlay pinned to top of aside. Content scrolls
6856
+ underneath it. z-index: 2 sits above content (z-index: 0). */
6802
6857
 
6803
6858
  :where(ui-layout-sidebar-header) {
6859
+ position: absolute;
6860
+ top: 0;
6861
+ left: 0;
6862
+ right: 0;
6863
+ z-index: 2;
6804
6864
  display: flex;
6805
- align-items: center;
6806
- min-height: var(--ui-layout-bar-height);
6807
- gap: calc(var(--_space) * 2);
6808
- padding-block: var(--_space);
6809
- padding-inline: calc(var(--_space-k) * var(--_space));
6810
- flex-shrink: 0;
6865
+ flex-direction: column;
6811
6866
  }
6812
6867
 
6813
6868
  /* ── Sidebar Content ── */
6869
+ /* WHY: Full height of the aside, scrollable. Padding-block offsets keep
6870
+ content from starting behind header / ending behind footer.
6871
+ Fade-out alpha mask dissolves content edges under the overlays. */
6814
6872
 
6815
6873
  :where(ui-layout-sidebar-content) {
6816
6874
  display: flex;
6817
6875
  flex-direction: column;
6818
- gap: calc(var(--_space) * 2);
6819
- padding-block: var(--_space);
6820
- padding-inline: calc(var(--_space-k) * var(--_space));
6821
- flex: 1;
6822
- min-height: 0;
6876
+ padding-block-start: var(--_sidebar-header-height);
6877
+ padding-block-end: var(--_sidebar-footer-height);
6823
6878
  width: 100%;
6879
+ height: 100%;
6880
+ overflow-y: auto;
6881
+ scrollbar-width: none;
6882
+ }
6883
+
6884
+ /* WHY: When header is present, fade top edge. Content dissolves as it
6885
+ scrolls under the header — transparent at top, fully visible by 1.25×
6886
+ the header height. No opaque header background needed. */
6887
+ :where(ui-layout-sidebar-content)[data-has-header] {
6888
+ mask-image: linear-gradient(
6889
+ to bottom,
6890
+ transparent 0,
6891
+ black calc(var(--_sidebar-header-height) * 1.25),
6892
+ black 100%
6893
+ );
6894
+ }
6895
+
6896
+ /* WHY: When footer is present, fade bottom edge. */
6897
+ :where(ui-layout-sidebar-content)[data-has-footer] {
6898
+ mask-image: linear-gradient(
6899
+ to bottom,
6900
+ black 0,
6901
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
6902
+ transparent 100%
6903
+ );
6904
+ }
6905
+
6906
+ /* WHY: When both header AND footer are present, fade both edges. */
6907
+ :where(ui-layout-sidebar-content)[data-has-header][data-has-footer] {
6908
+ mask-image: linear-gradient(
6909
+ to bottom,
6910
+ transparent 0,
6911
+ black calc(var(--_sidebar-header-height) * 1.25),
6912
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
6913
+ transparent 100%
6914
+ );
6824
6915
  }
6825
6916
 
6826
6917
  /* ── Sidebar Footer ── */
6918
+ /* WHY: Absolute-positioned overlay pinned to bottom of aside. */
6827
6919
 
6828
6920
  :where(ui-layout-sidebar-footer) {
6921
+ position: absolute;
6922
+ bottom: 0;
6923
+ left: 0;
6924
+ right: 0;
6925
+ z-index: 2;
6829
6926
  display: flex;
6830
- align-items: center;
6831
- min-height: var(--ui-layout-bar-height);
6832
- gap: calc(var(--_space) * 2);
6833
- padding-block: var(--_space);
6834
- padding-inline: calc(var(--_space-k) * var(--_space));
6835
- flex-shrink: 0;
6927
+ flex-direction: column;
6836
6928
  }
6837
6929
 
6838
- /* ── Sidebar Trigger ── */
6839
- /* WHY: Menu trigger for sidebar header/footer. Styled like ui-nav-group-header:
6840
- bold text, icon support, hover color. Opens a popover on click. */
6930
+ /* ── Sidebar Item ── */
6931
+ /* WHY: Universal sidebar row element. Provides inline padding for any content
6932
+ (buttons, links, icons). When a child ui-listbox[popover] is present, JS
6933
+ wires PopoverController for click-to-toggle menu behavior.
6934
+ Absorbs the old ui-layout-sidebar-trigger role — one element for all rows. */
6841
6935
 
6842
- :where(ui-layout-sidebar-trigger) {
6936
+ :where(ui-layout-sidebar-item) {
6843
6937
  display: flex;
6844
6938
  align-items: center;
6845
6939
  flex: 1;
@@ -6855,31 +6949,61 @@
6855
6949
  user-select: none;
6856
6950
  border: none;
6857
6951
  background: none;
6858
- padding: var(--_space);
6952
+ padding-block: var(--_space);
6953
+ padding-inline: calc(var(--_space-k) * var(--_space));
6859
6954
  border-radius: var(--_radius);
6860
6955
  transition: color var(--_duration) var(--_easing);
6861
6956
  }
6862
6957
 
6863
- :where(ui-layout-sidebar-trigger):hover,
6864
- :where(ui-layout-sidebar-trigger)[force-hover] {
6958
+ /* WHY: Items in header/footer match the breadcrumb bar height so the
6959
+ first sidebar row aligns horizontally with the breadcrumb. */
6960
+ :where(ui-layout-sidebar-header) > :where(ui-layout-sidebar-item),
6961
+ :where(ui-layout-sidebar-footer) > :where(ui-layout-sidebar-item) {
6962
+ min-height: var(--ui-layout-bar-height);
6963
+ }
6964
+
6965
+ :where(ui-layout-sidebar-item):hover,
6966
+ :where(ui-layout-sidebar-item)[force-hover] {
6865
6967
  color: var(--_ink-strong);
6866
6968
  }
6867
6969
 
6868
- :where(ui-layout-sidebar-trigger):focus-visible,
6869
- :where(ui-layout-sidebar-trigger)[force-focus-visible] {
6970
+ :where(ui-layout-sidebar-item):focus-visible,
6971
+ :where(ui-layout-sidebar-item)[force-focus-visible] {
6870
6972
  outline: 2px solid var(--ui-focus-ring);
6871
6973
  outline-offset: -2px;
6872
6974
  }
6873
6975
 
6976
+ /* WHY: Fixed-size icon well so all sidebar icons (header logo, nav group icons,
6977
+ standalone icons) center-align on the same column width regardless of intrinsic
6978
+ icon size. 1.5rem matches the sidebar's visual rhythm at default density.
6979
+ Consumer wraps the icon: <span slot="icon"><ui-icon name="..."></ui-icon></span>
6980
+ The wrapper is the well; the icon keeps its own --ui-icon-size dimensions. */
6981
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
6982
+ display: inline-flex;
6983
+ align-items: center;
6984
+ justify-content: center;
6985
+ width: 1.5rem;
6986
+ height: 1.5rem;
6987
+ flex-shrink: 0;
6988
+ }
6989
+
6874
6990
  /* WHY: Trailing caret pushes to end — same pattern as button justify="spread". */
6875
- :where(ui-layout-sidebar-trigger) > :where([slot="trailing"]) {
6991
+ :where(ui-layout-sidebar-item) > :where([slot="trailing"]) {
6876
6992
  margin-inline-start: auto;
6877
6993
  flex-shrink: 0;
6878
6994
  color: var(--_ink-muted);
6879
6995
  }
6880
6996
 
6997
+ /* WHY: When the item wraps a sub-component (e.g. ui-button) that provides
6998
+ its own icon, hide [slot="icon"] in expanded mode — it only serves as the
6999
+ bare collapsed-rail representation. Items without sub-components (just
7000
+ icon + label + trailing) keep [slot="icon"] visible in both modes. */
7001
+ :where(ui-layout-sidebar-item:has(> ui-button)) > :where([slot="icon"]) {
7002
+ display: none;
7003
+ }
7004
+
6881
7005
  /* WHY: Title text truncates when sidebar narrows during resize. */
6882
- :where(ui-layout-sidebar-trigger) > :where([slot="label"]) {
7006
+ :where(ui-layout-sidebar-item) > :where([slot="label"]) {
6883
7007
  flex: 1;
6884
7008
  min-width: 0;
6885
7009
  white-space: nowrap;
@@ -6887,25 +7011,67 @@
6887
7011
  text-overflow: ellipsis;
6888
7012
  }
6889
7013
 
6890
- /* ── Trigger Popover ── */
6891
- /* WHY: Popover opens to the right of the sidebar trigger.
7014
+ /* ── Item Popover ── */
7015
+ /* WHY: Popover opens to the right of the sidebar item.
6892
7016
  Default: top-aligned, grows downward (span-block-end).
6893
7017
  Flip: bottom-aligned, grows upward (span-block-start). */
6894
7018
 
6895
- :where(ui-layout-sidebar-trigger) > :where(ui-listbox[popover]) {
7019
+ :where(ui-layout-sidebar-item) > :where(ui-listbox[popover]) {
6896
7020
  position: fixed;
6897
7021
  position-area: inline-end span-block-end;
6898
- position-try-fallbacks: --flip-up;
7022
+ position-try-fallbacks: --sidebar-item-flip-up;
6899
7023
  margin: 0 0 0 var(--ui-popover-gap);
6900
7024
  min-width: 200px;
6901
7025
  max-height: var(--ui-popover-max-height);
6902
7026
  overflow-y: auto;
6903
7027
  }
6904
7028
 
6905
- @position-try --flip-up {
7029
+ @position-try --sidebar-item-flip-up {
6906
7030
  position-area: inline-end span-block-start;
6907
7031
  }
6908
7032
 
7033
+ /* ── Container Query: Collapsed Sidebar ── */
7034
+ /* WHY: Each component owns its own collapsed behavior via @container.
7035
+ The aside is the container (container-name: sidebar). When it shrinks
7036
+ to 48px (icon rail), components respond to the width, not to [collapsed].
7037
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
7038
+
7039
+ @container sidebar (max-width: 80px) {
7040
+
7041
+ /* Resize handle — no dragging in icon rail */
7042
+ :where(.layout-resize-handle) {
7043
+ display: none;
7044
+ }
7045
+
7046
+ /* Header/footer — center content horizontally */
7047
+ :where(ui-layout-sidebar-header),
7048
+ :where(ui-layout-sidebar-footer) {
7049
+ align-items: center;
7050
+ }
7051
+
7052
+ /* Content — center items in the 48px rail */
7053
+ :where(ui-layout-sidebar-content) {
7054
+ align-items: center;
7055
+ }
7056
+
7057
+ /* Item — shrink to icon-only. Show [slot="icon"], hide everything else.
7058
+ WHY: Sidebar items can contain complex children (buttons with slots,
7059
+ links, etc.). Rather than overriding sub-component styles, the item
7060
+ provides its own bare icon via [slot="icon"] for the collapsed rail. */
7061
+ :where(ui-layout-sidebar-item) {
7062
+ flex: 0 0 auto;
7063
+ padding-inline: var(--_space);
7064
+ }
7065
+
7066
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
7067
+ display: inline-flex;
7068
+ }
7069
+
7070
+ :where(ui-layout-sidebar-item) > :where(:not([slot="icon"]):not(ui-listbox[popover]):not(.nav-group-flyout)) {
7071
+ display: none;
7072
+ }
7073
+ }
7074
+
6909
7075
  }
6910
7076
 
6911
7077
  @layer ui {