@sableui/react 0.17.0 → 0.19.0

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 (154) hide show
  1. package/AGENTS.md +10 -3
  2. package/CHANGELOG.md +235 -0
  3. package/README.md +11 -4
  4. package/bin/sable.mjs +266 -30
  5. package/dist/{_contract-CDa9-NdB.d.ts → _contract-DlklV0Im.d.ts} +2 -2
  6. package/dist/activity-DT8VK9fx.d.ts +25 -0
  7. package/dist/adapters/github.d.ts +41 -0
  8. package/dist/adapters/github.js +24 -0
  9. package/dist/blocks/index.d.ts +1 -1
  10. package/dist/components/activity-heatmap/activity-heatmap.js +420 -0
  11. package/dist/components/activity-heatmap/activity-heatmap.meta.js +142 -0
  12. package/dist/components/activity-heatmap/index.js +9 -0
  13. package/dist/components/avatar/avatar.js +16 -7
  14. package/dist/components/avatar/avatar.meta.js +43 -10
  15. package/dist/components/avatar/avatar.variants.js +26 -2
  16. package/dist/components/code-block/code-block.js +5 -3
  17. package/dist/components/code-block/code-block.meta.js +6 -0
  18. package/dist/components/collapsible/collapsible.js +6 -5
  19. package/dist/components/collapsible/collapsible.meta.js +14 -5
  20. package/dist/components/data-table/data-table.js +132 -45
  21. package/dist/components/data-table/data-table.meta.js +63 -4
  22. package/dist/components/date-picker/date-picker.js +13 -6
  23. package/dist/components/date-picker/date-picker.meta.js +22 -0
  24. package/dist/components/dialog/dialog.js +25 -5
  25. package/dist/components/dialog/dialog.meta.js +26 -5
  26. package/dist/components/media-image/media-image.js +3 -1
  27. package/dist/components/media-image/media-image.meta.js +15 -1
  28. package/dist/components/media-tile/media-tile.js +13 -1
  29. package/dist/components/media-tile/media-tile.meta.js +10 -0
  30. package/dist/components/menu/menu.js +2 -0
  31. package/dist/components/menu/menu.meta.js +2 -2
  32. package/dist/components/page-header/page-header.js +9 -4
  33. package/dist/components/page-header/page-header.meta.js +26 -1
  34. package/dist/components/panel-group/panel-group.js +17 -1
  35. package/dist/components/panel-group/panel-group.meta.js +43 -3
  36. package/dist/components/pill/pill.meta.js +15 -6
  37. package/dist/components/pill/pill.variants.js +1 -0
  38. package/dist/components/search-input/search-input.js +30 -5
  39. package/dist/components/search-input/search-input.meta.js +29 -4
  40. package/dist/components/section-header/section-header.js +27 -17
  41. package/dist/components/section-header/section-header.meta.js +27 -5
  42. package/dist/components/sheet/sheet.js +15 -3
  43. package/dist/components/sheet/sheet.meta.js +2 -2
  44. package/dist/components/side-nav/side-nav.variants.js +8 -3
  45. package/dist/components/sidebar/index.js +4 -0
  46. package/dist/components/sidebar/sidebar.js +90 -11
  47. package/dist/components/sidebar/sidebar.meta.js +38 -4
  48. package/dist/components/table/table.js +7 -3
  49. package/dist/components/tabs/tabs.js +10 -2
  50. package/dist/components/tabs/tabs.meta.js +4 -0
  51. package/dist/components/workspace/workspace.js +13 -3
  52. package/dist/components/workspace/workspace.meta.js +109 -1
  53. package/dist/file-hashes.json +94 -80
  54. package/dist/foundations/typography.js +13 -0
  55. package/dist/icon/Icon.js +21 -2
  56. package/dist/icon/index.d.ts +36 -1
  57. package/dist/icon/index.js +5 -0
  58. package/dist/icon/names.js +240 -0
  59. package/dist/icon-sets/reicon.js +41 -1
  60. package/dist/index.d.ts +540 -75
  61. package/dist/index.js +18 -1
  62. package/dist/layout/AspectBox.js +2 -5
  63. package/dist/layout/Bleed.js +10 -6
  64. package/dist/layout/Center.js +11 -3
  65. package/dist/layout/Columns.js +6 -3
  66. package/dist/layout/Grid.js +14 -6
  67. package/dist/layout/GridOverlay.js +7 -7
  68. package/dist/layout/Region.js +18 -3
  69. package/dist/layout/Row.js +27 -20
  70. package/dist/layout/Spacer.js +13 -2
  71. package/dist/layout/SplitPane.js +1 -1
  72. package/dist/layout/Stack.js +25 -18
  73. package/dist/layout/index.d.ts +57 -6
  74. package/dist/lib/chart/activity.js +32 -0
  75. package/dist/lib/chart/calendar.js +67 -0
  76. package/dist/lib/chart/chart-tooltip.js +2 -2
  77. package/dist/lib/chart/heat.js +55 -0
  78. package/dist/lib/cn.js +1 -0
  79. package/dist/lib/focus-return.js +53 -0
  80. package/dist/lib/taxonomy.js +1 -0
  81. package/dist/lib/use-media-query.js +41 -0
  82. package/dist/{meta-DX5r5wQL.d.ts → meta-Dfm9AExe.d.ts} +6 -0
  83. package/dist/patterns/dashboard-page.js +23 -17
  84. package/dist/patterns/index.d.ts +7 -2
  85. package/dist/patterns/index.js +5 -1
  86. package/dist/patterns/tracker-page.js +392 -0
  87. package/dist/patterns/tracker-page.meta.js +40 -0
  88. package/dist/presets/index.d.ts +1 -1
  89. package/dist/styles.css +301 -188
  90. package/dist/{theme-provider-BydfM5Z0.d.ts → theme-provider-BUBealk_.d.ts} +1 -1
  91. package/dist/themes/_contract.js +9 -1
  92. package/dist/themes/atelier/index.d.ts +1 -1
  93. package/dist/themes/ember/index.d.ts +1 -1
  94. package/dist/themes/index.d.ts +2 -2
  95. package/dist/themes/meridian/index.d.ts +1 -1
  96. package/dist/themes/poppy/index.d.ts +1 -1
  97. package/dist/themes/prism/index.d.ts +1 -1
  98. package/dist/themes/sable/index.d.ts +1 -1
  99. package/dist/themes/verdant/index.d.ts +1 -1
  100. package/llms/components/activity-heatmap.md +133 -0
  101. package/llms/components/avatar.md +32 -9
  102. package/llms/components/code-block.md +1 -0
  103. package/llms/components/collapsible.md +12 -4
  104. package/llms/components/data-table.md +40 -3
  105. package/llms/components/date-picker.md +15 -1
  106. package/llms/components/dialog.md +34 -3
  107. package/llms/components/media-image.md +12 -0
  108. package/llms/components/media-tile.md +21 -0
  109. package/llms/components/menu.md +1 -1
  110. package/llms/components/page-header.md +16 -1
  111. package/llms/components/panel-group.md +34 -2
  112. package/llms/components/pill.md +24 -5
  113. package/llms/components/search-input.md +37 -4
  114. package/llms/components/section-header.md +28 -4
  115. package/llms/components/sheet.md +2 -2
  116. package/llms/components/sidebar.md +17 -4
  117. package/llms/components/tabs.md +1 -0
  118. package/llms/components/workspace.md +81 -1
  119. package/llms/foundations/typography.md +4 -1
  120. package/llms/layout/aspect-box.md +1 -1
  121. package/llms/layout/bleed.md +1 -1
  122. package/llms/layout/center.md +1 -1
  123. package/llms/layout/columns.md +1 -1
  124. package/llms/layout/grid.md +1 -1
  125. package/llms/layout/region.md +1 -1
  126. package/llms/layout/row.md +9 -1
  127. package/llms/layout/spacer.md +1 -1
  128. package/llms/layout/split-pane.md +1 -1
  129. package/llms/layout/stack.md +1 -1
  130. package/llms/patterns/dashboard-page.md +18 -15
  131. package/llms/patterns/tracker-page.md +450 -0
  132. package/llms/reference/token-intent.md +3 -1
  133. package/llms/reference/utilities.md +4 -1
  134. package/llms/tasks/auditing-an-app.md +35 -1
  135. package/llms/tasks/building-a-page.md +3 -2
  136. package/llms/tasks/building-a-tool.md +101 -5
  137. package/llms/tasks/building-with-sable.md +11 -3
  138. package/llms/tasks/creating-a-preset.md +9 -0
  139. package/llms/tasks/getting-started.md +10 -3
  140. package/llms/tasks/using-presets.md +1 -2
  141. package/llms-full.txt +1215 -89
  142. package/llms.txt +11 -9
  143. package/package.json +7 -1
  144. package/registry.json +1021 -70
  145. package/scripts/adoption.mjs +252 -0
  146. package/scripts/agent-setup.mjs +110 -18
  147. package/scripts/app-css.mjs +129 -0
  148. package/scripts/grade.mjs +178 -2
  149. package/scripts/lint-rules.mjs +126 -0
  150. package/scripts/wiring.mjs +2 -1
  151. package/skills/entry/SKILL.md +1 -1
  152. package/skills/sable/SKILL.md +64 -6
  153. package/skills/sable/agents/openai.yaml +1 -1
  154. package/targets/ios.json +9 -0
package/dist/styles.css CHANGED
@@ -203,6 +203,17 @@
203
203
  See DESIGN.md → Layering.
204
204
  ========================================================================== */
205
205
 
206
+ /* The mobile bar height BottomNav, AppHeader, AppShell's bottom padding and
207
+ Workspace's bottomNav row share. In the base layer so a theme or an app that
208
+ declares its own (unlayered) wins; declared here because only the Sable
209
+ theme did, and AppShell reads it with no fallback, so under every other
210
+ theme its calc() was invalid and the padding under a BottomNav vanished. */
211
+ @layer base {
212
+ :root {
213
+ --nav-height: 56px;
214
+ }
215
+ }
216
+
206
217
  :root {
207
218
  --z-sticky: 30; /* sticky in-content chrome: table headers, sticky banners */
208
219
  --z-nav: 40; /* app chrome: AppHeader, Sidebar, BottomNav, status banners */
@@ -298,6 +309,12 @@
298
309
  Routed through a --theme-* hook rather than hardcoded, so a theme can still
299
310
  choose its own button shape without fighting this scope. */
300
311
  --button-radius: var(--theme-button-radius, 9999px);
312
+
313
+ /* Table geometry. Unset here so Table's own fallbacks (h-10 head, py-2.5
314
+ cells, on the spacing scale) apply — and declared, for the same symmetry:
315
+ a comfortable form nested in a compact tool gets the page table back. */
316
+ --table-head-h: initial;
317
+ --table-cell-py: initial;
301
318
  }
302
319
 
303
320
  /* Workspace register — measured from the Figma and Paper editors (2026-07-26),
@@ -337,6 +354,30 @@
337
354
  --tree-rail: 8px;
338
355
  --tree-column: 18px;
339
356
  --tree-indent: 18px;
357
+
358
+ /* Table geometry: a head row on the sm rung and one baseline step of block
359
+ padding, so a 16px ui line makes a 32px row — Linear's list row, and the
360
+ size a table in a tool panel was being forced to by hand (thirteen
361
+ consumer rules overriding th/td heights, padding and size in one app). */
362
+ --table-head-h: var(--control-sm);
363
+ --table-cell-py: calc(var(--spacing) * 2);
364
+ }
365
+
366
+ /* A finger is not a denser pointer. The compact ladder is measured from mouse-
367
+ driven editors (Figma, Paper), where a 24px target is aimed with a cursor; on
368
+ a phone or tablet the same tool chrome needs the page register's targets, so
369
+ on a coarse pointer compact keeps its type, radius and panel geometry and
370
+ takes the comfortable ladder's heights. Without this, every Workspace app
371
+ re-tuned `--control-*` by hand below some breakpoint — and a breakpoint is
372
+ the wrong question: a touch laptop is wide, a phone with a mouse is narrow. */
373
+ @media (pointer: coarse) {
374
+ [data-density="compact"] {
375
+ --control-xs: 28px;
376
+ --control-sm: 32px;
377
+ --control-md: 40px;
378
+ --control-cta: 44px;
379
+ --control-lg: 48px;
380
+ }
340
381
  }
341
382
 
342
383
  /* ==========================================================================
@@ -348,6 +389,13 @@
348
389
  Paper's entire panel ladder spans 249 → 242 → 238 (3-7 RGB units) and lets a
349
390
  1px low-contrast edge do the structural work. In this register hairlines carry
350
391
  structure INSTEAD of tonal steps — the reverse of the page register's rule.
392
+
393
+ Every mix here is `in oklab`, never `in oklch`. These blend a near-neutral
394
+ surface with its ink, so there is no hue worth interpolating, and oklch
395
+ loses it: measured in Chrome, `color-mix(in oklch, #141519 97%, #e4e5ea)`
396
+ resolves to `oklch(0.218 0.008 none)` and paints a missing hue as 0°, a
397
+ brown cast on a cool graphite preset's canvas. Bundled themes write oklch
398
+ and never showed it; presets and apps write hex.
351
399
  ========================================================================== */
352
400
 
353
401
  /* Declared on every element that can re-declare the base colors, not just
@@ -361,32 +409,32 @@
361
409
  /* The work surface. NOT `background`: the artboard is the subject, not the
362
410
  page behind the UI. Pulled a touch away from the page so docked panels can
363
411
  sit above it without a border. */
364
- --canvas: var(--theme-canvas, color-mix(in oklch, var(--background) 97%, var(--foreground)));
412
+ --canvas: var(--theme-canvas, color-mix(in oklab, var(--background) 97%, var(--foreground)));
365
413
  --canvas-foreground: var(--theme-canvas-foreground, var(--foreground));
366
414
 
367
415
  /* Docked chrome, one compressed step off the canvas. */
368
416
  --panel: var(--theme-panel, var(--background));
369
417
  --panel-foreground: var(--theme-panel-foreground, var(--foreground));
370
- --panel-raised: var(--theme-panel-raised, color-mix(in oklch, var(--background) 94%, var(--foreground)));
418
+ --panel-raised: var(--theme-panel-raised, color-mix(in oklab, var(--background) 94%, var(--foreground)));
371
419
 
372
420
  /* A compact control docked in a panel: the xs field rung and a field-style
373
421
  button. Mixed from the panel it sits in, a further step under the pointer.
374
422
  It used to borrow the canvas tone (3% ink), which measured 1.04:1 against
375
423
  a dark panel: the field all but vanished. 9% is what a shipped inspector
376
424
  settled on (1.15:1 dark, 1.25:1 light). */
377
- --panel-field: var(--theme-panel-field, color-mix(in oklch, var(--panel) 91%, var(--panel-foreground)));
378
- --panel-field-hover: var(--theme-panel-field-hover, color-mix(in oklch, var(--panel) 88%, var(--panel-foreground)));
425
+ --panel-field: var(--theme-panel-field, color-mix(in oklab, var(--panel) 91%, var(--panel-foreground)));
426
+ --panel-field-hover: var(--theme-panel-field-hover, color-mix(in oklab, var(--panel) 88%, var(--panel-foreground)));
379
427
 
380
428
  /* The structural edge. Alpha-based so it holds over any surface beneath it. */
381
- --hairline: var(--theme-hairline, color-mix(in oklch, transparent 93%, var(--foreground)));
382
- --hairline-strong: var(--theme-hairline-strong, color-mix(in oklch, transparent 86%, var(--foreground)));
429
+ --hairline: var(--theme-hairline, color-mix(in oklab, transparent 93%, var(--foreground)));
430
+ --hairline-strong: var(--theme-hairline-strong, color-mix(in oklab, transparent 86%, var(--foreground)));
383
431
 
384
432
  /* The edge of a FLOATING surface (popover, menu, select, tooltip, a floating
385
433
  panel). Lighter than `--border` on purpose: the shadow carries the
386
434
  elevation, and a full hairline plus a shadow stacks into a dark outline.
387
435
  Alpha-based, so it defines the shape on a same-colour page in light mode
388
436
  and reads as a faint lit edge in dark mode, where shadows barely show. */
389
- --border-floating: var(--theme-border-floating, color-mix(in oklch, transparent 92%, var(--foreground)));
437
+ --border-floating: var(--theme-border-floating, color-mix(in oklab, transparent 92%, var(--foreground)));
390
438
 
391
439
  /* The text field: its fill and its outline. Outlined on the card by default;
392
440
  a theme fills its fields instead with `--theme-field: var(--muted)` and
@@ -399,7 +447,7 @@
399
447
  the gap around the raised main panel. One step off the rail toward its ink,
400
448
  because most themes paint the rail the page's colour and the panel would
401
449
  otherwise sit on its own colour. */
402
- --shell-ground: var(--theme-shell-ground, color-mix(in oklch, var(--sidebar) 97%, var(--sidebar-foreground)));
450
+ --shell-ground: var(--theme-shell-ground, color-mix(in oklab, var(--sidebar) 97%, var(--sidebar-foreground)));
403
451
 
404
452
  /* Status and tone colours at graphic strength: a meter's fill, a segmented
405
453
  bar, a status dot, a chart series, a soft tint. The label fill is dark
@@ -493,7 +541,7 @@
493
541
  2px,
494
542
  round(
495
543
  nearest,
496
- calc(var(--type-body-md-size, 0.9375rem) * var(--type-body-md-leading, 1.55) * var(--leading-scale) / var(--baseline-split)),
544
+ calc(var(--type-body-md-size, 0.9375rem) * var(--type-base, 16) / 16 * var(--type-body-md-leading, 1.55) * var(--leading-scale) / var(--baseline-split)),
497
545
  1px
498
546
  )
499
547
  );
@@ -527,39 +575,62 @@
527
575
  --leading-scale, so Airy opens the text itself; display, heading and label
528
576
  leading is a shape the theme chose and only snaps. The `ui-*` tier is
529
577
  already whole px on its own 4px grid (the workspace register).
578
+
579
+ ── The base size: one number rebuilds the scale ───────────────────────────
580
+ Every page tier is authored on a 16px base (1rem is title-lg; body-md is
581
+ 15/16 of it). `--type-base` is that base as a unitless px number, so
582
+ `--type-base: 14` rebuilds the whole scale at 14: each tier keeps its ratio
583
+ to the base, the headings stay a heading's distance from the body, and the
584
+ baseline step (and with it `--spacing`) follows the body down. It is the
585
+ standard "set the base font size" knob, scoped to type and rhythm: unlike
586
+ changing the root font-size, it leaves the control ladder and every rem the
587
+ app wrote itself where they were.
588
+
589
+ Applied where the size is USED — `calc(var(--type-<tier>-size, <rem>) *
590
+ var(--type-base, 16) / 16)` — not where a theme declares it, so it scales a
591
+ theme's own tier values and a consumer's per-tier overrides alike, and works
592
+ on any element: set it on :root, a `[data-sable-theme]` scope or a preset
593
+ (`tokens.shared["type-base"]`) and the spacing follows too; on any other
594
+ subtree the type rescales and the rhythm stays the page's. Unitless, because
595
+ CSS cannot divide one length by another: `14`, never `14px`.
596
+
597
+ The `ui-*` tiers do NOT take it. They are absolute px measured from tool
598
+ chrome (Figma, Paper) and sit on their own 4px grid; the compact register's
599
+ `min(page, ui)` remaps already follow a smaller base down, so a 13px-base app
600
+ still never gets chrome type larger than its page type.
530
601
  ========================================================================== */
531
602
 
532
603
  @utility text-display-4xl {
533
604
  font-family: var(--font-display, var(--font-sans));
534
- font-size: var(--type-display-4xl-size, clamp(4.5rem, 12vw, 12rem));
605
+ font-size: calc(var(--type-display-4xl-size, clamp(4.5rem, 12vw, 12rem)) * var(--type-base, 16) / 16);
535
606
  font-weight: var(--type-display-4xl-weight, 800);
536
607
  line-height: round(nearest, calc(var(--type-display-4xl-leading, 0.9) * 1em), var(--baseline-step, 0.25rem));
537
608
  letter-spacing: var(--type-display-4xl-tracking, -0.05em);
538
609
  }
539
610
  @utility text-display-3xl {
540
611
  font-family: var(--font-display, var(--font-sans));
541
- font-size: var(--type-display-3xl-size, 6rem);
612
+ font-size: calc(var(--type-display-3xl-size, 6rem) * var(--type-base, 16) / 16);
542
613
  font-weight: var(--type-display-3xl-weight, 700);
543
614
  line-height: round(nearest, calc(var(--type-display-3xl-leading, 0.95) * 1em), var(--baseline-step, 0.25rem));
544
615
  letter-spacing: var(--type-display-3xl-tracking, -0.045em);
545
616
  }
546
617
  @utility text-display-2xl {
547
618
  font-family: var(--font-display, var(--font-sans));
548
- font-size: var(--type-display-2xl-size, 4.5rem);
619
+ font-size: calc(var(--type-display-2xl-size, 4.5rem) * var(--type-base, 16) / 16);
549
620
  font-weight: var(--type-display-2xl-weight, 700);
550
621
  line-height: round(nearest, calc(var(--type-display-2xl-leading, 1) * 1em), var(--baseline-step, 0.25rem));
551
622
  letter-spacing: var(--type-display-2xl-tracking, -0.04em);
552
623
  }
553
624
  @utility text-display-xl {
554
625
  font-family: var(--font-display, var(--font-sans));
555
- font-size: var(--type-display-xl-size, 3.75rem);
626
+ font-size: calc(var(--type-display-xl-size, 3.75rem) * var(--type-base, 16) / 16);
556
627
  font-weight: var(--type-display-xl-weight, 700);
557
628
  line-height: round(nearest, calc(var(--type-display-xl-leading, 1.05) * 1em), var(--baseline-step, 0.25rem));
558
629
  letter-spacing: var(--type-display-xl-tracking, -0.035em);
559
630
  }
560
631
  @utility text-display-lg {
561
632
  font-family: var(--font-display, var(--font-sans));
562
- font-size: var(--type-display-lg-size, 3rem);
633
+ font-size: calc(var(--type-display-lg-size, 3rem) * var(--type-base, 16) / 16);
563
634
  font-weight: var(--type-display-lg-weight, 700);
564
635
  line-height: round(nearest, calc(var(--type-display-lg-leading, 1.1) * 1em), var(--baseline-step, 0.25rem));
565
636
  letter-spacing: var(--type-display-lg-tracking, -0.03em);
@@ -567,28 +638,28 @@
567
638
 
568
639
  @utility text-heading-1 {
569
640
  font-family: var(--font-display, var(--font-sans));
570
- font-size: var(--type-heading-1-size, 2.25rem);
641
+ font-size: calc(var(--type-heading-1-size, 2.25rem) * var(--type-base, 16) / 16);
571
642
  font-weight: var(--type-heading-weight, 600);
572
643
  line-height: round(nearest, calc(var(--type-heading-1-leading, 1.15) * 1em), var(--baseline-step, 0.25rem));
573
644
  letter-spacing: var(--type-heading-1-tracking, -0.025em);
574
645
  }
575
646
  @utility text-heading-2 {
576
647
  font-family: var(--font-display, var(--font-sans));
577
- font-size: var(--type-heading-2-size, 1.75rem);
648
+ font-size: calc(var(--type-heading-2-size, 1.75rem) * var(--type-base, 16) / 16);
578
649
  font-weight: var(--type-heading-weight, 600);
579
650
  line-height: round(nearest, calc(var(--type-heading-2-leading, 1.2) * 1em), var(--baseline-step, 0.25rem));
580
651
  letter-spacing: var(--type-heading-2-tracking, -0.02em);
581
652
  }
582
653
  @utility text-heading-3 {
583
654
  font-family: var(--font-display, var(--font-sans));
584
- font-size: var(--type-heading-3-size, 1.375rem);
655
+ font-size: calc(var(--type-heading-3-size, 1.375rem) * var(--type-base, 16) / 16);
585
656
  font-weight: var(--type-heading-weight, 600);
586
657
  line-height: round(nearest, calc(var(--type-heading-3-leading, 1.25) * 1em), var(--baseline-step, 0.25rem));
587
658
  letter-spacing: var(--type-heading-3-tracking, -0.015em);
588
659
  }
589
660
  @utility text-heading-4 {
590
661
  font-family: var(--font-display, var(--font-sans));
591
- font-size: var(--type-heading-4-size, 1.125rem);
662
+ font-size: calc(var(--type-heading-4-size, 1.125rem) * var(--type-base, 16) / 16);
592
663
  font-weight: var(--type-heading-weight, 600);
593
664
  line-height: round(nearest, calc(var(--type-heading-4-leading, 1.3) * 1em), var(--baseline-step, 0.25rem));
594
665
  letter-spacing: var(--type-heading-4-tracking, -0.01em);
@@ -603,13 +674,13 @@
603
674
  (a trading desk, an operator console) needs its own body/label/mono sizes,
604
675
  and pinning them in a page's own <style> block is a patch, not a theme. */
605
676
  @utility text-title-lg {
606
- font-size: var(--type-title-lg-size, 1rem);
677
+ font-size: calc(var(--type-title-lg-size, 1rem) * var(--type-base, 16) / 16);
607
678
  font-weight: var(--type-title-weight, 600);
608
679
  line-height: round(nearest, calc(var(--type-title-leading, 1.4) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
609
680
  letter-spacing: var(--type-title-tracking, -0.01em);
610
681
  }
611
682
  @utility text-title-sm {
612
- font-size: var(--type-title-sm-size, 0.875rem);
683
+ font-size: calc(var(--type-title-sm-size, 0.875rem) * var(--type-base, 16) / 16);
613
684
  font-weight: var(--type-title-weight, 600);
614
685
  line-height: round(nearest, calc(var(--type-title-leading, 1.4) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
615
686
  letter-spacing: var(--type-title-sm-tracking, -0.005em);
@@ -628,20 +699,20 @@
628
699
  `pretty` costs layout work over many lines to fix a break nobody reads as an
629
700
  error. Ledes are short by definition, so the cost is bounded here. */
630
701
  @utility text-body-lg {
631
- font-size: var(--type-body-lg-size, 1.0625rem);
702
+ font-size: calc(var(--type-body-lg-size, 1.0625rem) * var(--type-base, 16) / 16);
632
703
  font-weight: var(--type-body-lg-weight, 400);
633
704
  line-height: round(nearest, calc(var(--type-body-lg-leading, 1.6) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
634
705
  letter-spacing: var(--type-body-lg-tracking, -0.005em);
635
706
  text-wrap: pretty;
636
707
  }
637
708
  @utility text-body-md {
638
- font-size: var(--type-body-md-size, 0.9375rem);
709
+ font-size: calc(var(--type-body-md-size, 0.9375rem) * var(--type-base, 16) / 16);
639
710
  font-weight: var(--type-body-md-weight, 400);
640
711
  line-height: round(nearest, calc(var(--type-body-md-leading, 1.55) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
641
712
  letter-spacing: var(--type-body-md-tracking, -0.005em);
642
713
  }
643
714
  @utility text-body-sm {
644
- font-size: var(--type-body-sm-size, 0.8125rem);
715
+ font-size: calc(var(--type-body-sm-size, 0.8125rem) * var(--type-base, 16) / 16);
645
716
  font-weight: var(--type-body-sm-weight, 400);
646
717
  line-height: round(nearest, calc(var(--type-body-sm-leading, 1.5) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
647
718
  letter-spacing: var(--type-body-sm-tracking, 0);
@@ -650,7 +721,7 @@
650
721
  dense table cells, and metadata. Same size as text-caption but with body
651
722
  leading: caption is tuned for one-line annotation, body-xs for wrapping. */
652
723
  @utility text-body-xs {
653
- font-size: var(--type-body-xs-size, 0.75rem);
724
+ font-size: calc(var(--type-body-xs-size, 0.75rem) * var(--type-base, 16) / 16);
654
725
  font-weight: var(--type-body-xs-weight, 400);
655
726
  line-height: round(nearest, calc(var(--type-body-xs-leading, 1.5) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
656
727
  letter-spacing: var(--type-body-xs-tracking, 0);
@@ -678,7 +749,7 @@
678
749
  @utility sable-list {
679
750
  list-style: disc;
680
751
  padding-inline-start: 1.25em;
681
- font-size: var(--type-body-md-size, 0.9375rem);
752
+ font-size: calc(var(--type-body-md-size, 0.9375rem) * var(--type-base, 16) / 16);
682
753
  line-height: round(nearest, calc(var(--type-body-md-leading, 1.55) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
683
754
  color: var(--foreground);
684
755
  & > li + li {
@@ -692,7 +763,7 @@
692
763
  @utility sable-list-numbered {
693
764
  list-style: decimal;
694
765
  padding-inline-start: 1.5em;
695
- font-size: var(--type-body-md-size, 0.9375rem);
766
+ font-size: calc(var(--type-body-md-size, 0.9375rem) * var(--type-base, 16) / 16);
696
767
  line-height: round(nearest, calc(var(--type-body-md-leading, 1.55) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
697
768
  color: var(--foreground);
698
769
  & > li + li {
@@ -714,12 +785,12 @@
714
785
  /* The values' type and ink live on the list and the <dd>s inherit them, so a
715
786
  text-* utility on a <dd> (text-success, text-mono-md) simply wins. The term
716
787
  and the wrapping rules sit in :where(), which adds no specificity. */
717
- font-size: var(--type-body-md-size, 0.9375rem);
788
+ font-size: calc(var(--type-body-md-size, 0.9375rem) * var(--type-base, 16) / 16);
718
789
  line-height: round(nearest, calc(var(--type-body-md-leading, 1.55) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
719
790
  letter-spacing: var(--type-body-md-tracking, -0.005em);
720
791
  color: var(--foreground);
721
792
  & > :where(dt) {
722
- font-size: var(--type-label-md-size, 0.75rem);
793
+ font-size: calc(var(--type-label-md-size, 0.75rem) * var(--type-base, 16) / 16);
723
794
  font-weight: var(--type-label-md-weight, 600);
724
795
  line-height: round(nearest, calc(var(--type-label-md-leading, 1.2) * 1em), var(--baseline-step, 0.25rem));
725
796
  letter-spacing: var(--type-label-md-tracking, 0);
@@ -738,19 +809,19 @@
738
809
  }
739
810
 
740
811
  @utility text-label-lg {
741
- font-size: var(--type-label-lg-size, 0.875rem);
812
+ font-size: calc(var(--type-label-lg-size, 0.875rem) * var(--type-base, 16) / 16);
742
813
  font-weight: var(--type-label-lg-weight, 600);
743
814
  line-height: round(nearest, calc(var(--type-label-lg-leading, 1.25) * 1em), var(--baseline-step, 0.25rem));
744
815
  letter-spacing: var(--type-label-lg-tracking, -0.005em);
745
816
  }
746
817
  @utility text-label-md {
747
- font-size: var(--type-label-md-size, 0.75rem);
818
+ font-size: calc(var(--type-label-md-size, 0.75rem) * var(--type-base, 16) / 16);
748
819
  font-weight: var(--type-label-md-weight, 600);
749
820
  line-height: round(nearest, calc(var(--type-label-md-leading, 1.2) * 1em), var(--baseline-step, 0.25rem));
750
821
  letter-spacing: var(--type-label-md-tracking, 0);
751
822
  }
752
823
  @utility text-label-sm {
753
- font-size: var(--type-label-sm-size, 0.6875rem);
824
+ font-size: calc(var(--type-label-sm-size, 0.6875rem) * var(--type-base, 16) / 16);
754
825
  font-weight: var(--type-label-sm-weight, 600);
755
826
  line-height: round(nearest, calc(var(--type-label-sm-leading, 1.2) * 1em), var(--baseline-step, 0.25rem));
756
827
  letter-spacing: var(--type-label-sm-tracking, 0);
@@ -760,14 +831,14 @@
760
831
  it (Label `eyebrow`) rather than getting caps from the smallest label. The
761
832
  transform stays a themeable hook for a theme that wants caps here. */
762
833
  @utility text-label-xs {
763
- font-size: var(--type-label-xs-size, 0.6875rem);
834
+ font-size: calc(var(--type-label-xs-size, 0.6875rem) * var(--type-base, 16) / 16);
764
835
  font-weight: var(--type-label-xs-weight, 600);
765
836
  line-height: round(nearest, calc(var(--type-label-xs-leading, 1.2) * 1em), var(--baseline-step, 0.25rem));
766
837
  letter-spacing: var(--type-label-xs-tracking, 0.01em);
767
838
  text-transform: var(--type-label-xs-transform, none);
768
839
  }
769
840
  @utility text-caption {
770
- font-size: var(--type-caption-size, 0.75rem);
841
+ font-size: calc(var(--type-caption-size, 0.75rem) * var(--type-base, 16) / 16);
771
842
  font-weight: var(--type-caption-weight, 400);
772
843
  line-height: round(nearest, calc(var(--type-caption-leading, 1.4) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
773
844
  letter-spacing: var(--type-caption-tracking, 0);
@@ -778,7 +849,7 @@
778
849
  via TYPE_OVERLINE_TOKENS (see _contract.ts); `--type-overline-transform`
779
850
  lets a theme opt out of all-caps rather than hardcoding `uppercase`. */
780
851
  @utility text-overline {
781
- font-size: var(--type-overline-size, 0.75rem);
852
+ font-size: calc(var(--type-overline-size, 0.75rem) * var(--type-base, 16) / 16);
782
853
  font-weight: var(--type-overline-weight, 600);
783
854
  line-height: round(nearest, calc(var(--type-overline-leading, 1.2) * 1em), var(--baseline-step, 0.25rem));
784
855
  letter-spacing: var(--type-overline-tracking, 0.08em);
@@ -790,7 +861,7 @@
790
861
  the technical voice) rather than a heading tier — headings stay semantic. */
791
862
  @utility text-mono-xl {
792
863
  font-family: var(--font-mono);
793
- font-size: var(--type-mono-xl-size, 1.75rem);
864
+ font-size: calc(var(--type-mono-xl-size, 1.75rem) * var(--type-base, 16) / 16);
794
865
  font-weight: var(--type-mono-xl-weight, 500);
795
866
  line-height: round(nearest, calc(var(--type-mono-xl-leading, 1.15) * 1em), var(--baseline-step, 0.25rem));
796
867
  letter-spacing: var(--type-mono-xl-tracking, -0.01em);
@@ -798,7 +869,7 @@
798
869
  }
799
870
  @utility text-mono-lg {
800
871
  font-family: var(--font-mono);
801
- font-size: var(--type-mono-lg-size, 1.25rem);
872
+ font-size: calc(var(--type-mono-lg-size, 1.25rem) * var(--type-base, 16) / 16);
802
873
  font-weight: var(--type-mono-lg-weight, 500);
803
874
  line-height: round(nearest, calc(var(--type-mono-lg-leading, 1.25) * 1em), var(--baseline-step, 0.25rem));
804
875
  letter-spacing: var(--type-mono-lg-tracking, 0);
@@ -806,7 +877,7 @@
806
877
  }
807
878
  @utility text-mono-md {
808
879
  font-family: var(--font-mono);
809
- font-size: var(--type-mono-md-size, 0.8125rem);
880
+ font-size: calc(var(--type-mono-md-size, 0.8125rem) * var(--type-base, 16) / 16);
810
881
  font-weight: var(--type-mono-md-weight, 400);
811
882
  line-height: round(nearest, calc(var(--type-mono-md-leading, 1.5) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
812
883
  letter-spacing: var(--type-mono-md-tracking, 0);
@@ -817,7 +888,7 @@
817
888
  `font-mono text-caption` recomposition. */
818
889
  @utility text-mono-caption {
819
890
  font-family: var(--font-mono);
820
- font-size: var(--type-mono-caption-size, 0.75rem);
891
+ font-size: calc(var(--type-mono-caption-size, 0.75rem) * var(--type-base, 16) / 16);
821
892
  font-weight: var(--type-mono-caption-weight, 400);
822
893
  line-height: round(nearest, calc(var(--type-mono-caption-leading, 1.4) * var(--leading-scale, 1) * 1em), var(--baseline-step, 0.25rem));
823
894
  letter-spacing: var(--type-mono-caption-tracking, 0);
@@ -825,7 +896,7 @@
825
896
  }
826
897
  @utility text-mono-sm {
827
898
  font-family: var(--font-mono);
828
- font-size: var(--type-mono-sm-size, 0.6875rem);
899
+ font-size: calc(var(--type-mono-sm-size, 0.6875rem) * var(--type-base, 16) / 16);
829
900
  font-weight: var(--type-mono-sm-weight, 500);
830
901
  line-height: round(nearest, calc(var(--type-mono-sm-leading, 1.2) * 1em), var(--baseline-step, 0.25rem));
831
902
  letter-spacing: var(--type-mono-sm-tracking, 0.02em);
@@ -932,6 +1003,20 @@
932
1003
  letter-spacing: 0.005em;
933
1004
  }
934
1005
 
1006
+ /* The title of what a panel or inspector is showing — the open issue, the
1007
+ selected object. One step above ui-md on the chrome ladder (10 / 11 / 12 /
1008
+ 14): far enough above its 12px description to lead without weight alone,
1009
+ short of the 16px page title so it still reads as chrome. Linear's issue
1010
+ peek, Figma's selection header and Paper's inspector title all sit at
1011
+ 13-15px. On a 20px leading, the ui grid's next 4px step. Compact `text-title-lg`
1012
+ resolves here, so a page-register title dropped into a panel lands on it. */
1013
+ @utility text-ui-title {
1014
+ font-size: var(--type-ui-title-size, 14px);
1015
+ font-weight: var(--type-ui-title-weight, 600);
1016
+ line-height: var(--type-ui-title-leading, 20px);
1017
+ letter-spacing: -0.005em;
1018
+ }
1019
+
935
1020
  /* The reading variant — descriptions and help text inside chrome. */
936
1021
  @utility text-ui-body {
937
1022
  font-size: var(--type-ui-md-size, 12px);
@@ -1599,6 +1684,17 @@
1599
1684
  ========================================================================== */
1600
1685
  [data-density="compact"] {
1601
1686
  /*
1687
+ * The remap stops at a comfortable boundary. A reading view inside a tool —
1688
+ * an issue's overview, a settings form — is wrapped in
1689
+ * `<Density value="comfortable">` to get page type back, and the descendant
1690
+ * selectors alone still matched it, so the wrapper restored the control
1691
+ * ladder and left the prose at 11px. `:not(compact comfortable *)` rather
1692
+ * than `@scope (compact) to (comfortable)`: @scope would be the exact tool,
1693
+ * but a browser without it (Safari < 17.4, older Firefox — inside Tailwind
1694
+ * v4's support range) drops the whole block, and the register's type with
1695
+ * it. Limit: a compact scope nested inside that comfortable one gets page
1696
+ * type too, a third level of nesting nothing ships.
1697
+ *
1602
1698
  * Every value is `min(page, workspace)`, so the register can only ever
1603
1699
  * COMPRESS. Without it these grow on some apps: the page tiers are `rem`
1604
1700
  * and the workspace tiers are absolute px, so at a 14px root
@@ -1606,72 +1702,82 @@
1606
1702
  * tool panel — a "denser" register rendering larger text than the page it
1607
1703
  * sits beside. `min()` makes that impossible at any root size.
1608
1704
  */
1609
- & .text-body-lg {
1610
- font-size: min(1.0625rem, var(--type-ui-md-size, 12px));
1705
+ & .text-body-lg:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1706
+ font-size: min(calc(1.0625rem * var(--type-base, 16) / 16), var(--type-ui-md-size, 12px));
1611
1707
  line-height: var(--type-ui-leading, 16px);
1612
1708
  }
1613
- & .text-body-md {
1614
- font-size: min(0.9375rem, var(--type-ui-md-size, 12px));
1709
+ & .text-body-md:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1710
+ font-size: min(calc(0.9375rem * var(--type-base, 16) / 16), var(--type-ui-md-size, 12px));
1615
1711
  line-height: var(--type-ui-leading, 16px);
1616
1712
  }
1617
- & .text-body-sm {
1618
- font-size: min(0.8125rem, var(--type-ui-sm-size, 11px));
1713
+ & .text-body-sm:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1714
+ font-size: min(calc(0.8125rem * var(--type-base, 16) / 16), var(--type-ui-sm-size, 11px));
1619
1715
  line-height: var(--type-ui-leading, 16px);
1620
1716
  }
1621
- & .text-body-xs,
1622
- & .text-caption {
1623
- font-size: min(0.75rem, var(--type-ui-sm-size, 11px));
1717
+ & .text-body-xs:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *),
1718
+ & .text-caption:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1719
+ font-size: min(calc(0.75rem * var(--type-base, 16) / 16), var(--type-ui-sm-size, 11px));
1624
1720
  line-height: var(--type-ui-leading, 16px);
1625
1721
  }
1626
- & .text-label-lg {
1627
- font-size: min(0.875rem, var(--type-ui-md-size, 12px));
1722
+ & .text-label-lg:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1723
+ font-size: min(calc(0.875rem * var(--type-base, 16) / 16), var(--type-ui-md-size, 12px));
1628
1724
  font-weight: var(--type-ui-md-weight, 500);
1629
1725
  line-height: var(--type-ui-leading, 16px);
1630
1726
  }
1631
- & .text-label-md {
1632
- font-size: min(0.75rem, var(--type-ui-md-size, 12px));
1727
+ & .text-label-md:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1728
+ font-size: min(calc(0.75rem * var(--type-base, 16) / 16), var(--type-ui-md-size, 12px));
1633
1729
  font-weight: var(--type-ui-md-weight, 500);
1634
1730
  line-height: var(--type-ui-leading, 16px);
1635
1731
  }
1636
1732
  /* The list utilities carry their own type, so they follow the same tiers:
1637
1733
  items and values as body-md, terms as label-md. */
1638
- & :is(.sable-list, .sable-list-numbered, .sable-list-dl) {
1639
- font-size: min(0.9375rem, var(--type-ui-md-size, 12px));
1734
+ & :is(.sable-list, .sable-list-numbered, .sable-list-dl):not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1735
+ font-size: min(calc(0.9375rem * var(--type-base, 16) / 16), var(--type-ui-md-size, 12px));
1640
1736
  line-height: var(--type-ui-leading, 16px);
1641
1737
  }
1642
- & .sable-list-dl > :where(dt) {
1643
- font-size: min(0.75rem, var(--type-ui-md-size, 12px));
1738
+ & .sable-list-dl > :where(dt):not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1739
+ font-size: min(calc(0.75rem * var(--type-base, 16) / 16), var(--type-ui-md-size, 12px));
1644
1740
  font-weight: var(--type-ui-md-weight, 500);
1645
1741
  line-height: var(--type-ui-leading, 16px);
1646
1742
  }
1647
- & .text-label-sm,
1648
- & .text-label-xs {
1649
- font-size: min(0.6875rem, var(--type-ui-micro-size, 10px));
1743
+ & .text-label-sm:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *),
1744
+ & .text-label-xs:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1745
+ font-size: min(calc(0.6875rem * var(--type-base, 16) / 16), var(--type-ui-micro-size, 10px));
1650
1746
  font-weight: var(--type-ui-micro-weight, 450);
1651
1747
  line-height: 14px;
1652
1748
  }
1653
- & .text-title-sm {
1654
- font-size: min(0.875rem, var(--type-ui-md-size, 12px));
1749
+ & .text-title-sm:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1750
+ font-size: min(calc(0.875rem * var(--type-base, 16) / 16), var(--type-ui-md-size, 12px));
1655
1751
  font-weight: var(--type-ui-section-weight, 550);
1656
1752
  line-height: var(--type-ui-leading, 16px);
1657
1753
  }
1658
- & .text-title-lg {
1659
- font-size: min(1rem, var(--type-ui-md-size, 12px));
1660
- font-weight: var(--type-ui-section-weight, 550);
1661
- line-height: var(--type-ui-leading, 16px);
1754
+ /* title-lg names the thing a panel is about (the issue open in an inspector,
1755
+ the selected layer), so it takes the ui TITLE tier rather than ui-md. On
1756
+ ui-md it measured 12px/550 against a 12px description beneath it — the
1757
+ title and its own body the same size, hierarchy carried by weight alone. */
1758
+ & .text-title-lg:not([data-density="compact"] [data-density="comfortable"], [data-density="compact"] [data-density="comfortable"] *) {
1759
+ font-size: min(calc(1rem * var(--type-base, 16) / 16), var(--type-ui-title-size, 14px));
1760
+ font-weight: var(--type-ui-title-weight, 600);
1761
+ line-height: var(--type-ui-title-leading, 20px);
1662
1762
  }
1663
1763
  }
1664
1764
 
1665
1765
  /* ==========================================================================
1666
1766
  Layout — responsive grid spans + split panes.
1667
1767
 
1668
- The fluid layout primitives (Region, Grid, Stack…) compute their styles inline
1669
- so any dynamic value works without Tailwind scanning a class. Breakpoint-aware
1670
- layout can't be inline (a style attribute has no media queries), so `Col` and
1671
- `SplitPane` set per-breakpoint CSS custom properties inline and these rules
1672
- resolve them mobile-first. Breakpoints match Tailwind's defaults
1673
- (sm 40 / md 48 / lg 64 / xl 80 / 2xl 96 rem) so they line up with the `lg:*`
1674
- visibility utilities the app-shell components already use.
1768
+ The fluid layout primitives (Region, Grid, Stack…) put their layout in
1769
+ classes (`flex`, `grid`, `items-*`, `px-(--page-inset)`) and pass dynamic
1770
+ values as inline CSS custom properties that those classes read, so any value
1771
+ works without Tailwind scanning a class. Never an inline `display`, `gap` or
1772
+ padding: inline style beats every utility, and `<Row className="hidden
1773
+ md:flex">` silently did nothing. Breakpoint-aware layout can't be inline (a
1774
+ style attribute has no media queries), so `Col` and `SplitPane` set
1775
+ per-breakpoint CSS custom properties inline and these rules resolve them
1776
+ mobile-first. The rules sit in `@layer components` so a utility in the
1777
+ caller's `className` (`hidden`, `lg:grid-cols-3`, `gap-8`) still wins.
1778
+ Breakpoints match Tailwind's defaults (sm 40 / md 48 / lg 64 / xl 80 /
1779
+ 2xl 96 rem) so they line up with the `lg:*` visibility utilities the
1780
+ app-shell components already use.
1675
1781
  ========================================================================== */
1676
1782
 
1677
1783
  /* Each element owns its breakpoint values. Custom properties inherit, so a
@@ -1679,18 +1785,20 @@
1679
1785
  Grid inside `Columns count={{ lg: 12 }}` drew 12 tracks at lg. Resetting
1680
1786
  them here makes an unset one invalid, so the fallback chain below runs on the
1681
1787
  element itself; the values it was given are inline and win over this. */
1682
- [data-sable-col] {
1683
- --col-span-base: initial; --col-span-sm: initial; --col-span-md: initial;
1684
- --col-span-lg: initial; --col-span-xl: initial; --col-span-2xl: initial;
1685
- --col-start-base: initial; --col-start-sm: initial; --col-start-md: initial;
1686
- --col-start-lg: initial; --col-start-xl: initial; --col-start-2xl: initial;
1687
- }
1688
- [data-sable-grid-cols] {
1689
- --cols-base: initial; --cols-sm: initial; --cols-md: initial;
1690
- --cols-lg: initial; --cols-xl: initial; --cols-2xl: initial;
1691
- }
1692
- [data-sable-split] {
1693
- --split-gap: initial;
1788
+ @layer components {
1789
+ [data-sable-col] {
1790
+ --col-span-base: initial; --col-span-sm: initial; --col-span-md: initial;
1791
+ --col-span-lg: initial; --col-span-xl: initial; --col-span-2xl: initial;
1792
+ --col-start-base: initial; --col-start-sm: initial; --col-start-md: initial;
1793
+ --col-start-lg: initial; --col-start-xl: initial; --col-start-2xl: initial;
1794
+ }
1795
+ [data-sable-grid-cols] {
1796
+ --cols-base: initial; --cols-sm: initial; --cols-md: initial;
1797
+ --cols-lg: initial; --cols-xl: initial; --cols-2xl: initial;
1798
+ }
1799
+ [data-sable-split] {
1800
+ --split-gap: initial;
1801
+ }
1694
1802
  }
1695
1803
 
1696
1804
  /* Toasts keep clear of an open Sheet's actions. A right sheet's pinned footer
@@ -1698,9 +1806,16 @@
1698
1806
  top-right one.
1699
1807
  - Where there is room, a stack on the sheet's side moves beside it. The sheet
1700
1808
  publishes its real width (--sable-sheet-right-w / -left-w) while mounted.
1701
- - On a narrower screen the sheet leaves no room beside it. A bottom stack
1702
- moves to the top, off the footer's actions; a top stack stays, over the
1703
- header.
1809
+ - On a narrower screen the sheet leaves no room beside it and both of its
1810
+ edges hold controls: a footer of actions (a composer, Check out) and a
1811
+ header with its close button. So every stack — any position, either side
1812
+ — moves to the top and drops below a PROTECTED INSET: the sheet's padding
1813
+ plus one control row, which clears a padded SheetHeader with a close
1814
+ button and a 48px bar alike. It then covers the top of the sheet's
1815
+ scrolling content, never an action. The first version moved bottom stacks
1816
+ to the top edge itself, straight over the close button, and an app worked
1817
+ around it by switching to bottom-center — over its composer's send button.
1818
+ A sheet whose header is taller sets `--sable-sheet-top-inset` on <html>.
1704
1819
  - A bottom sheet sends a bottom stack to the top, a top sheet sends a top
1705
1820
  stack to the bottom, at any width.
1706
1821
  A stack that moves to the other edge re-anchors its stacked toasts too: they
@@ -1715,13 +1830,11 @@
1715
1830
  }
1716
1831
  }
1717
1832
  @media (width < 52rem) {
1718
- :root:has([data-sable-sheet="right"][data-open]) [data-sable-toast-viewport][data-position="bottom-right"],
1719
- :root:has([data-sable-sheet="left"][data-open]) [data-sable-toast-viewport][data-position="bottom-left"] {
1720
- top: calc(var(--spacing) * 4);
1833
+ :root:has(:is([data-sable-sheet="right"], [data-sable-sheet="left"])[data-open]) [data-sable-toast-viewport] {
1834
+ top: var(--sable-sheet-top-inset, calc(var(--spacing) * 8 + var(--control-md)));
1721
1835
  bottom: auto;
1722
1836
  }
1723
- :root:has([data-sable-sheet="right"][data-open]) [data-sable-toast-viewport][data-position="bottom-right"] [data-sable-toast-stacked],
1724
- :root:has([data-sable-sheet="left"][data-open]) [data-sable-toast-viewport][data-position="bottom-left"] [data-sable-toast-stacked] {
1837
+ :root:has(:is([data-sable-sheet="right"], [data-sable-sheet="left"])[data-open]) [data-sable-toast-viewport][data-position^="bottom"] [data-sable-toast-stacked] {
1725
1838
  top: 0;
1726
1839
  bottom: auto;
1727
1840
  transform-origin: top;
@@ -1761,126 +1874,126 @@
1761
1874
  }
1762
1875
  }
1763
1876
 
1764
- /* `Col span={{ base, md, lg }}` / `start={…}` — each breakpoint resolves to the
1765
- most specific value defined at or below it (mobile-first cascade). */
1766
- [data-sable-col] {
1767
- grid-column: var(--col-start, auto) / span var(--col-span, 1);
1768
- --col-span: var(--col-span-base, 1);
1769
- --col-start: var(--col-start-base, auto);
1770
- }
1771
- @media (width >= 40rem) {
1877
+ @layer components {
1878
+ /* `Col span={{ base, md, lg }}` / `start={…}` — each breakpoint resolves to the
1879
+ most specific value defined at or below it (mobile-first cascade). */
1772
1880
  [data-sable-col] {
1773
- --col-span: var(--col-span-sm, var(--col-span-base, 1));
1774
- --col-start: var(--col-start-sm, var(--col-start-base, auto));
1881
+ grid-column: var(--col-start, auto) / span var(--col-span, 1);
1882
+ --col-span: var(--col-span-base, 1);
1883
+ --col-start: var(--col-start-base, auto);
1884
+ }
1885
+ @media (width >= 40rem) {
1886
+ [data-sable-col] {
1887
+ --col-span: var(--col-span-sm, var(--col-span-base, 1));
1888
+ --col-start: var(--col-start-sm, var(--col-start-base, auto));
1889
+ }
1775
1890
  }
1776
- }
1777
- @media (width >= 48rem) {
1778
- [data-sable-col] {
1779
- --col-span: var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1)));
1780
- --col-start: var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto)));
1891
+ @media (width >= 48rem) {
1892
+ [data-sable-col] {
1893
+ --col-span: var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1)));
1894
+ --col-start: var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto)));
1895
+ }
1781
1896
  }
1782
- }
1783
- @media (width >= 64rem) {
1784
- [data-sable-col] {
1785
- --col-span: var(--col-span-lg, var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1))));
1786
- --col-start: var(--col-start-lg, var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto))));
1897
+ @media (width >= 64rem) {
1898
+ [data-sable-col] {
1899
+ --col-span: var(--col-span-lg, var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1))));
1900
+ --col-start: var(--col-start-lg, var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto))));
1901
+ }
1787
1902
  }
1788
- }
1789
- @media (width >= 80rem) {
1790
- [data-sable-col] {
1791
- --col-span: var(--col-span-xl, var(--col-span-lg, var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1)))));
1792
- --col-start: var(--col-start-xl, var(--col-start-lg, var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto)))));
1903
+ @media (width >= 80rem) {
1904
+ [data-sable-col] {
1905
+ --col-span: var(--col-span-xl, var(--col-span-lg, var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1)))));
1906
+ --col-start: var(--col-start-xl, var(--col-start-lg, var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto)))));
1907
+ }
1793
1908
  }
1794
- }
1795
- @media (width >= 96rem) {
1796
- [data-sable-col] {
1797
- --col-span: var(--col-span-2xl, var(--col-span-xl, var(--col-span-lg, var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1))))));
1798
- --col-start: var(--col-start-2xl, var(--col-start-xl, var(--col-start-lg, var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto))))));
1909
+ @media (width >= 96rem) {
1910
+ [data-sable-col] {
1911
+ --col-span: var(--col-span-2xl, var(--col-span-xl, var(--col-span-lg, var(--col-span-md, var(--col-span-sm, var(--col-span-base, 1))))));
1912
+ --col-start: var(--col-start-2xl, var(--col-start-xl, var(--col-start-lg, var(--col-start-md, var(--col-start-sm, var(--col-start-base, auto))))));
1913
+ }
1799
1914
  }
1800
- }
1801
1915
 
1802
- /* `Columns count` / `Grid cols` as a breakpoint map — set per-breakpoint --cols-*
1803
- props inline; resolved mobile-first into the track's column count, so a grid
1804
- thins to fewer columns on small screens (e.g. 2 → 6 → 12). */
1805
- [data-sable-grid-cols] {
1806
- display: grid;
1807
- grid-template-columns: repeat(var(--cols, var(--grid-columns)), minmax(0, 1fr));
1808
- --cols: var(--cols-base, var(--grid-columns));
1809
- }
1810
- @media (width >= 40rem) {
1916
+ /* `Columns count` / `Grid cols` as a breakpoint map — set per-breakpoint --cols-*
1917
+ props inline; resolved mobile-first into the track's column count, so a grid
1918
+ thins to fewer columns on small screens (e.g. 2 → 6 → 12). */
1811
1919
  [data-sable-grid-cols] {
1812
- --cols: var(--cols-sm, var(--cols-base, var(--grid-columns)));
1920
+ grid-template-columns: repeat(var(--cols, var(--grid-columns)), minmax(0, 1fr));
1921
+ --cols: var(--cols-base, var(--grid-columns));
1813
1922
  }
1814
- }
1815
- @media (width >= 48rem) {
1816
- [data-sable-grid-cols] {
1817
- --cols: var(--cols-md, var(--cols-sm, var(--cols-base, var(--grid-columns))));
1923
+ @media (width >= 40rem) {
1924
+ [data-sable-grid-cols] {
1925
+ --cols: var(--cols-sm, var(--cols-base, var(--grid-columns)));
1926
+ }
1818
1927
  }
1819
- }
1820
- @media (width >= 64rem) {
1821
- [data-sable-grid-cols] {
1822
- --cols: var(
1823
- --cols-lg,
1824
- var(--cols-md, var(--cols-sm, var(--cols-base, var(--grid-columns))))
1825
- );
1928
+ @media (width >= 48rem) {
1929
+ [data-sable-grid-cols] {
1930
+ --cols: var(--cols-md, var(--cols-sm, var(--cols-base, var(--grid-columns))));
1931
+ }
1826
1932
  }
1827
- }
1828
- @media (width >= 80rem) {
1829
- [data-sable-grid-cols] {
1830
- --cols: var(
1831
- --cols-xl,
1832
- var(
1933
+ @media (width >= 64rem) {
1934
+ [data-sable-grid-cols] {
1935
+ --cols: var(
1833
1936
  --cols-lg,
1834
1937
  var(--cols-md, var(--cols-sm, var(--cols-base, var(--grid-columns))))
1835
- )
1836
- );
1938
+ );
1939
+ }
1837
1940
  }
1838
- }
1839
- @media (width >= 96rem) {
1840
- [data-sable-grid-cols] {
1841
- --cols: var(
1842
- --cols-2xl,
1843
- var(
1941
+ @media (width >= 80rem) {
1942
+ [data-sable-grid-cols] {
1943
+ --cols: var(
1844
1944
  --cols-xl,
1845
1945
  var(
1846
1946
  --cols-lg,
1847
1947
  var(--cols-md, var(--cols-sm, var(--cols-base, var(--grid-columns))))
1848
1948
  )
1849
- )
1850
- );
1949
+ );
1950
+ }
1951
+ }
1952
+ @media (width >= 96rem) {
1953
+ [data-sable-grid-cols] {
1954
+ --cols: var(
1955
+ --cols-2xl,
1956
+ var(
1957
+ --cols-xl,
1958
+ var(
1959
+ --cols-lg,
1960
+ var(--cols-md, var(--cols-sm, var(--cols-base, var(--grid-columns))))
1961
+ )
1962
+ )
1963
+ );
1964
+ }
1851
1965
  }
1852
- }
1853
1966
 
1854
- /* `SplitPane collapseBelow="lg"` — a single stacked column below the breakpoint;
1855
- a two-column track (primary:secondary fr ratio) at or above it. */
1856
- [data-sable-split] {
1857
- display: grid;
1858
- gap: var(--split-gap, var(--grid-gutter));
1859
- grid-template-columns: 1fr;
1860
- }
1861
- @media (width >= 40rem) {
1862
- [data-sable-split][data-collapse="sm"] {
1863
- grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1967
+ /* `SplitPane collapseBelow="lg"` — a single stacked column below the breakpoint;
1968
+ a two-column track (primary:secondary fr ratio) at or above it. */
1969
+ [data-sable-split] {
1970
+ gap: var(--split-gap, var(--grid-gutter));
1971
+ grid-template-columns: 1fr;
1864
1972
  }
1865
- }
1866
- @media (width >= 48rem) {
1867
- [data-sable-split][data-collapse="md"] {
1868
- grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1973
+ @media (width >= 40rem) {
1974
+ [data-sable-split][data-collapse="sm"] {
1975
+ grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1976
+ }
1869
1977
  }
1870
- }
1871
- @media (width >= 64rem) {
1872
- [data-sable-split][data-collapse="lg"] {
1873
- grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1978
+ @media (width >= 48rem) {
1979
+ [data-sable-split][data-collapse="md"] {
1980
+ grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1981
+ }
1874
1982
  }
1875
- }
1876
- @media (width >= 80rem) {
1877
- [data-sable-split][data-collapse="xl"] {
1878
- grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1983
+ @media (width >= 64rem) {
1984
+ [data-sable-split][data-collapse="lg"] {
1985
+ grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1986
+ }
1879
1987
  }
1880
- }
1881
- @media (width >= 96rem) {
1882
- [data-sable-split][data-collapse="2xl"] {
1883
- grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1988
+ @media (width >= 80rem) {
1989
+ [data-sable-split][data-collapse="xl"] {
1990
+ grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1991
+ }
1992
+ }
1993
+ @media (width >= 96rem) {
1994
+ [data-sable-split][data-collapse="2xl"] {
1995
+ grid-template-columns: var(--split-primary, 1fr) var(--split-secondary, 1fr);
1996
+ }
1884
1997
  }
1885
1998
  }
1886
1999
 
@@ -2043,7 +2156,7 @@
2043
2156
  @utility checkerboard {
2044
2157
  background-image: repeating-conic-gradient(
2045
2158
  var(--panel) 0% 25%,
2046
- var(--checkerboard-alt, color-mix(in oklch, var(--panel) 88%, var(--foreground)))
2159
+ var(--checkerboard-alt, color-mix(in oklab, var(--panel) 88%, var(--foreground)))
2047
2160
  0% 50%
2048
2161
  );
2049
2162
  background-size: var(--checkerboard-size, 16px) var(--checkerboard-size, 16px);