@sableui/react 0.0.0-stage → 0.1.1
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.
- package/AGENTS.md +157 -0
- package/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +221 -0
- package/DESIGN.md +548 -0
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/THIRD_PARTY_NOTICES.md +44 -0
- package/bin/sable-lint.mjs +0 -0
- package/bin/sable.mjs +1223 -0
- package/dist/_contract-CYScOdf5.d.ts +150 -0
- package/dist/chunk-3IWZYBGH.js +398 -0
- package/dist/chunk-5Z5KLOAU.js +167 -0
- package/dist/chunk-7MG4SHYX.js +15 -0
- package/dist/chunk-ADLLDWLW.js +15 -0
- package/dist/chunk-EP55KSYA.js +14 -0
- package/dist/chunk-J23ELQQZ.js +9396 -0
- package/dist/chunk-JI7WJP2L.js +286 -0
- package/dist/chunk-K7RUHUGC.js +14 -0
- package/dist/chunk-KO3NAEBF.js +15 -0
- package/dist/chunk-L4IEIQQA.js +14 -0
- package/dist/chunk-L6Y46C62.js +270 -0
- package/dist/chunk-NEQNXI2G.js +25 -0
- package/dist/chunk-PAOZBZME.js +40 -0
- package/dist/chunk-RWP4XBSK.js +139 -0
- package/dist/chunk-SRQWQUTW.js +63 -0
- package/dist/chunk-YXBOYWBN.js +15 -0
- package/dist/foundations/index.d.ts +88 -0
- package/dist/foundations/index.js +792 -0
- package/dist/icon/index.d.ts +60 -0
- package/dist/icon/index.js +2 -0
- package/dist/icon-sets/brands.d.ts +44 -0
- package/dist/icon-sets/brands.js +147 -0
- package/dist/icon-sets/reicon.d.ts +8 -0
- package/dist/icon-sets/reicon.js +1 -0
- package/dist/index.d.ts +7013 -0
- package/dist/index.js +15462 -0
- package/dist/ios/chrome.css +83 -0
- package/dist/ios/frame.css +5 -0
- package/dist/ios/index.d.ts +70 -0
- package/dist/ios/index.js +313 -0
- package/dist/ios/preview.d.ts +64 -0
- package/dist/ios/preview.js +133 -0
- package/dist/ios/styles.css +77 -0
- package/dist/layout/index.d.ts +280 -0
- package/dist/layout/index.js +2 -0
- package/dist/meta-6dUzgC6M.d.ts +205 -0
- package/dist/patterns/index.d.ts +36 -0
- package/dist/patterns/index.js +934 -0
- package/dist/presets/index.d.ts +37 -0
- package/dist/presets/index.js +117 -0
- package/dist/registry-CyL28OzJ.d.ts +28 -0
- package/dist/styles.css +1893 -0
- package/dist/theme-provider-BWcizhAt.d.ts +105 -0
- package/dist/themes/atelier/atelier.css +213 -0
- package/dist/themes/atelier/index.d.ts +9 -0
- package/dist/themes/atelier/index.js +1 -0
- package/dist/themes/base/base.css +443 -0
- package/dist/themes/base/index.d.ts +9 -0
- package/dist/themes/base/index.js +1 -0
- package/dist/themes/ember/ember.css +270 -0
- package/dist/themes/ember/index.d.ts +9 -0
- package/dist/themes/ember/index.js +1 -0
- package/dist/themes/index.d.ts +10 -0
- package/dist/themes/index.js +8 -0
- package/dist/themes/meridian/index.d.ts +9 -0
- package/dist/themes/meridian/index.js +1 -0
- package/dist/themes/meridian/meridian.css +255 -0
- package/dist/themes/poppy/index.d.ts +6 -0
- package/dist/themes/poppy/index.js +1 -0
- package/dist/themes/poppy/poppy.css +174 -0
- package/dist/themes/prism/index.d.ts +6 -0
- package/dist/themes/prism/index.js +1 -0
- package/dist/themes/prism/prism.css +183 -0
- package/dist/themes/verdant/index.d.ts +9 -0
- package/dist/themes/verdant/index.js +1 -0
- package/dist/themes/verdant/verdant.css +210 -0
- package/dist/themes.css +30 -0
- package/llms/components/accordion.md +93 -0
- package/llms/components/app-header.md +70 -0
- package/llms/components/app-shell.md +117 -0
- package/llms/components/attachment.md +105 -0
- package/llms/components/avatar.md +141 -0
- package/llms/components/badge.md +117 -0
- package/llms/components/banner.md +119 -0
- package/llms/components/bottom-nav.md +80 -0
- package/llms/components/breadcrumbs.md +75 -0
- package/llms/components/browser.md +67 -0
- package/llms/components/bubble.md +119 -0
- package/llms/components/button.md +199 -0
- package/llms/components/calendar.md +75 -0
- package/llms/components/card.md +135 -0
- package/llms/components/carousel.md +87 -0
- package/llms/components/chat-composer.md +126 -0
- package/llms/components/checkbox-card.md +98 -0
- package/llms/components/checkbox.md +90 -0
- package/llms/components/code-block.md +81 -0
- package/llms/components/code.md +62 -0
- package/llms/components/collapsible.md +83 -0
- package/llms/components/color-field.md +100 -0
- package/llms/components/combobox.md +119 -0
- package/llms/components/command-menu.md +158 -0
- package/llms/components/context-card.md +73 -0
- package/llms/components/context-menu.md +79 -0
- package/llms/components/data-table.md +99 -0
- package/llms/components/date-picker.md +97 -0
- package/llms/components/description.md +96 -0
- package/llms/components/destructive-action-modal.md +76 -0
- package/llms/components/dialog.md +127 -0
- package/llms/components/dots-menu.md +64 -0
- package/llms/components/empty-state.md +113 -0
- package/llms/components/entity.md +67 -0
- package/llms/components/feedback.md +63 -0
- package/llms/components/fieldset.md +106 -0
- package/llms/components/file-dropzone.md +103 -0
- package/llms/components/form.md +111 -0
- package/llms/components/gliding-highlight.md +84 -0
- package/llms/components/inline-status.md +62 -0
- package/llms/components/input.md +127 -0
- package/llms/components/kbd.md +57 -0
- package/llms/components/label.md +73 -0
- package/llms/components/marker.md +99 -0
- package/llms/components/marquee.md +66 -0
- package/llms/components/menu.md +122 -0
- package/llms/components/message-scroller.md +110 -0
- package/llms/components/message.md +114 -0
- package/llms/components/meter.md +124 -0
- package/llms/components/middle-truncate.md +62 -0
- package/llms/components/note.md +81 -0
- package/llms/components/notification-badge.md +67 -0
- package/llms/components/number-field.md +70 -0
- package/llms/components/otp-field.md +101 -0
- package/llms/components/page-header.md +76 -0
- package/llms/components/page-loader.md +67 -0
- package/llms/components/pagination.md +92 -0
- package/llms/components/panel-group.md +120 -0
- package/llms/components/panel-section.md +122 -0
- package/llms/components/phone.md +70 -0
- package/llms/components/pill.md +73 -0
- package/llms/components/popover.md +79 -0
- package/llms/components/progress.md +98 -0
- package/llms/components/radio-card.md +90 -0
- package/llms/components/radio.md +86 -0
- package/llms/components/relative-time.md +60 -0
- package/llms/components/scroller.md +88 -0
- package/llms/components/scrub-field.md +97 -0
- package/llms/components/search-input.md +84 -0
- package/llms/components/select.md +106 -0
- package/llms/components/separator.md +70 -0
- package/llms/components/settings-menu.md +139 -0
- package/llms/components/sheet.md +97 -0
- package/llms/components/show-more.md +59 -0
- package/llms/components/side-nav.md +106 -0
- package/llms/components/sidebar.md +87 -0
- package/llms/components/skeleton.md +62 -0
- package/llms/components/slider.md +114 -0
- package/llms/components/spinner.md +87 -0
- package/llms/components/status-bar.md +91 -0
- package/llms/components/status-dot.md +70 -0
- package/llms/components/stepper.md +125 -0
- package/llms/components/streaming-text.md +86 -0
- package/llms/components/switch.md +87 -0
- package/llms/components/table.md +112 -0
- package/llms/components/tabs.md +84 -0
- package/llms/components/textarea.md +88 -0
- package/llms/components/theme-selector.md +95 -0
- package/llms/components/theme-switcher.md +71 -0
- package/llms/components/thinking-orb.md +91 -0
- package/llms/components/toast.md +130 -0
- package/llms/components/toggle.md +95 -0
- package/llms/components/toolbar.md +168 -0
- package/llms/components/tooltip.md +93 -0
- package/llms/components/tree.md +227 -0
- package/llms/components/video.md +66 -0
- package/llms/components/workspace.md +138 -0
- package/llms/foundations/accessibility.md +60 -0
- package/llms/foundations/motion.md +96 -0
- package/llms/foundations/scrolling.md +41 -0
- package/llms/foundations/shape.md +73 -0
- package/llms/foundations/spacing.md +82 -0
- package/llms/foundations/surfaces.md +69 -0
- package/llms/foundations/typography.md +120 -0
- package/llms/layout/aspect-box.md +37 -0
- package/llms/layout/bleed.md +31 -0
- package/llms/layout/center.md +37 -0
- package/llms/layout/columns.md +51 -0
- package/llms/layout/grid-overlay.md +40 -0
- package/llms/layout/grid.md +47 -0
- package/llms/layout/page.md +39 -0
- package/llms/layout/region.md +49 -0
- package/llms/layout/row.md +40 -0
- package/llms/layout/safe-area.md +37 -0
- package/llms/layout/spacer.md +39 -0
- package/llms/layout/split-pane.md +41 -0
- package/llms/layout/stack.md +39 -0
- package/llms/patterns/auth-page.md +68 -0
- package/llms/patterns/brand-landing-page.md +423 -0
- package/llms/patterns/chat-panel.md +210 -0
- package/llms/patterns/dashboard-page.md +170 -0
- package/llms/patterns/feed-page.md +103 -0
- package/llms/patterns/landing-page.md +122 -0
- package/llms/patterns/list-detail-page.md +100 -0
- package/llms/patterns/settings-page.md +121 -0
- package/llms/reference/token-intent.md +324 -0
- package/llms/reference/utilities.md +129 -0
- package/llms/tasks/adding-a-component.md +113 -0
- package/llms/tasks/auditing-an-app.md +216 -0
- package/llms/tasks/building-a-page.md +109 -0
- package/llms/tasks/building-a-tool.md +141 -0
- package/llms/tasks/building-an-ios-prototype.md +105 -0
- package/llms/tasks/building-with-sable.md +73 -0
- package/llms/tasks/choosing-a-component.md +109 -0
- package/llms/tasks/converting-an-app.md +139 -0
- package/llms/tasks/creating-a-preset.md +43 -0
- package/llms/tasks/creating-design-assets.md +96 -0
- package/llms/tasks/designing-a-surface.md +115 -0
- package/llms/tasks/developing-taste.md +157 -0
- package/llms/tasks/getting-started.md +63 -0
- package/llms/tasks/learning-from-builds.md +74 -0
- package/llms/tasks/reviewing-a-design.md +114 -0
- package/llms/tasks/reviewing-a-journey.md +84 -0
- package/llms/tasks/reviewing-motion.md +92 -0
- package/llms/tasks/using-presets.md +87 -0
- package/llms/taste/asset-plan.template.json +53 -0
- package/llms/taste/composition-follows-content.md +20 -0
- package/llms/taste/hierarchy-is-a-relationship.md +22 -0
- package/llms/taste/lessons.json +57 -0
- package/llms/taste/motion-needs-a-reason.md +21 -0
- package/llms/taste/surface-states-travel-together.md +20 -0
- package/llms-full.txt +14906 -0
- package/llms.txt +405 -0
- package/package.json +185 -3
- package/registry.json +18812 -0
- package/scripts/agent-setup.mjs +54 -0
- package/scripts/design-atlas.mjs +221 -0
- package/scripts/design-directions.mjs +123 -0
- package/scripts/design-memory.mjs +166 -0
- package/scripts/design-preferences.mjs +227 -0
- package/scripts/design-presets.mjs +48 -0
- package/scripts/grade.mjs +509 -0
- package/scripts/lint-rules.mjs +1065 -0
- package/scripts/review-evidence.mjs +120 -0
- package/scripts/skeleton.mjs +248 -0
- package/skills/entry/SKILL.md +34 -0
- package/skills/sable/SKILL.md +187 -0
- package/skills/sable/agents/openai.yaml +6 -0
- package/targets/ios.json +982 -0
- package/third-party/reicon-LICENSE.txt +20 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Shape
|
|
2
|
+
|
|
3
|
+
Radius signals what something is: tight on containers, full on actions. Nested corners stay concentric, and one edge treatment marks a boundary.
|
|
4
|
+
|
|
5
|
+
**7 radius steps · concentric nesting · 1 edge treatment per boundary**
|
|
6
|
+
|
|
7
|
+
## The radius ladder
|
|
8
|
+
|
|
9
|
+
Each step is 4px apart, so common insets land on a real step. Themes and the customizer retune every step together.
|
|
10
|
+
|
|
11
|
+
| Name | Value | Use |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `rounded-none` | 0 | Full-bleed media. |
|
|
14
|
+
| `rounded-xs` | 4px | Tags, kbd, small inline marks. |
|
|
15
|
+
| `rounded-sm` | 8px | Inputs, menu items, list rows. |
|
|
16
|
+
| `rounded-md` | 12px | Popovers, menus, toasts. |
|
|
17
|
+
| `rounded-lg` | 16px | Cards, dialogs, sheets. |
|
|
18
|
+
| `rounded-xl` | 20px | Hero surfaces. |
|
|
19
|
+
| `rounded-full` | pill | Buttons (via --button-radius), avatars, pills, dots. |
|
|
20
|
+
|
|
21
|
+
## Edges
|
|
22
|
+
|
|
23
|
+
| Name | Value | Use |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `border-border` | hairline | The default edge: inputs, cards, badges. |
|
|
26
|
+
| `border-border-strong` | hairline, higher contrast | Structural rules: table grids, section separators. |
|
|
27
|
+
| `border-border-floating` | faint, alpha | Every floating surface: popovers, menus, selects, tooltips, dialogs, sheets, toasts. Lighter than border; the shadow carries the elevation. |
|
|
28
|
+
| `ring-2 ring-ring` | 2px focus ring | Focus only. Never a thick border. |
|
|
29
|
+
|
|
30
|
+
## Rules
|
|
31
|
+
|
|
32
|
+
1. **Nested corners are concentric: inner radius = outer radius − inset. Write it as rounded-concentric-<outer>/<inset>, not a hardcoded step.** A 12px popover with 4px padding takes 8px items. The utility computes the subtraction from the live tokens, so the pair stays concentric when a theme or the customizer rescales the radius ladder; a hardcoded rounded-sm drifts.
|
|
33
|
+
2. **A list of rows inside a rounded popup uses popup-inset on the popup, rounded-popup-item on the rows and popup-bleed on a separator.** All three derive from the popup's radius: the inset grows to a third of it (never below --spacing(1), so density moves it too), the rows take what is left, and a separator bleeds by exactly the inset. A rounder look gets more room instead of rows that round into pills, and the default ladder still lands on the familiar 4px inset and 8px rows. *Enforced by sable-lint: popup-separator-bleed.*
|
|
34
|
+
3. **A Button, Pill or Badge gets its look from a variant, never from override classes.** border, bg, rounded, ring and shadow classes on a component fight the variant's own hover, selected and focus states, and give one call site a look no other site can ask for. Use the variant that exists (Button variant="floating" over content, "on-color" on a fill; Pill appearance="outline"; Badge appearance and tone) or add one to the component. *Enforced by sable-lint: component-restyle.*
|
|
35
|
+
4. **The outer radius is always at least the inner radius.** If the inset is larger than the outer radius, square the inner element or step the container up.
|
|
36
|
+
5. **Never mix radii within one component.** A component reads as one object when its corners agree.
|
|
37
|
+
6. **Actions are full; containers are tight.** The contrast itself says 'this is a control'. A theme flattens buttons through --button-radius, not by editing each one.
|
|
38
|
+
7. **High-contrast outlines (selection, emphasis) are inset rings; hairline chrome is a border.** Rings anti-alias on rounded corners at any display scale; a 1px light border on dark snaps to the pixel grid and looks jagged.
|
|
39
|
+
8. **One edge treatment per boundary: a border, a ring or a shadow.** Stacking all three makes an edge heavier than the content it holds.
|
|
40
|
+
9. **Prefer a tonal step or space over a divider line.** The flattest thing that reads is best.
|
|
41
|
+
|
|
42
|
+
## Never
|
|
43
|
+
|
|
44
|
+
- **Never:** rounded-[10px] or any off-ladder radius. **Instead:** The ladder step for the role. Off-ladder radii cannot follow a theme or the customizer's radius control.
|
|
45
|
+
- **Never:** A card inside a card with the same radius. **Instead:** Flatten the inner one, or use rounded-concentric-*. Nested surfaces with equal corners look misaligned at every corner.
|
|
46
|
+
- **Never:** <Button className="rounded-full">, <Pill className="border bg-card">, a -mx-1 separator in a popup. **Instead:** The component's variant (add one if it is missing); popup-bleed for the separator. An override fights the component's own states and does not follow the radius ladder, the density or a theme. *Enforced by sable-lint: component-restyle, popup-separator-bleed.*
|
|
47
|
+
- **Never:** border-2 for emphasis or focus. **Instead:** ring-2 ring-ring for focus; a tonal fill for emphasis. A thick border shifts layout and competes with the focus ring.
|
|
48
|
+
|
|
49
|
+
## Details
|
|
50
|
+
|
|
51
|
+
### Crisp selected outlines on dark surfaces
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// before
|
|
55
|
+
<button className="rounded-lg border border-foreground">
|
|
56
|
+
// after
|
|
57
|
+
<button className="rounded-lg ring-[1.5px] ring-foreground ring-inset">
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
A 1px high-contrast border on a rounded corner snaps unevenly at fractional device-pixel ratios (Windows 125%/150%) and reads as jagged, worst light-on-dark. A box-shadow ring is anti-aliased instead, and inset keeps it from changing the element's size. Keep hairline borders for low-contrast chrome, where the snapping is invisible.
|
|
61
|
+
|
|
62
|
+
### Concentric menu items
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
// before
|
|
66
|
+
<div className="rounded-md p-1"><div className="rounded-md">Item</div></div>
|
|
67
|
+
// after
|
|
68
|
+
<div className="rounded-md popup-inset"><div className="rounded-popup-item">Item</div></div>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
12px outer − 4px inset = 8px inner at the default ladder, and the utilities keep the subtraction right when a preset or the customizer rescales the radius or the density.
|
|
72
|
+
|
|
73
|
+
**Related:** `sable docs surfaces` · `sable docs spacing` · `sable token radii`
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Spacing & sizing
|
|
2
|
+
|
|
3
|
+
One 4px scale governs every gap, inset and size, and space is assigned by role. Controls read a height ladder that a density scope can retune all at once.
|
|
4
|
+
|
|
5
|
+
**4px base · 8px rhythm · 3 roles · 1 control ladder**
|
|
6
|
+
|
|
7
|
+
## Spacing by role
|
|
8
|
+
|
|
9
|
+
Steps below 2 (8px) are optical correction only.
|
|
10
|
+
|
|
11
|
+
| Name | Value | Use |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `Inset p-4` | 16px | Padding inside a surface. p-3 for a dense card, px-3 py-2 for a row. |
|
|
14
|
+
| `Stack, within a group gap-1.5 – gap-2` | 6–8px | Label to field, title to description. |
|
|
15
|
+
| `Stack, between groups gap-4 – gap-6` | 16–24px | Field to field, card to card. |
|
|
16
|
+
| `Stack, between sections gap-12+` | 48px+ | Major regions of a page. |
|
|
17
|
+
| `Inline gap-2 – gap-3` | 8–12px | Icon to label, control to control. |
|
|
18
|
+
|
|
19
|
+
## The control ladder
|
|
20
|
+
|
|
21
|
+
Comfortable values; data-density="compact" (or <Density>) redefines every step for a region.
|
|
22
|
+
|
|
23
|
+
| Name | Value | Use |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `h-(--control-xs)` | 28px · compact 24 | Inline chips, tree rows. |
|
|
26
|
+
| `h-(--control-sm)` | 32px · compact 28 | Compact buttons, toolbar controls. |
|
|
27
|
+
| `h-(--control-md)` | 40px · compact 32 | The default: inputs, buttons, selects, menu triggers. |
|
|
28
|
+
| `h-(--control-cta)` | 44px · compact 36 | The primary call to action; clears the touch floor on its own. |
|
|
29
|
+
| `h-(--control-lg)` | 48px · compact 40 | Prominent or touch-first actions. |
|
|
30
|
+
|
|
31
|
+
## Icons and touch
|
|
32
|
+
|
|
33
|
+
| Name | Value | Use |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `text-base (16px)` | sm | Inline with label-md / body-sm. |
|
|
36
|
+
| `text-xl (20px)` | md · default | The default UI glyph beside label-lg. |
|
|
37
|
+
| `hit-target` | 44×44px | Keeps a visually small control's hit area at the WCAG floor without moving layout. |
|
|
38
|
+
|
|
39
|
+
## Rules
|
|
40
|
+
|
|
41
|
+
1. **Every margin, padding and gap is a step on the 4px scale.** Off-scale values break the rhythm that lets adjacent components line up without coordination.
|
|
42
|
+
2. **Padding, gaps and insets in a component sit on the spacing scale: a step (p-4, gap-2) or --spacing(n) inside an arbitrary value (p-[var(--card-p,--spacing(6))]).** The density (Character) control works by retuning --spacing, so anything on the scale moves with it. A literal p-[1.5rem] or padding: "12px" stays put while everything around it tightens. Keep fixed rem/px for what must not scale: a 1px hairline, a ring width. *Enforced by design-lint: fixed-spacing (library source).*
|
|
43
|
+
3. **Related things sit closer than unrelated things.** Proximity groups content without a divider line. Use space first; reach for a border only when space is not enough.
|
|
44
|
+
4. **Space lives on the container (gap), not on children (margins).** gap survives reordering, conditional children and RTL; per-child margins do not.
|
|
45
|
+
5. **Control heights read the --control-* ladder.** A density scope retunes every control at once. A hardcoded height has to be found and overridden by hand. *Enforced by sable-lint: raw-control-height.*
|
|
46
|
+
6. **Single-column content inside a card stacks with flex flex-col and a gap, not CSS grid.** A grid track sizes to its widest min-content child, so one long unbreakable string (a URL, a code line, a table) blows the column out past the card. A flex column lets children shrink to the card.
|
|
47
|
+
7. **A compact surface drops one whole step; a spacious one adds one.** Moving in whole steps keeps density changes proportional.
|
|
48
|
+
8. **Every interactive element clears a 44×44px hit area.** WCAG 2.5.5. Use padding or hit-target to grow the hit area; never a bigger glyph.
|
|
49
|
+
9. **Borderless controls get roughly twice the gap of bordered ones.** A border declares where a control ends. Two ghost buttons 12px apart read as one target.
|
|
50
|
+
|
|
51
|
+
## Never
|
|
52
|
+
|
|
53
|
+
- **Never:** Arbitrary values: p-[15px], gap-[7px], mt-[13px]. **Instead:** The nearest scale step. Arbitrary values cannot follow density or a customized scale.
|
|
54
|
+
- **Never:** h-[40px] on a control. **Instead:** h-(--control-md). It will not follow <Density>. *Enforced by sable-lint: raw-control-height.*
|
|
55
|
+
- **Never:** Icons sized with type utilities (text-body-md on an <Icon>). **Instead:** text-{size} scale classes. Type utilities carry line-height and tracking that misalign glyphs.
|
|
56
|
+
- **Never:** A fixed width sized to an English label. **Instead:** padding-inline and max-width with wrapping. German and Finnish run 30–50% longer.
|
|
57
|
+
|
|
58
|
+
## Details
|
|
59
|
+
|
|
60
|
+
### A dense region in one attribute
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
// before
|
|
64
|
+
<div className="[&_button]:h-8 [&_input]:h-8">…</div>
|
|
65
|
+
// after
|
|
66
|
+
<Density value="compact">…</Density>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Every control, menu and popup inside retunes, including portaled overlays.
|
|
70
|
+
|
|
71
|
+
### Grouping by space
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// before
|
|
75
|
+
<Separator /> between every field
|
|
76
|
+
// after
|
|
77
|
+
<Stack gap={3}> between groups (24px), <Stack gap={1}> inside a group (8px)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Two spacing steps do the work of every divider, with less ink. Stack's numeric gap counts --rhythm-unit — two spacing steps — so it follows the density control like every other spacing value.
|
|
81
|
+
|
|
82
|
+
**Related:** `sable docs shape` · `sable docs typography` · `sable docs building-a-tool` · `sable token layout`
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Surfaces & elevation
|
|
2
|
+
|
|
3
|
+
Depth is a step up the surface ladder, chosen by how transient a surface is. Dark mode lifts with lighter tones and edge light; light mode adds soft shadows.
|
|
4
|
+
|
|
5
|
+
**4 surface tokens · 5 elevation roles · 1 shadow per surface · 1 layer scale**
|
|
6
|
+
|
|
7
|
+
## The surface ladder
|
|
8
|
+
|
|
9
|
+
| Name | Value | Use |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `bg-background` | the page | The document body; the furthest-back surface. |
|
|
12
|
+
| `bg-card` | +1 step | Resting containers: cards, panels, dialogs. |
|
|
13
|
+
| `bg-muted` | +1 step, quieter | Wells, code blocks, inactive fills inside a card. |
|
|
14
|
+
| `bg-popover` | +2 steps | Anything that floats: menus, popovers, selects, tooltips. Always distinct from card, so a menu in a dialog never melts into it. |
|
|
15
|
+
| `bg-overlay` | scrim | Behind modals, and pills over imagery. |
|
|
16
|
+
|
|
17
|
+
## Elevation by role
|
|
18
|
+
|
|
19
|
+
Raise by transience — how much a surface floats over content — not by importance.
|
|
20
|
+
|
|
21
|
+
| Name | Value | Use |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `Resting` | no shadow | Page content and flat cards. A hairline border if it needs an edge. |
|
|
24
|
+
| `Raised shadow-sm` | subtle | A draggable card, a sticky header over scrolled content. |
|
|
25
|
+
| `Floating shadow-md` | anchored | Menus, popovers, selects, dropdowns. |
|
|
26
|
+
| `Modal shadow-lg` | with scrim | Dialogs, sheets, the command menu. |
|
|
27
|
+
| `Top shadow-xl` | rare | The toast stack. |
|
|
28
|
+
|
|
29
|
+
## Layers
|
|
30
|
+
|
|
31
|
+
| Name | Value | Use |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `z-(--z-sticky)` | 30 | Sticky in-content chrome: table headers. |
|
|
34
|
+
| `z-(--z-nav)` | 40 | App chrome: header, sidebar, bottom nav. |
|
|
35
|
+
| `z-(--z-overlay)` | 50 | Every portaled transient surface. |
|
|
36
|
+
| `z-(--z-toast)` | 60 | Toasts, above modals. |
|
|
37
|
+
| `z-(--z-skip-link)` | 100 | The skip link, above everything. |
|
|
38
|
+
|
|
39
|
+
## Rules
|
|
40
|
+
|
|
41
|
+
1. **Pick the elevation role, not a shadow value.** Elevation is a five-step system. A custom box-shadow cannot follow a theme or the light/dark split.
|
|
42
|
+
2. **Floating surfaces use bg-popover, resting ones bg-card. Anything with shadow-md or above floats: menus, popovers, selects, tooltips, dialogs, sheets, toasts, a floating panel.** In dark mode popover is guaranteed lighter than card, so a menu opened inside a dialog always reads as on top of it. In light mode several presets make background, card and popover the same colour, so the fill alone never carries elevation. *Enforced by themes.test.ts + sable-lint: floating-surface.*
|
|
43
|
+
3. **A floating surface's edge is border-border-floating (or ring-border-floating), never the full border-border. The shadow carries the elevation.** A full hairline plus a shadow stacks into a heavy dark outline. border-floating is a faint alpha edge: it defines the shape against a same-coloured page in light mode and reads as a lit edge in dark mode, where shadows barely show. *Enforced by sable-lint: floating-surface.*
|
|
44
|
+
4. **A recessed area inside a surface (a segmented-control track, a pad, a preview well) is sable-well, and it takes no outline. Whatever sits on the well takes the surface's own colour back (bg-popover inside a popover).** sable-well mixes a fixed amount of ink into the surface it sits in, so it is the same visible step on the page, in a card or in a floating panel, in every theme and mode. A fixed bg-muted can nearly vanish inside a popover in dark mode, and adding an outline to every well to compensate stacks a panel into boxes.
|
|
45
|
+
5. **One shadow per surface; never a heavy shadow with a heavy border.** Stacked depth cues read as decoration rather than position.
|
|
46
|
+
6. **Do not elevate a static section to make it stand out.** Elevation means 'this floats'. Use a tonal step or space for emphasis.
|
|
47
|
+
7. **For modals, the scrim is the primary depth cue.** The scrim is what tells the user the page behind is inert; the shadow is secondary.
|
|
48
|
+
8. **Stack with the named layer scale.** Overlays portal to the end of body and order themselves by open order; the layer only has to clear in-flow chrome.
|
|
49
|
+
|
|
50
|
+
## Never
|
|
51
|
+
|
|
52
|
+
- **Never:** z-[9999] or any raw z-index. **Instead:** z-(--z-overlay) and the other named layers. Raw z-indexes become a bidding war that a future overlay always loses. *Enforced by sable-lint: raw-z-index.*
|
|
53
|
+
- **Never:** shadow-[0_8px_30px_rgba(0,0,0,.2)] or any custom shadow. **Instead:** shadow-sm … shadow-xl by role. Custom shadows ignore the theme's light/dark elevation recipes.
|
|
54
|
+
- **Never:** bg-card on a popover, or bg-popover on a resting card. **Instead:** Match the token to the role. Swapping them is how floating surfaces melt into the surface beneath.
|
|
55
|
+
|
|
56
|
+
## Details
|
|
57
|
+
|
|
58
|
+
### A menu inside a dialog
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// before
|
|
62
|
+
Dialog bg-card · Menu bg-card — identical in dark mode
|
|
63
|
+
// after
|
|
64
|
+
Dialog bg-card · Menu bg-popover — one step lighter, plus edge light
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The menu reads as above the dialog without any prop.
|
|
68
|
+
|
|
69
|
+
**Related:** `sable docs shape` · `sable docs token-intent`
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Typography
|
|
2
|
+
|
|
3
|
+
Type is a closed vocabulary of paired utilities. Each one bundles size, weight, line-height and tracking, and is picked by role rather than by size.
|
|
4
|
+
|
|
5
|
+
**6 families · 8 everyday tiers · 2 text colors · 1 utility per text node**
|
|
6
|
+
|
|
7
|
+
## The everyday set
|
|
8
|
+
|
|
9
|
+
Product UI is built almost entirely from these eight. Reach outside them on purpose.
|
|
10
|
+
|
|
11
|
+
| Name | Value | Use |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `text-heading-1` | 36px · 600 | The page title. Exactly one per route. |
|
|
14
|
+
| `text-heading-2` | 28px · 600 | A major section of the page. |
|
|
15
|
+
| `text-title-lg` | 16px · 600 | Names a component: card, dialog, sheet, list row. |
|
|
16
|
+
| `text-title-sm` | 14px · 600 | Names a dense component: compact card, table section. |
|
|
17
|
+
| `text-body-md` | 15px · 400 | The default paragraph, description and help text. |
|
|
18
|
+
| `text-body-sm` | 13px · 400 | Secondary copy and table cells. |
|
|
19
|
+
| `text-label-lg` | 14px · 600 | Buttons, field labels, tabs. |
|
|
20
|
+
| `text-caption` | 12px · 400 | Timestamps, footnotes, meta rows. |
|
|
21
|
+
|
|
22
|
+
## The six families
|
|
23
|
+
|
|
24
|
+
Every tier belongs to one family, and the family is the decision. Size follows from it.
|
|
25
|
+
|
|
26
|
+
| Name | Value | Use |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `Display display-lg…4xl` | 48–120px · 700–800 | Type as image: landing heroes, campaign numbers. Never in product chrome; never two per view. |
|
|
29
|
+
| `Heading heading-1…4` | 36 / 28 / 22 / 18px · 600 | The document outline (h1–h4). Keep the order; never skip a level to get a size. |
|
|
30
|
+
| `Title title-lg · title-sm` | 16 / 14px · 600 | Names a UI part without claiming a document heading. |
|
|
31
|
+
| `Body body-lg/md/sm/xs` | 17 / 15 / 13 / 12px · 400 | Reading text. -lg is the lede (carries text-wrap: pretty); -xs is a dense-UI floor, not a prose size. |
|
|
32
|
+
| `Label label-lg/md/sm · caption` | 14 / 12 / 11px · 600; caption 12 · 400 | Functional, scannable text of one to three words. Caption is also the group-label tier: nav sections, menu groups. |
|
|
33
|
+
| `Mono mono-xl/lg/md/caption/sm` | 28 / 20 / 13 / 12 / 11px · tabular | Numbers that are the content, IDs, hashes, code, aligned figures. |
|
|
34
|
+
|
|
35
|
+
## Text color
|
|
36
|
+
|
|
37
|
+
| Name | Value | Use |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| `text-foreground` | full contrast | Primary text: headings, body, values. |
|
|
40
|
+
| `text-muted-foreground` | secondary contrast (AA on every surface) | Supporting text: descriptions, captions, meta, placeholders. |
|
|
41
|
+
|
|
42
|
+
## Rules
|
|
43
|
+
|
|
44
|
+
1. **One paired utility per text node. Compose it with a color and margin only.** Each utility is a tuned set of size, weight, leading and tracking. Splitting it into text-* + font-* + tracking-* breaks the tuning and every theme override of it. *Enforced by sable-lint: decomposed-type.*
|
|
45
|
+
2. **Pick the tier by role, not by the size it renders at.** A card title is title-lg even when body-lg is the same size: role fixes the weight (600 vs 400) and the rhythm around it.
|
|
46
|
+
3. **Two text colors: foreground and muted-foreground.** Both are contrast-tested against every surface in every theme. A third grey, or an opacity step, is untested contrast.
|
|
47
|
+
4. **One heading-1 per route; headings descend without skipping levels.** The heading outline is how screen-reader users navigate a page. Restyle with a tier rather than jumping a level for its size.
|
|
48
|
+
5. **Hierarchy comes from a clear jump (heading → title → body), then weight and space, before size.** Two sizes one step apart read as the same level. Space and weight separate levels more cheaply than another size.
|
|
49
|
+
6. **Cap reading text at --measure-prose (65ch).** Past roughly 75 characters the eye loses the line return. A wide region can still hold a 65ch column.
|
|
50
|
+
7. **Numbers that must align use a mono tier (tabular figures).** Proportional digits change width as values update, so totals and columns shimmy.
|
|
51
|
+
8. **text-wrap: balance on headings, pretty on ledes, neither on long prose.** balance evens short headlines; pretty only refuses a one-word last line. Both cost layout work that long prose does not repay.
|
|
52
|
+
9. **A mark attached to a line of type (a dot, a bullet, a timeline marker, a leading icon) sits in a box one line tall (h-[1lh] items-center) and is sized in em.** 1lh is the line-height of the text beside it, so the mark centres on that line in every tier, density and theme. A px size with a mt-[3px] nudge is right for one font at one size and wrong everywhere else.
|
|
53
|
+
|
|
54
|
+
## Never
|
|
55
|
+
|
|
56
|
+
- **Never:** Opacity on text (text-foreground/60, opacity-70 on a paragraph). **Instead:** text-muted-foreground. An alpha step over an unknown surface has unknown contrast, and it shifts per theme. *Enforced by sable-lint: text-opacity.*
|
|
57
|
+
- **Never:** A hand-picked size, weight or tracking (text-[15px], font-medium, tracking-wide). **Instead:** The paired utility for the role. Off-vocabulary type cannot be retuned by a theme or the customizer, so it drifts the moment either changes. *Enforced by sable-lint: decomposed-type.*
|
|
58
|
+
- **Never:** Uppercase in product chrome: nav and menu group labels, page eyebrows, table headers, section titles. **Instead:** Sentence-case text-caption or text-label-md in muted-foreground; hierarchy from size, weight and space. Tracked capitals spread across a UI read as shouting and slow word recognition. The eyebrow tier (label-xs / overline) is an editorial accent: at most one per marketing view.
|
|
59
|
+
- **Never:** Display tiers inside product UI, or two display sizes in one view. **Instead:** heading-1 for a page title; mono-xl for a metric. Display is type as image. In chrome it competes with the content it is supposed to frame.
|
|
60
|
+
- **Never:** Justified text, or reading copy below body-sm. **Instead:** Ragged-right at body-md; body-xs only for dense data. Justification opens rivers on the web, which has no hyphenation control; 12px prose fails people with low vision.
|
|
61
|
+
- **Never:** A new font family, or raw font-feature-settings / font-variation-settings. **Instead:** The theme's sans, mono and optional display; font-variant-numeric and font-weight. Raw axis tags silently do nothing on a fallback font, and every theme declares a fallback stack.
|
|
62
|
+
|
|
63
|
+
## Details
|
|
64
|
+
|
|
65
|
+
### Steady numbers
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
// before
|
|
69
|
+
<span className="text-body-md">{price}</span>
|
|
70
|
+
// after
|
|
71
|
+
<span className="text-mono-md">{price}</span>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Mono tiers set tabular-nums, so a ticking value stops nudging its neighbors sideways.
|
|
75
|
+
|
|
76
|
+
### Optically centred labels
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// before
|
|
80
|
+
<button className="h-(--control-md) px-4 items-center">Save</button>
|
|
81
|
+
// after
|
|
82
|
+
<Button>Save</Button> {/* label wrapped in .sable-label-trim */}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
text-box: trim-both cap alphabetic centres the cap-height band the eye reads, not the line box. Measured: Helvetica otherwise sits a full 1px high on a 40px control.
|
|
86
|
+
|
|
87
|
+
### A marker centred on its line
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
// before
|
|
91
|
+
<span className="mt-[5px] size-2 rounded-full bg-primary" />
|
|
92
|
+
// after
|
|
93
|
+
<span className="flex h-[1lh] items-center"><span className="size-[0.5em] rounded-full bg-primary" /></span>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The wrapper is exactly one line of the adjacent text tall, so the dot centres on the first line whatever the tier; em sizing keeps it in proportion when the type scales. Stepper indicator="dot" is built this way.
|
|
97
|
+
|
|
98
|
+
### Balanced headings
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// before
|
|
102
|
+
<h2 className="text-heading-2">Ship faster with fewer review rounds</h2>
|
|
103
|
+
// after
|
|
104
|
+
<h2 className="text-heading-2 text-balance">Ship faster with fewer review rounds</h2>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Evens line lengths so a two-line heading does not end on a single orphaned word.
|
|
108
|
+
|
|
109
|
+
### Secondary text without opacity
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
// before
|
|
113
|
+
<p className="text-foreground/60">Updated 2m ago</p>
|
|
114
|
+
// after
|
|
115
|
+
<p className="text-caption text-muted-foreground">Updated 2m ago</p>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
muted-foreground is contrast-tested on every surface; a 60% alpha is not.
|
|
119
|
+
|
|
120
|
+
**Related:** `sable docs spacing` · `sable docs accessibility` · `sable token typography` · `sable token typographyOverrides`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# AspectBox
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Reserves space via aspect-ratio so async media loads with zero layout shift. Wrap images, video, embeds.
|
|
6
|
+
|
|
7
|
+
**Keywords:** aspect box, aspect ratio, media, layout shift
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use AspectBox to reserve stable aspect-ratio space for async media, embeds, and responsive previews.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it around images, video, and embeds that should not shift layout while loading.
|
|
16
|
+
- Do: Match the ratio to the expected media or design slot.
|
|
17
|
+
- Don't: Do not use AspectBox for content whose height should grow with text or controls.
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Name | Type | Default | Description |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| ratio | number | 16/9 | Width / height ratio (e.g. 1 for square, 4/5 for portrait). |
|
|
24
|
+
|
|
25
|
+
## Related
|
|
26
|
+
|
|
27
|
+
- Components: Video, Dialog, Browser
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Cover image slot
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { AspectBox } from "@sableui/react"
|
|
35
|
+
|
|
36
|
+
<AspectBox ratio={16 / 9}><img src={post.coverUrl} alt={post.title} className="size-full object-cover" /></AspectBox>
|
|
37
|
+
```
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Bleed
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Escape a width-capped or inset ancestor to span the full CONTENT area (container-query units, so it stops at a fixed Sidebar instead of bleeding under it). Hero bands, profile covers, full-bleed media inside a capped Page.
|
|
6
|
+
|
|
7
|
+
**Keywords:** bleed, full bleed, media band, layout
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Bleed to let a band or media element escape an inset or width-capped ancestor while staying within the content area.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it for hero bands, profile covers, or media that should align beyond a capped page column.
|
|
16
|
+
- Do: Keep surrounding content inside Region or Page so the bleed remains intentional.
|
|
17
|
+
- Don't: Do not use Bleed to place content under fixed app chrome or sidebars.
|
|
18
|
+
|
|
19
|
+
## Related
|
|
20
|
+
|
|
21
|
+
- Components: Page, Region, AspectBox
|
|
22
|
+
|
|
23
|
+
## Examples
|
|
24
|
+
|
|
25
|
+
### Full-bleed hero inside a capped page
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { AspectBox, Bleed, Page, PageHeader } from "@sableui/react"
|
|
29
|
+
|
|
30
|
+
<Page max="feed"><Bleed><AspectBox ratio={3}><img src="/cover.jpg" alt="" className="size-full object-cover" /></AspectBox></Bleed><PageHeader title="Profile" /></Page>
|
|
31
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Center
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Horizontally center a block, optionally capping its width (commonly "prose" for a reading column inside a wider layout).
|
|
6
|
+
|
|
7
|
+
**Keywords:** center, measure, reading width, layout
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Center to center a block and optionally cap its width inside a wider layout.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it for reading columns, forms, or compact content inside a broad Region.
|
|
16
|
+
- Do: Choose a named max when the content role matches Sable's measure tokens.
|
|
17
|
+
- Don't: Do not use Center for full page inset or app chrome; use Region or AppShell.
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Name | Type | Default | Description |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| max | "prose" \| "form" \| "feed" \| "content" \| "wide" \| CSS length | — | Cap width: a named region token or any raw CSS length. |
|
|
24
|
+
|
|
25
|
+
## Related
|
|
26
|
+
|
|
27
|
+
- Components: Region, Page, Stack
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
### Reading column in a wide page
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import { Center } from "@sableui/react"
|
|
35
|
+
|
|
36
|
+
<Center max="prose"><p className="text-body-lg">Long-form description text that should hold a comfortable reading measure even inside a wide dashboard region.</p></Center>
|
|
37
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Columns
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
An explicit N-column track (default 12) for proportioned layouts — pair with Col spans (list-detail, asymmetric editorial layouts). For uniform card grids prefer Grid minItemWidth.
|
|
6
|
+
|
|
7
|
+
**Keywords:** columns, 12 column, span, layout
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Columns and Col for proportioned layouts that need explicit spans, such as list-detail or asymmetric panes.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use breakpoint span maps when the same layout needs to collapse cleanly on smaller screens.
|
|
16
|
+
- Do: Use Columns for proportional composition, not for uniform card wrapping.
|
|
17
|
+
- Don't: Do not use column spans as a replacement for Region width or text measure.
|
|
18
|
+
|
|
19
|
+
### Anatomy
|
|
20
|
+
|
|
21
|
+
| Part | Required | Description |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Columns | yes | The explicit column track container. |
|
|
24
|
+
| Col | yes | A child cell with span and optional start placement. |
|
|
25
|
+
|
|
26
|
+
## Subcomponents
|
|
27
|
+
|
|
28
|
+
| Name | Type | Description |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| Col | span?: number \| breakpoint map · start?: number \| breakpoint map | A cell in the track: columns to span (default 1) and optional 1-based start column. Both accept breakpoint maps ({ base: 12, lg: 8 }). |
|
|
31
|
+
|
|
32
|
+
## Props
|
|
33
|
+
|
|
34
|
+
| Name | Type | Default | Description |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| count | number \| breakpoint map | 12 | Columns in the track; a breakpoint map ({ base: 2, md: 6, lg: 12 }) thins the track on small screens. |
|
|
37
|
+
| gap | number \| string | var(--grid-gutter) | Gap between columns and rows. Number = rhythm units (×8px); string = raw CSS. The column gap is capped so all gaps together never take more than 75% of the track's width — a large gap can't push a narrow screen into horizontal scroll, and at normal desktop widths it renders exactly as given. The row gap is never capped. |
|
|
38
|
+
|
|
39
|
+
## Related
|
|
40
|
+
|
|
41
|
+
- Components: Grid, SplitPane, Region
|
|
42
|
+
|
|
43
|
+
## Examples
|
|
44
|
+
|
|
45
|
+
### List-detail (8/4)
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { Card, CardHeader, CardTitle, Col, Columns } from "@sableui/react"
|
|
49
|
+
|
|
50
|
+
<Columns count={12}><Col span={{ base: 12, lg: 8 }}><Card><CardHeader><CardTitle>Posts</CardTitle></CardHeader></Card></Col><Col span={{ base: 12, lg: 4 }}><Card variant="flat"><CardHeader><CardTitle>Filters</CardTitle></CardHeader></Card></Col></Columns>
|
|
51
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# GridOverlay
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
Dev/docs structure visualizer — overlays the column grid so the layout skeleton is legible. Place inside a position:relative container; never ships in product UI.
|
|
6
|
+
|
|
7
|
+
**Keywords:** grid overlay, debug, layout visualizer, docs
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use GridOverlay only as a development or documentation visualizer for the column structure.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it inside a position-relative layout while debugging alignment.
|
|
16
|
+
- Do: Keep it gated behind docs, debug, or development-only state.
|
|
17
|
+
- Don't: Do not ship GridOverlay as visible product UI.
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Name | Type | Default | Description |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| columns | number | 12 | Number of column bands. |
|
|
24
|
+
| gap | string | var(--grid-gutter) | Gap between bands. |
|
|
25
|
+
| inset | boolean | true | Align bands to the fluid page inset. |
|
|
26
|
+
| show | boolean | true | Toggle visibility. |
|
|
27
|
+
|
|
28
|
+
## Related
|
|
29
|
+
|
|
30
|
+
- Components: Grid, Columns, Region
|
|
31
|
+
|
|
32
|
+
## Examples
|
|
33
|
+
|
|
34
|
+
### Debugging a layout
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { Card, CardContent, Col, Columns, GridOverlay } from "@sableui/react"
|
|
38
|
+
|
|
39
|
+
<div className="relative"><GridOverlay show={debug} /><Columns count={12}><Col span={8}><Card><CardContent><p className="text-body-md">Main</p></CardContent></Card></Col><Col span={4}><Card><CardContent><p className="text-body-md">Rail</p></CardContent></Card></Col></Columns></div>
|
|
40
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Grid
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
CSS Grid within a region. Two modes: fixed/responsive cols count, or minItemWidth auto-fit so cells breathe with NO breakpoints (the default choice for card grids).
|
|
6
|
+
|
|
7
|
+
**Keywords:** grid, card grid, auto fit, layout
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Grid for uniform grid layouts inside a Region, especially card grids that can breathe responsively.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Prefer minItemWidth for responsive card grids that do not need breakpoint-specific spans.
|
|
16
|
+
- Do: Use explicit columns only when the design truly needs a fixed track count.
|
|
17
|
+
- Don't: Do not use Grid to set a page's reading width; use Region or Center.
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Name | Type | Default | Description |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| cols | number \| { base?, sm?, md?, lg?, xl?, "2xl"? } | 12 | Fixed column count or a mobile-first breakpoint map ({ base: 2, md: 4 }). Ignored when minItemWidth is set. |
|
|
24
|
+
| minItemWidth | string | — | Auto-fit mode: repeat(auto-fit, minmax(minItemWidth, 1fr)) — the grid grows/wraps responsively without breakpoints. e.g. "16rem". |
|
|
25
|
+
| gap | number \| string | var(--grid-gutter) | Gap between cells. Number = rhythm units (×8px); string = raw CSS. |
|
|
26
|
+
|
|
27
|
+
## Related
|
|
28
|
+
|
|
29
|
+
- Components: Region, Columns, Card
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
### Card grid that breathes (no breakpoints)
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Card, CardHeader, CardTitle, Grid } from "@sableui/react"
|
|
37
|
+
|
|
38
|
+
<Grid minItemWidth="16rem" gap={3}><Card><CardHeader><CardTitle>Alpha</CardTitle></CardHeader></Card><Card><CardHeader><CardTitle>Beta</CardTitle></CardHeader></Card><Card><CardHeader><CardTitle>Gamma</CardTitle></CardHeader></Card></Grid>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Responsive fixed columns
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { Badge, Grid } from "@sableui/react"
|
|
45
|
+
|
|
46
|
+
<Grid cols={{ base: 1, md: 2, lg: 4 }}><Badge appearance="plain" tone="standard" dot /><Badge appearance="plain" tone="warning" dot /><Badge appearance="plain" tone="neutral" dot /><Badge appearance="plain" tone="destructive" dot /></Grid>
|
|
47
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Page
|
|
2
|
+
|
|
3
|
+
**Status:** stable
|
|
4
|
+
|
|
5
|
+
A route shell: Region (inset + opt-in max cap) whose children flow as a vertically-rhythmic Stack. Use as the top-level wrapper of a route's content — distinct from AppShell (the frame around routes) and PageHeader (the heading block inside).
|
|
6
|
+
|
|
7
|
+
**Keywords:** page, route shell, region, stack
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Use Page as a route content shell that combines Region sizing with vertical rhythm.
|
|
12
|
+
|
|
13
|
+
### Best practices
|
|
14
|
+
|
|
15
|
+
- Do: Use it as the top-level wrapper for route content after application chrome.
|
|
16
|
+
- Do: Set max by the page's content role rather than by the current viewport.
|
|
17
|
+
- Don't: Do not confuse Page with AppShell; Page is content, AppShell is the frame around routes.
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Name | Type | Default | Description |
|
|
22
|
+
| --- | --- | --- | --- |
|
|
23
|
+
| gap | number \| string | 4 | Vertical gap between stacked page sections. Number = rhythm units (×8px, so 4 = 32px); string = raw CSS. |
|
|
24
|
+
| align | "start" \| "center" \| "end" \| "stretch" | "stretch" | Cross-axis alignment of the stacked sections. |
|
|
25
|
+
| …RegionProps | as, inset, max, bleed, safeArea | — | All Region props pass through (Page IS a Region). |
|
|
26
|
+
|
|
27
|
+
## Related
|
|
28
|
+
|
|
29
|
+
- Components: Region, Stack, PageHeader, AppShell
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
### Feed route
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { Card, CardContent, Page, PageHeader } from "@sableui/react"
|
|
37
|
+
|
|
38
|
+
<Page as="main" max="feed"><PageHeader title="Home" description="Latest posts from artists you follow." /><Card><CardContent><p className="text-body-md">First post.</p></CardContent></Card></Page>
|
|
39
|
+
```
|