@nonoun/native-ui 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/components/ui-kbd/index.d.ts +3 -0
  2. package/dist/components/ui-kbd/index.d.ts.map +1 -0
  3. package/dist/components/ui-kbd/ui-kbd-element.d.ts +5 -0
  4. package/dist/components/ui-kbd/ui-kbd-element.d.ts.map +1 -0
  5. package/dist/components/ui-kbd/ui-kbd.d.ts +3 -0
  6. package/dist/components/ui-kbd/ui-kbd.d.ts.map +1 -0
  7. package/dist/components/ui-nav/ui-nav-group-element.d.ts +8 -0
  8. package/dist/components/ui-nav/ui-nav-group-element.d.ts.map +1 -1
  9. package/dist/components-lean.css +288 -169
  10. package/dist/components.css +290 -176
  11. package/dist/containers/ui-layout-sidebar/index.d.ts +1 -1
  12. package/dist/containers/ui-layout-sidebar/index.d.ts.map +1 -1
  13. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-element.d.ts +4 -1
  14. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-element.d.ts.map +1 -1
  15. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item-element.d.ts +10 -0
  16. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item-element.d.ts.map +1 -0
  17. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item.d.ts +3 -0
  18. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-item.d.ts.map +1 -0
  19. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar.d.ts +1 -1
  20. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar.d.ts.map +1 -1
  21. package/dist/custom-elements.json +2892 -2484
  22. package/dist/foundation.css +79 -1
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/native-ui-lean.css +367 -170
  26. package/dist/native-ui.css +369 -177
  27. package/dist/native-ui.js +3 -6
  28. package/dist/register-all.js +1 -1
  29. package/dist/ui-icon-element.js +95 -7
  30. package/package.json +1 -1
  31. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger-element.d.ts +0 -8
  32. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger-element.d.ts.map +0 -1
  33. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger.d.ts +0 -3
  34. package/dist/containers/ui-layout-sidebar/ui-layout-sidebar-trigger.d.ts.map +0 -1
@@ -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.25rem;
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;
@@ -4071,8 +4161,11 @@
4071
4161
  /* WHY: Connector draws from below the header to the bottom of the group.
4072
4162
  Only shown when header contains an icon. Scoped via :has(). */
4073
4163
 
4164
+ /* WHY: Insets account for the summary's padding-inline so the connector
4165
+ line passes through the icon center and child text aligns with header text. */
4074
4166
  :where(ui-nav-group):has(:where(ui-nav-group-header) :where(ui-icon)) {
4075
- --_group-line-inset: calc(var(--_icon-size) / 2);
4167
+ --_group-pad: calc(var(--_space-k) * var(--_space));
4168
+ --_group-line-inset: calc(var(--_group-pad) + var(--_icon-size) / 2);
4076
4169
  --_group-child-inset: calc(var(--_icon-size) + var(--_space) * 2);
4077
4170
  }
4078
4171
 
@@ -4118,6 +4211,73 @@
4118
4211
  margin-inline-start: var(--_group-child-inset, 0);
4119
4212
  }
4120
4213
 
4214
+ /* ── Nav Group Flyout (collapsed sidebar) ── */
4215
+ /* WHY: In collapsed mode, summary click opens a ui-listbox popover to the right
4216
+ instead of expanding the <details>. Same pattern as sidebar-trigger menus. */
4217
+
4218
+ :where(ui-nav-group) > :where(ui-listbox.nav-group-flyout[popover]) {
4219
+ position: fixed;
4220
+ position-area: inline-end span-block-end;
4221
+ position-try-fallbacks: --nav-flyout-flip-up;
4222
+ margin: 0 0 0 var(--ui-popover-gap);
4223
+ min-width: 200px;
4224
+ max-height: var(--ui-popover-max-height);
4225
+ overflow-y: auto;
4226
+ }
4227
+
4228
+ @position-try --nav-flyout-flip-up {
4229
+ position-area: inline-end span-block-start;
4230
+ }
4231
+
4232
+ /* ── Container Query: Collapsed Sidebar ── */
4233
+ /* WHY: Nav components own their own collapsed behavior via @container.
4234
+ The sidebar aside declares container-name: sidebar. When it shrinks
4235
+ to 48px (icon rail), nav responds to the width — not to [collapsed].
4236
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
4237
+
4238
+ @container sidebar (max-width: 80px) {
4239
+
4240
+ /* Nav items hide entirely — only group headers (with icons) remain. */
4241
+ :where(ui-nav-item) {
4242
+ display: none;
4243
+ }
4244
+
4245
+ /* Summary shrinks to icon-only. Reduced inline padding wraps tightly
4246
+ around the icon; parent align-items: center handles horizontal centering. */
4247
+ :where(ui-nav-group) > :where(details) > :where(summary) {
4248
+ padding-inline: var(--_space);
4249
+ border-radius: var(--_radius);
4250
+ }
4251
+
4252
+ /* Hide chevron — no expand/collapse in icon rail (flyout instead). */
4253
+ :where(ui-nav-group) > :where(details) > :where(summary)::after {
4254
+ display: none;
4255
+ }
4256
+
4257
+ /* Hide vertical connector line and sliding indicator. */
4258
+ :where(ui-nav-group)::after,
4259
+ :where(ui-nav-group)::before {
4260
+ display: none;
4261
+ }
4262
+
4263
+ /* Header collapses to icon-only. font-size: 0 hides text nodes
4264
+ (can't be targeted by CSS). Icon overrides back to normal below. */
4265
+ :where(ui-nav-group-header) {
4266
+ flex: 0 0 auto;
4267
+ font-size: 0;
4268
+ gap: 0;
4269
+ }
4270
+
4271
+ :where(ui-nav-group-header) :where(ui-icon) {
4272
+ font-size: var(--_font-size, 1rem);
4273
+ }
4274
+
4275
+ /* Collapse inter-group spacing in icon rail. */
4276
+ :where(ui-nav-group) + :where(ui-nav-group) {
4277
+ margin-block-start: 0;
4278
+ }
4279
+ }
4280
+
4121
4281
  }
4122
4282
 
4123
4283
  @layer ui {
@@ -4258,27 +4418,15 @@
4258
4418
  }
4259
4419
 
4260
4420
  /* ── 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
4421
 
4265
4422
  :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
4423
  background: var(--_surface);
4277
4424
  border-color: var(--_surface);
4278
4425
  }
4279
4426
 
4280
- :where(ui-radio-group)[intent] :where(ui-radio)[aria-checked="true"]::after {
4427
+ :where(ui-radio)[aria-checked="true"]::after {
4281
4428
  background: var(--_surface-ink);
4429
+ transform: translateY(-50%) scale(1);
4282
4430
  }
4283
4431
 
4284
4432
  /* ── Hover ── */
@@ -4296,12 +4444,6 @@
4296
4444
 
4297
4445
  :where(ui-radio)[aria-checked="true"]:hover::before,
4298
4446
  :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
4447
  background: var(--_surface-hover);
4306
4448
  border-color: var(--_surface-hover);
4307
4449
  }
@@ -4314,11 +4456,6 @@
4314
4456
  }
4315
4457
 
4316
4458
  :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
4459
  background: var(--_surface-active);
4323
4460
  border-color: var(--_surface-active);
4324
4461
  }
@@ -4417,16 +4554,12 @@
4417
4554
  /* 0% = track-height (circle), 100% = full width */
4418
4555
  width: calc(var(--_track-height) + (100% - var(--_track-height)) * var(--_progress));
4419
4556
  border-radius: calc(var(--_track-height) / 2);
4420
- background: var(--accent-surface);
4557
+ background: var(--_surface);
4421
4558
 
4422
4559
  transition:
4423
4560
  background var(--_duration) var(--_easing);
4424
4561
  }
4425
4562
 
4426
- :where(ui-range)[intent]::after {
4427
- background: var(--_surface);
4428
- }
4429
-
4430
4563
  /* ── Thumb ── */
4431
4564
 
4432
4565
  :where(ui-range) > :where(.ui-range-thumb) {
@@ -4438,8 +4571,8 @@
4438
4571
  width: var(--_thumb-size);
4439
4572
  height: var(--_thumb-size);
4440
4573
  border-radius: 50%;
4441
- background: var(--accent-surface-ink);
4442
- border: 2px solid var(--accent-surface);
4574
+ background: var(--_surface-ink);
4575
+ border: 2px solid var(--_surface);
4443
4576
  box-shadow: var(--ui-shadow-sm);
4444
4577
  pointer-events: none;
4445
4578
 
@@ -4449,11 +4582,6 @@
4449
4582
  border-color var(--_duration) var(--_easing);
4450
4583
  }
4451
4584
 
4452
- :where(ui-range)[intent] > :where(.ui-range-thumb) {
4453
- background: var(--_surface-ink);
4454
- border-color: var(--_surface);
4455
- }
4456
-
4457
4585
  /* ── Focus ── */
4458
4586
 
4459
4587
  :where(ui-range):focus-visible > :where(.ui-range-thumb),
@@ -4466,22 +4594,11 @@
4466
4594
 
4467
4595
  :where(ui-range):hover::after,
4468
4596
  :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
4597
  background: var(--_surface-hover);
4475
4598
  }
4476
4599
 
4477
4600
  :where(ui-range):hover > :where(.ui-range-thumb),
4478
4601
  :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
4602
  border-color: var(--_surface-hover);
4486
4603
  box-shadow: var(--ui-shadow-sm), 0 0 0 4px var(--_surface);
4487
4604
  }
@@ -4489,19 +4606,10 @@
4489
4606
  /* ── Active (dragging) ── */
4490
4607
 
4491
4608
  :where(ui-range)[pressed]::after {
4492
- background: var(--accent-surface-active);
4493
- }
4494
-
4495
- :where(ui-range)[intent][pressed]::after {
4496
4609
  background: var(--_surface-active);
4497
4610
  }
4498
4611
 
4499
4612
  :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
4613
  border-color: var(--_surface-active);
4506
4614
  box-shadow: var(--ui-shadow-sm), 0 0 0 6px var(--_surface);
4507
4615
  }
@@ -5056,22 +5164,13 @@
5056
5164
  When an explicit [intent] is set, the intent selector's --_surface wins. */
5057
5165
 
5058
5166
  :where(ui-switch)[aria-checked="true"]::before {
5059
- background: var(--accent-surface);
5060
- border-color: var(--accent-surface);
5167
+ background: var(--_surface);
5168
+ border-color: var(--_surface);
5061
5169
  }
5062
5170
 
5063
5171
  :where(ui-switch)[aria-checked="true"]::after {
5064
5172
  /* Slide thumb to the right via translateX — GPU composited */
5065
5173
  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
5174
  background: var(--_surface-ink);
5076
5175
  }
5077
5176
 
@@ -5090,12 +5189,6 @@
5090
5189
 
5091
5190
  :where(ui-switch)[aria-checked="true"]:hover::before,
5092
5191
  :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
5192
  background: var(--_surface-hover);
5100
5193
  border-color: var(--_surface-hover);
5101
5194
  }
@@ -5108,11 +5201,6 @@
5108
5201
  }
5109
5202
 
5110
5203
  :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
5204
  background: var(--_surface-active);
5117
5205
  border-color: var(--_surface-active);
5118
5206
  }
@@ -6572,7 +6660,7 @@
6572
6660
 
6573
6661
  :where(ui-layout-chat) :where(.layout-resize-handle):hover,
6574
6662
  :where(ui-layout-chat[resizing]) :where(.layout-resize-handle) {
6575
- background: var(--neutral-border-muted);
6663
+ background: var(--_border-muted);
6576
6664
  }
6577
6665
 
6578
6666
  }
@@ -6634,7 +6722,7 @@
6634
6722
 
6635
6723
  :where(ui-layout-inspector) :where(.layout-resize-handle):hover,
6636
6724
  :where(ui-layout-inspector[resizing]) :where(.layout-resize-handle) {
6637
- background: var(--neutral-border-muted);
6725
+ background: var(--_border-muted);
6638
6726
  }
6639
6727
 
6640
6728
  }
@@ -6661,16 +6749,16 @@
6661
6749
  content: '';
6662
6750
  width: var(--ui-layout-sidebar-width);
6663
6751
  flex-shrink: 0;
6664
- background: var(--neutral-body);
6665
- border-right: 1px solid var(--neutral-border-muted);
6752
+ background: var(--_body);
6753
+ border-right: 1px solid var(--_border-muted);
6666
6754
  }
6667
6755
 
6668
6756
  :where(ui-layout-sidebar):not([data-ready])[collapsed]::before {
6669
6757
  content: '';
6670
6758
  width: 48px;
6671
6759
  flex-shrink: 0;
6672
- background: var(--neutral-body);
6673
- border-right: 1px solid var(--neutral-border-muted);
6760
+ background: var(--_body);
6761
+ border-right: 1px solid var(--_border-muted);
6674
6762
  }
6675
6763
 
6676
6764
  /* ── Content Column ── */
@@ -6686,16 +6774,23 @@
6686
6774
  }
6687
6775
 
6688
6776
  /* ── Sidebar Aside ── */
6777
+ /* WHY: position: relative creates the containing block for absolute
6778
+ header/footer overlays. Content fills the full height and scrolls
6779
+ underneath the pinned header/footer. */
6689
6780
 
6690
6781
  :where(ui-layout-sidebar) > :where([slot="sidebar"]) {
6782
+ --_sidebar-header-height: 0px;
6783
+ --_sidebar-footer-height: 0px;
6784
+
6785
+ container-name: sidebar;
6786
+ container-type: inline-size;
6787
+ position: sticky;
6788
+ top: 0;
6691
6789
  width: var(--ui-layout-sidebar-width);
6692
6790
  min-width: 160px;
6693
6791
  max-width: 400px;
6694
6792
  height: 100dvh;
6695
- position: sticky;
6696
- top: 0;
6697
- display: flex;
6698
- flex-direction: column;
6793
+ display: block;
6699
6794
  transition: width var(--_duration) var(--_easing), min-width var(--_duration) var(--_easing);
6700
6795
  overflow: hidden;
6701
6796
  z-index: 10;
@@ -6707,7 +6802,9 @@
6707
6802
  }
6708
6803
 
6709
6804
  /* ── Collapsed Icon Rail ── */
6710
- /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding. */
6805
+ /* WHY: Collapsed = icon rail, not fully hidden. 48px fits icons (1rem) + padding.
6806
+ [collapsed] drives the aside to 48px which triggers @container sidebar queries
6807
+ in this file and in child component CSS (ui-nav.css, etc.). */
6711
6808
 
6712
6809
  :where(ui-layout-sidebar)[collapsed] > :where([slot="sidebar"]) {
6713
6810
  width: 48px;
@@ -6728,11 +6825,7 @@
6728
6825
 
6729
6826
  :where(ui-layout-sidebar) :where(.layout-resize-handle):hover,
6730
6827
  :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;
6828
+ background: var(--_border-muted);
6736
6829
  }
6737
6830
 
6738
6831
  /* WHY: When collapsed to icon rail, the sidebar edge meets content — drop left padding. */
@@ -6746,47 +6839,88 @@
6746
6839
  }
6747
6840
 
6748
6841
  /* ── Sidebar Header ── */
6842
+ /* WHY: Absolute-positioned overlay pinned to top of aside. Content scrolls
6843
+ underneath it. z-index: 2 sits above content (z-index: 0). */
6749
6844
 
6750
6845
  :where(ui-layout-sidebar-header) {
6846
+ position: absolute;
6847
+ top: 0;
6848
+ left: 0;
6849
+ right: 0;
6850
+ z-index: 2;
6751
6851
  display: flex;
6752
- align-items: center;
6753
- min-height: var(--ui-layout-bar-height);
6754
- gap: calc(var(--_space) * 2);
6755
- padding-block: var(--_space);
6756
- padding-inline: calc(var(--_space-k) * var(--_space));
6757
- flex-shrink: 0;
6852
+ flex-direction: column;
6758
6853
  }
6759
6854
 
6760
6855
  /* ── Sidebar Content ── */
6856
+ /* WHY: Full height of the aside, scrollable. Padding-block offsets keep
6857
+ content from starting behind header / ending behind footer.
6858
+ Fade-out alpha mask dissolves content edges under the overlays. */
6761
6859
 
6762
6860
  :where(ui-layout-sidebar-content) {
6763
6861
  display: flex;
6764
6862
  flex-direction: column;
6765
- gap: calc(var(--_space) * 2);
6766
- padding-block: var(--_space);
6767
- padding-inline: calc(var(--_space-k) * var(--_space));
6768
- flex: 1;
6769
- min-height: 0;
6863
+ padding-block-start: var(--_sidebar-header-height);
6864
+ padding-block-end: var(--_sidebar-footer-height);
6770
6865
  width: 100%;
6866
+ height: 100%;
6867
+ overflow-y: auto;
6868
+ scrollbar-width: none;
6869
+ }
6870
+
6871
+ /* WHY: When header is present, fade top edge. Content dissolves as it
6872
+ scrolls under the header — transparent at top, fully visible by 1.25×
6873
+ the header height. No opaque header background needed. */
6874
+ :where(ui-layout-sidebar-content)[data-has-header] {
6875
+ mask-image: linear-gradient(
6876
+ to bottom,
6877
+ transparent 0,
6878
+ black calc(var(--_sidebar-header-height) * 1.25),
6879
+ black 100%
6880
+ );
6881
+ }
6882
+
6883
+ /* WHY: When footer is present, fade bottom edge. */
6884
+ :where(ui-layout-sidebar-content)[data-has-footer] {
6885
+ mask-image: linear-gradient(
6886
+ to bottom,
6887
+ black 0,
6888
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
6889
+ transparent 100%
6890
+ );
6891
+ }
6892
+
6893
+ /* WHY: When both header AND footer are present, fade both edges. */
6894
+ :where(ui-layout-sidebar-content)[data-has-header][data-has-footer] {
6895
+ mask-image: linear-gradient(
6896
+ to bottom,
6897
+ transparent 0,
6898
+ black calc(var(--_sidebar-header-height) * 1.25),
6899
+ black calc(100% - var(--_sidebar-footer-height) * 1.25),
6900
+ transparent 100%
6901
+ );
6771
6902
  }
6772
6903
 
6773
6904
  /* ── Sidebar Footer ── */
6905
+ /* WHY: Absolute-positioned overlay pinned to bottom of aside. */
6774
6906
 
6775
6907
  :where(ui-layout-sidebar-footer) {
6908
+ position: absolute;
6909
+ bottom: 0;
6910
+ left: 0;
6911
+ right: 0;
6912
+ z-index: 2;
6776
6913
  display: flex;
6777
- align-items: center;
6778
- min-height: var(--ui-layout-bar-height);
6779
- gap: calc(var(--_space) * 2);
6780
- padding-block: var(--_space);
6781
- padding-inline: calc(var(--_space-k) * var(--_space));
6782
- flex-shrink: 0;
6914
+ flex-direction: column;
6783
6915
  }
6784
6916
 
6785
- /* ── Sidebar Trigger ── */
6786
- /* WHY: Menu trigger for sidebar header/footer. Styled like ui-nav-group-header:
6787
- bold text, icon support, hover color. Opens a popover on click. */
6917
+ /* ── Sidebar Item ── */
6918
+ /* WHY: Universal sidebar row element. Provides inline padding for any content
6919
+ (buttons, links, icons). When a child ui-listbox[popover] is present, JS
6920
+ wires PopoverController for click-to-toggle menu behavior.
6921
+ Absorbs the old ui-layout-sidebar-trigger role — one element for all rows. */
6788
6922
 
6789
- :where(ui-layout-sidebar-trigger) {
6923
+ :where(ui-layout-sidebar-item) {
6790
6924
  display: flex;
6791
6925
  align-items: center;
6792
6926
  flex: 1;
@@ -6802,31 +6936,47 @@
6802
6936
  user-select: none;
6803
6937
  border: none;
6804
6938
  background: none;
6805
- padding: var(--_space);
6939
+ padding-block: var(--_space);
6940
+ padding-inline: calc(var(--_space-k) * var(--_space));
6806
6941
  border-radius: var(--_radius);
6807
6942
  transition: color var(--_duration) var(--_easing);
6808
6943
  }
6809
6944
 
6810
- :where(ui-layout-sidebar-trigger):hover,
6811
- :where(ui-layout-sidebar-trigger)[force-hover] {
6945
+ /* WHY: Items in header/footer match the breadcrumb bar height so the
6946
+ first sidebar row aligns horizontally with the breadcrumb. */
6947
+ :where(ui-layout-sidebar-header) > :where(ui-layout-sidebar-item),
6948
+ :where(ui-layout-sidebar-footer) > :where(ui-layout-sidebar-item) {
6949
+ min-height: var(--ui-layout-bar-height);
6950
+ }
6951
+
6952
+ :where(ui-layout-sidebar-item):hover,
6953
+ :where(ui-layout-sidebar-item)[force-hover] {
6812
6954
  color: var(--_ink-strong);
6813
6955
  }
6814
6956
 
6815
- :where(ui-layout-sidebar-trigger):focus-visible,
6816
- :where(ui-layout-sidebar-trigger)[force-focus-visible] {
6957
+ :where(ui-layout-sidebar-item):focus-visible,
6958
+ :where(ui-layout-sidebar-item)[force-focus-visible] {
6817
6959
  outline: 2px solid var(--ui-focus-ring);
6818
6960
  outline-offset: -2px;
6819
6961
  }
6820
6962
 
6821
6963
  /* WHY: Trailing caret pushes to end — same pattern as button justify="spread". */
6822
- :where(ui-layout-sidebar-trigger) > :where([slot="trailing"]) {
6964
+ :where(ui-layout-sidebar-item) > :where([slot="trailing"]) {
6823
6965
  margin-inline-start: auto;
6824
6966
  flex-shrink: 0;
6825
6967
  color: var(--_ink-muted);
6826
6968
  }
6827
6969
 
6970
+ /* WHY: When the item wraps a sub-component (e.g. ui-button) that provides
6971
+ its own icon, hide [slot="icon"] in expanded mode — it only serves as the
6972
+ bare collapsed-rail representation. Items without sub-components (just
6973
+ icon + label + trailing) keep [slot="icon"] visible in both modes. */
6974
+ :where(ui-layout-sidebar-item:has(> ui-button)) > :where([slot="icon"]) {
6975
+ display: none;
6976
+ }
6977
+
6828
6978
  /* WHY: Title text truncates when sidebar narrows during resize. */
6829
- :where(ui-layout-sidebar-trigger) > :where([slot="label"]) {
6979
+ :where(ui-layout-sidebar-item) > :where([slot="label"]) {
6830
6980
  flex: 1;
6831
6981
  min-width: 0;
6832
6982
  white-space: nowrap;
@@ -6834,25 +6984,67 @@
6834
6984
  text-overflow: ellipsis;
6835
6985
  }
6836
6986
 
6837
- /* ── Trigger Popover ── */
6838
- /* WHY: Popover opens to the right of the sidebar trigger.
6987
+ /* ── Item Popover ── */
6988
+ /* WHY: Popover opens to the right of the sidebar item.
6839
6989
  Default: top-aligned, grows downward (span-block-end).
6840
6990
  Flip: bottom-aligned, grows upward (span-block-start). */
6841
6991
 
6842
- :where(ui-layout-sidebar-trigger) > :where(ui-listbox[popover]) {
6992
+ :where(ui-layout-sidebar-item) > :where(ui-listbox[popover]) {
6843
6993
  position: fixed;
6844
6994
  position-area: inline-end span-block-end;
6845
- position-try-fallbacks: --flip-up;
6995
+ position-try-fallbacks: --sidebar-item-flip-up;
6846
6996
  margin: 0 0 0 var(--ui-popover-gap);
6847
6997
  min-width: 200px;
6848
6998
  max-height: var(--ui-popover-max-height);
6849
6999
  overflow-y: auto;
6850
7000
  }
6851
7001
 
6852
- @position-try --flip-up {
7002
+ @position-try --sidebar-item-flip-up {
6853
7003
  position-area: inline-end span-block-start;
6854
7004
  }
6855
7005
 
7006
+ /* ── Container Query: Collapsed Sidebar ── */
7007
+ /* WHY: Each component owns its own collapsed behavior via @container.
7008
+ The aside is the container (container-name: sidebar). When it shrinks
7009
+ to 48px (icon rail), components respond to the width, not to [collapsed].
7010
+ Threshold 80px: collapsed = 48px, min expanded = 160px. */
7011
+
7012
+ @container sidebar (max-width: 80px) {
7013
+
7014
+ /* Resize handle — no dragging in icon rail */
7015
+ :where(.layout-resize-handle) {
7016
+ display: none;
7017
+ }
7018
+
7019
+ /* Header/footer — center content horizontally */
7020
+ :where(ui-layout-sidebar-header),
7021
+ :where(ui-layout-sidebar-footer) {
7022
+ align-items: center;
7023
+ }
7024
+
7025
+ /* Content — center items in the 48px rail */
7026
+ :where(ui-layout-sidebar-content) {
7027
+ align-items: center;
7028
+ }
7029
+
7030
+ /* Item — shrink to icon-only. Show [slot="icon"], hide everything else.
7031
+ WHY: Sidebar items can contain complex children (buttons with slots,
7032
+ links, etc.). Rather than overriding sub-component styles, the item
7033
+ provides its own bare icon via [slot="icon"] for the collapsed rail. */
7034
+ :where(ui-layout-sidebar-item) {
7035
+ flex: 0 0 auto;
7036
+ padding-inline: var(--_space);
7037
+ }
7038
+
7039
+ :where(ui-layout-sidebar-item) > :where([slot="icon"]) {
7040
+ display: block;
7041
+ }
7042
+
7043
+ :where(ui-layout-sidebar-item) > :where(:not([slot="icon"]):not(ui-listbox[popover]):not(.nav-group-flyout)) {
7044
+ display: none;
7045
+ }
7046
+ }
7047
+
6856
7048
  }
6857
7049
 
6858
7050
  @layer ui {