@sableui/react 0.0.0-stage → 0.1.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 (247) hide show
  1. package/AGENTS.md +157 -0
  2. package/CHANGELOG.md +50 -0
  3. package/CONTRIBUTING.md +221 -0
  4. package/DESIGN.md +548 -0
  5. package/LICENSE +21 -0
  6. package/README.md +194 -2
  7. package/THIRD_PARTY_NOTICES.md +44 -0
  8. package/bin/sable-lint.mjs +0 -0
  9. package/bin/sable.mjs +1223 -0
  10. package/dist/_contract-CYScOdf5.d.ts +150 -0
  11. package/dist/chunk-3IWZYBGH.js +398 -0
  12. package/dist/chunk-5Z5KLOAU.js +167 -0
  13. package/dist/chunk-7MG4SHYX.js +15 -0
  14. package/dist/chunk-ADLLDWLW.js +15 -0
  15. package/dist/chunk-EP55KSYA.js +14 -0
  16. package/dist/chunk-J23ELQQZ.js +9396 -0
  17. package/dist/chunk-JI7WJP2L.js +286 -0
  18. package/dist/chunk-K7RUHUGC.js +14 -0
  19. package/dist/chunk-KO3NAEBF.js +15 -0
  20. package/dist/chunk-L4IEIQQA.js +14 -0
  21. package/dist/chunk-L6Y46C62.js +270 -0
  22. package/dist/chunk-NEQNXI2G.js +25 -0
  23. package/dist/chunk-PAOZBZME.js +40 -0
  24. package/dist/chunk-RWP4XBSK.js +139 -0
  25. package/dist/chunk-SRQWQUTW.js +63 -0
  26. package/dist/chunk-YXBOYWBN.js +15 -0
  27. package/dist/foundations/index.d.ts +88 -0
  28. package/dist/foundations/index.js +792 -0
  29. package/dist/icon/index.d.ts +60 -0
  30. package/dist/icon/index.js +2 -0
  31. package/dist/icon-sets/brands.d.ts +44 -0
  32. package/dist/icon-sets/brands.js +147 -0
  33. package/dist/icon-sets/reicon.d.ts +8 -0
  34. package/dist/icon-sets/reicon.js +1 -0
  35. package/dist/index.d.ts +7013 -0
  36. package/dist/index.js +15462 -0
  37. package/dist/ios/chrome.css +83 -0
  38. package/dist/ios/frame.css +5 -0
  39. package/dist/ios/index.d.ts +70 -0
  40. package/dist/ios/index.js +313 -0
  41. package/dist/ios/preview.d.ts +64 -0
  42. package/dist/ios/preview.js +133 -0
  43. package/dist/ios/styles.css +77 -0
  44. package/dist/layout/index.d.ts +280 -0
  45. package/dist/layout/index.js +2 -0
  46. package/dist/meta-6dUzgC6M.d.ts +205 -0
  47. package/dist/patterns/index.d.ts +36 -0
  48. package/dist/patterns/index.js +934 -0
  49. package/dist/presets/index.d.ts +37 -0
  50. package/dist/presets/index.js +117 -0
  51. package/dist/registry-CyL28OzJ.d.ts +28 -0
  52. package/dist/styles.css +1893 -0
  53. package/dist/theme-provider-BWcizhAt.d.ts +105 -0
  54. package/dist/themes/atelier/atelier.css +213 -0
  55. package/dist/themes/atelier/index.d.ts +9 -0
  56. package/dist/themes/atelier/index.js +1 -0
  57. package/dist/themes/base/base.css +443 -0
  58. package/dist/themes/base/index.d.ts +9 -0
  59. package/dist/themes/base/index.js +1 -0
  60. package/dist/themes/ember/ember.css +270 -0
  61. package/dist/themes/ember/index.d.ts +9 -0
  62. package/dist/themes/ember/index.js +1 -0
  63. package/dist/themes/index.d.ts +10 -0
  64. package/dist/themes/index.js +8 -0
  65. package/dist/themes/meridian/index.d.ts +9 -0
  66. package/dist/themes/meridian/index.js +1 -0
  67. package/dist/themes/meridian/meridian.css +255 -0
  68. package/dist/themes/poppy/index.d.ts +6 -0
  69. package/dist/themes/poppy/index.js +1 -0
  70. package/dist/themes/poppy/poppy.css +174 -0
  71. package/dist/themes/prism/index.d.ts +6 -0
  72. package/dist/themes/prism/index.js +1 -0
  73. package/dist/themes/prism/prism.css +183 -0
  74. package/dist/themes/verdant/index.d.ts +9 -0
  75. package/dist/themes/verdant/index.js +1 -0
  76. package/dist/themes/verdant/verdant.css +210 -0
  77. package/dist/themes.css +30 -0
  78. package/llms/components/accordion.md +93 -0
  79. package/llms/components/app-header.md +70 -0
  80. package/llms/components/app-shell.md +117 -0
  81. package/llms/components/attachment.md +105 -0
  82. package/llms/components/avatar.md +141 -0
  83. package/llms/components/badge.md +117 -0
  84. package/llms/components/banner.md +119 -0
  85. package/llms/components/bottom-nav.md +80 -0
  86. package/llms/components/breadcrumbs.md +75 -0
  87. package/llms/components/browser.md +67 -0
  88. package/llms/components/bubble.md +119 -0
  89. package/llms/components/button.md +199 -0
  90. package/llms/components/calendar.md +75 -0
  91. package/llms/components/card.md +135 -0
  92. package/llms/components/carousel.md +87 -0
  93. package/llms/components/chat-composer.md +126 -0
  94. package/llms/components/checkbox-card.md +98 -0
  95. package/llms/components/checkbox.md +90 -0
  96. package/llms/components/code-block.md +81 -0
  97. package/llms/components/code.md +62 -0
  98. package/llms/components/collapsible.md +83 -0
  99. package/llms/components/color-field.md +100 -0
  100. package/llms/components/combobox.md +119 -0
  101. package/llms/components/command-menu.md +158 -0
  102. package/llms/components/context-card.md +73 -0
  103. package/llms/components/context-menu.md +79 -0
  104. package/llms/components/data-table.md +99 -0
  105. package/llms/components/date-picker.md +97 -0
  106. package/llms/components/description.md +96 -0
  107. package/llms/components/destructive-action-modal.md +76 -0
  108. package/llms/components/dialog.md +127 -0
  109. package/llms/components/dots-menu.md +64 -0
  110. package/llms/components/empty-state.md +113 -0
  111. package/llms/components/entity.md +67 -0
  112. package/llms/components/feedback.md +63 -0
  113. package/llms/components/fieldset.md +106 -0
  114. package/llms/components/file-dropzone.md +103 -0
  115. package/llms/components/form.md +111 -0
  116. package/llms/components/gliding-highlight.md +84 -0
  117. package/llms/components/inline-status.md +62 -0
  118. package/llms/components/input.md +127 -0
  119. package/llms/components/kbd.md +57 -0
  120. package/llms/components/label.md +73 -0
  121. package/llms/components/marker.md +99 -0
  122. package/llms/components/marquee.md +66 -0
  123. package/llms/components/menu.md +122 -0
  124. package/llms/components/message-scroller.md +110 -0
  125. package/llms/components/message.md +114 -0
  126. package/llms/components/meter.md +124 -0
  127. package/llms/components/middle-truncate.md +62 -0
  128. package/llms/components/note.md +81 -0
  129. package/llms/components/notification-badge.md +67 -0
  130. package/llms/components/number-field.md +70 -0
  131. package/llms/components/otp-field.md +101 -0
  132. package/llms/components/page-header.md +76 -0
  133. package/llms/components/page-loader.md +67 -0
  134. package/llms/components/pagination.md +92 -0
  135. package/llms/components/panel-group.md +120 -0
  136. package/llms/components/panel-section.md +122 -0
  137. package/llms/components/phone.md +70 -0
  138. package/llms/components/pill.md +73 -0
  139. package/llms/components/popover.md +79 -0
  140. package/llms/components/progress.md +98 -0
  141. package/llms/components/radio-card.md +90 -0
  142. package/llms/components/radio.md +86 -0
  143. package/llms/components/relative-time.md +60 -0
  144. package/llms/components/scroller.md +88 -0
  145. package/llms/components/scrub-field.md +97 -0
  146. package/llms/components/search-input.md +84 -0
  147. package/llms/components/select.md +106 -0
  148. package/llms/components/separator.md +70 -0
  149. package/llms/components/settings-menu.md +139 -0
  150. package/llms/components/sheet.md +97 -0
  151. package/llms/components/show-more.md +59 -0
  152. package/llms/components/side-nav.md +106 -0
  153. package/llms/components/sidebar.md +87 -0
  154. package/llms/components/skeleton.md +62 -0
  155. package/llms/components/slider.md +114 -0
  156. package/llms/components/spinner.md +87 -0
  157. package/llms/components/status-bar.md +91 -0
  158. package/llms/components/status-dot.md +70 -0
  159. package/llms/components/stepper.md +125 -0
  160. package/llms/components/streaming-text.md +86 -0
  161. package/llms/components/switch.md +87 -0
  162. package/llms/components/table.md +112 -0
  163. package/llms/components/tabs.md +84 -0
  164. package/llms/components/textarea.md +88 -0
  165. package/llms/components/theme-selector.md +95 -0
  166. package/llms/components/theme-switcher.md +71 -0
  167. package/llms/components/thinking-orb.md +91 -0
  168. package/llms/components/toast.md +130 -0
  169. package/llms/components/toggle.md +95 -0
  170. package/llms/components/toolbar.md +168 -0
  171. package/llms/components/tooltip.md +93 -0
  172. package/llms/components/tree.md +227 -0
  173. package/llms/components/video.md +66 -0
  174. package/llms/components/workspace.md +138 -0
  175. package/llms/foundations/accessibility.md +60 -0
  176. package/llms/foundations/motion.md +96 -0
  177. package/llms/foundations/scrolling.md +41 -0
  178. package/llms/foundations/shape.md +73 -0
  179. package/llms/foundations/spacing.md +82 -0
  180. package/llms/foundations/surfaces.md +69 -0
  181. package/llms/foundations/typography.md +120 -0
  182. package/llms/layout/aspect-box.md +37 -0
  183. package/llms/layout/bleed.md +31 -0
  184. package/llms/layout/center.md +37 -0
  185. package/llms/layout/columns.md +51 -0
  186. package/llms/layout/grid-overlay.md +40 -0
  187. package/llms/layout/grid.md +47 -0
  188. package/llms/layout/page.md +39 -0
  189. package/llms/layout/region.md +49 -0
  190. package/llms/layout/row.md +40 -0
  191. package/llms/layout/safe-area.md +37 -0
  192. package/llms/layout/spacer.md +39 -0
  193. package/llms/layout/split-pane.md +41 -0
  194. package/llms/layout/stack.md +39 -0
  195. package/llms/patterns/auth-page.md +68 -0
  196. package/llms/patterns/brand-landing-page.md +423 -0
  197. package/llms/patterns/chat-panel.md +210 -0
  198. package/llms/patterns/dashboard-page.md +170 -0
  199. package/llms/patterns/feed-page.md +103 -0
  200. package/llms/patterns/landing-page.md +122 -0
  201. package/llms/patterns/list-detail-page.md +100 -0
  202. package/llms/patterns/settings-page.md +121 -0
  203. package/llms/reference/token-intent.md +324 -0
  204. package/llms/reference/utilities.md +129 -0
  205. package/llms/tasks/adding-a-component.md +113 -0
  206. package/llms/tasks/auditing-an-app.md +216 -0
  207. package/llms/tasks/building-a-page.md +109 -0
  208. package/llms/tasks/building-a-tool.md +141 -0
  209. package/llms/tasks/building-an-ios-prototype.md +105 -0
  210. package/llms/tasks/building-with-sable.md +73 -0
  211. package/llms/tasks/choosing-a-component.md +109 -0
  212. package/llms/tasks/converting-an-app.md +139 -0
  213. package/llms/tasks/creating-a-preset.md +43 -0
  214. package/llms/tasks/creating-design-assets.md +96 -0
  215. package/llms/tasks/designing-a-surface.md +115 -0
  216. package/llms/tasks/developing-taste.md +157 -0
  217. package/llms/tasks/getting-started.md +63 -0
  218. package/llms/tasks/learning-from-builds.md +74 -0
  219. package/llms/tasks/reviewing-a-design.md +114 -0
  220. package/llms/tasks/reviewing-a-journey.md +84 -0
  221. package/llms/tasks/reviewing-motion.md +92 -0
  222. package/llms/tasks/using-presets.md +87 -0
  223. package/llms/taste/asset-plan.template.json +53 -0
  224. package/llms/taste/composition-follows-content.md +20 -0
  225. package/llms/taste/hierarchy-is-a-relationship.md +22 -0
  226. package/llms/taste/lessons.json +57 -0
  227. package/llms/taste/motion-needs-a-reason.md +21 -0
  228. package/llms/taste/surface-states-travel-together.md +20 -0
  229. package/llms-full.txt +14906 -0
  230. package/llms.txt +405 -0
  231. package/package.json +185 -3
  232. package/registry.json +18812 -0
  233. package/scripts/agent-setup.mjs +54 -0
  234. package/scripts/design-atlas.mjs +221 -0
  235. package/scripts/design-directions.mjs +123 -0
  236. package/scripts/design-memory.mjs +166 -0
  237. package/scripts/design-preferences.mjs +227 -0
  238. package/scripts/design-presets.mjs +48 -0
  239. package/scripts/grade.mjs +509 -0
  240. package/scripts/lint-rules.mjs +1065 -0
  241. package/scripts/review-evidence.mjs +120 -0
  242. package/scripts/skeleton.mjs +248 -0
  243. package/skills/entry/SKILL.md +34 -0
  244. package/skills/sable/SKILL.md +187 -0
  245. package/skills/sable/agents/openai.yaml +6 -0
  246. package/targets/ios.json +982 -0
  247. package/third-party/reicon-LICENSE.txt +20 -0
@@ -0,0 +1,170 @@
1
+ # DashboardPage
2
+
3
+ App-shell dashboard: sidebar chrome (AppHeader + BottomNav below lg), a wide content region, a stat-card row that breathes, and a data table — the default shape for operator/analytics home routes.
4
+
5
+ **Intent:** Reach for this when a route's job is monitoring or managing many records (metrics, deployments, orders, posts). Not for reading-focused routes (use FeedPage) or single-record routes (use ListDetailPage's detail side).
6
+
7
+ ## Notes
8
+
9
+ - Region cap is `wide` (1280px): dashboards hold tables and multi-column grids, never a reading column.
10
+ - Stat cards: CardHeader carries label (CardDescription) + value + trend badge (CardAction pins it top-right); KPI values are `text-mono-xl` — the metric mono tier, tabular figures — never a heading tier.
11
+ - Stat cards use an explicit cols map (1 → 2 → 4) — a 4-stat row must fold to 2×2, never strand an orphan the way auto-fit can.
12
+ - The table runs flush to the card edges with `<CardContent flush>`, which hands the card's padding to the table (`--table-edge-px`), so the first and last columns line up with the CardHeader at any card density — never hand-set pl-6/pr-6. Numeric columns align end in mono.
13
+ - The Activity list is Entity rows in CardList, inset to the card padding and divided by hairlines — the same list shape as ListDetailPage, never CardContent with hand-tuned px.
14
+ - Believable density is part of the pattern: 7 rows, commit hashes in mono-caption, RelativeTime muted — never 3 rows of filler.
15
+ - The page has exactly one heading-1 (PageHeader); card titles are Title tier, not headings.
16
+ - Chrome (Sidebar) is outside the content grid; the Page region caps only content.
17
+ - Sidebar hides below lg, so the shell's header carries an AppHeader and its footer a BottomNav with the same destinations — never strand mobile users without navigation.
18
+
19
+ ## Accessibility
20
+
21
+ - AppShell provides the skip link and <main id="main-content"> landmark.
22
+ - The active SidebarItem and BottomNavItem carry aria-current via `active`.
23
+ - DataTable headers are real <th>; sortable columns are announced.
24
+
25
+ ## Source
26
+
27
+ ```tsx
28
+ import { AppHeader, AppShell, Avatar, Badge, BottomNav, BottomNavItem, Button, Card, CardAction, CardContent, CardDescription, CardHeader, CardList, CardTitle, DataTable, Entity, Grid, Icon, Page, PageHeader, RelativeTime, Row, Sidebar, SidebarHeader, SidebarItem, SidebarNav } from "@sableui/react"
29
+
30
+ const MIN = 60 * 1000
31
+ const HOUR = 60 * MIN
32
+
33
+ const stats = [
34
+ { label: "Revenue", value: "$12,480", delta: "+8.2%", up: true, note: "vs. previous 30 days" },
35
+ { label: "Collects", value: "1,284", delta: "+3.1%", up: true, note: "182 from new collectors" },
36
+ { label: "New collectors", value: "312", delta: "+12.4%", up: true, note: "best month this year" },
37
+ { label: "Sell-through", value: "64%", delta: "−1.8%", up: false, note: "across 48 active editions" },
38
+ ]
39
+
40
+ const deployments = [
41
+ { id: "dp_01", project: "storefront", commit: "a41f2c9", status: "ready" as const, duration: "42s", updated: 2 * MIN },
42
+ { id: "dp_02", project: "api", commit: "7c03bd1", status: "building" as const, duration: "1m 08s", updated: 9 * MIN },
43
+ { id: "dp_03", project: "docs", commit: "e992a70", status: "queued" as const, duration: "—", updated: 12 * MIN },
44
+ { id: "dp_04", project: "worker", commit: "51b8e04", status: "failed" as const, duration: "18s", updated: 1 * HOUR },
45
+ { id: "dp_05", project: "storefront", commit: "90cc41a", status: "ready" as const, duration: "39s", updated: 3 * HOUR },
46
+ { id: "dp_06", project: "renderer", commit: "b7714dd", status: "ready" as const, duration: "2m 31s", updated: 5 * HOUR },
47
+ { id: "dp_07", project: "api", commit: "f30a9e2", status: "ready" as const, duration: "1m 02s", updated: 8 * HOUR },
48
+ ]
49
+
50
+ const activity = [
51
+ { id: "ac_01", who: "Nina Simone", initials: "NS", what: "collected Gold Rush #42", when: 2 * MIN },
52
+ { id: "ac_02", who: "Grace Hopper", initials: "GH", what: "minted a 100-run edition", when: 26 * MIN },
53
+ { id: "ac_03", who: "Ada Lovelace", initials: "AL", what: "followed you", when: 1 * HOUR },
54
+ { id: "ac_04", who: "Mary Shelley", initials: "MS", what: "collected Quiet Harbor", when: 3 * HOUR },
55
+ { id: "ac_05", who: "Frida Kahlo", initials: "FK", what: "tipped 0.2 SOL on Process", when: 6 * HOUR },
56
+ ]
57
+
58
+ export function DashboardPage() {
59
+ return (
60
+ <AppShell
61
+ // Sidebar hides below lg, so the same destinations ride in the shell's
62
+ // mobile slots — without them the route has no navigation under 1024px.
63
+ header={<AppHeader title={<span className="text-title-sm">Atelier</span>} />}
64
+ bottomNav={
65
+ <BottomNav aria-label="Primary">
66
+ <BottomNavItem icon={<Icon name="grid" />} label="Overview" showLabel active render={<a href="/" />} />
67
+ <BottomNavItem icon={<Icon name="image-stack" />} label="Editions" showLabel render={<a href="/editions" />} />
68
+ <BottomNavItem icon={<Icon name="user" />} label="Collectors" showLabel render={<a href="/collectors" />} />
69
+ <BottomNavItem icon={<Icon name="bell" />} label="Activity" showLabel render={<a href="/activity" />} />
70
+ <BottomNavItem icon={<Icon name="gear" />} label="Settings" showLabel render={<a href="/settings" />} />
71
+ </BottomNav>
72
+ }
73
+ sidebar={
74
+ <Sidebar>
75
+ <SidebarHeader>
76
+ <span className="text-title-sm">Atelier</span>
77
+ </SidebarHeader>
78
+ <SidebarNav>
79
+ <SidebarItem icon={<Icon name="grid" />} label="Overview" active render={<a href="/" />} />
80
+ <SidebarItem icon={<Icon name="image-stack" />} label="Editions" render={<a href="/editions" />} />
81
+ <SidebarItem icon={<Icon name="user" />} label="Collectors" render={<a href="/collectors" />} />
82
+ <SidebarItem icon={<Icon name="bell" />} label="Activity" render={<a href="/activity" />} />
83
+ <SidebarItem icon={<Icon name="gear" />} label="Settings" render={<a href="/settings" />} />
84
+ </SidebarNav>
85
+ </Sidebar>
86
+ }
87
+ >
88
+ <Page max="wide" gap={4} className="py-6">
89
+ <PageHeader
90
+ title="Overview"
91
+ description="Sales, collects, and edition activity for the last 30 days."
92
+ actions={
93
+ <Row gap={2}>
94
+ <Button variant="outline">Export</Button>
95
+ <Button>New edition</Button>
96
+ </Row>
97
+ }
98
+ />
99
+ <Grid cols={{ base: 1, sm: 2, xl: 4 }} gap={3}>
100
+ {stats.map((stat) => (
101
+ <Card key={stat.label}>
102
+ <CardHeader>
103
+ <CardDescription>{stat.label}</CardDescription>
104
+ <CardTitle className="text-mono-xl">{stat.value}</CardTitle>
105
+ <CardAction>
106
+ <Badge tone={stat.up ? "success" : "neutral"} size="sm">
107
+ <Icon name={stat.up ? "chevron-up" : "chevron-down"} />
108
+ {stat.delta}
109
+ </Badge>
110
+ </CardAction>
111
+ </CardHeader>
112
+ <CardContent>
113
+ <p className="text-caption text-muted-foreground">{stat.note}</p>
114
+ </CardContent>
115
+ </Card>
116
+ ))}
117
+ </Grid>
118
+ <Grid cols={{ base: 1, xl: 3 }} gap={3}>
119
+ <Card className="xl:col-span-2">
120
+ <CardHeader>
121
+ <CardTitle>Recent deployments</CardTitle>
122
+ <CardDescription>Builds across every project, newest first.</CardDescription>
123
+ <CardAction>
124
+ <Button variant="ghost" size="sm" render={<a href="/deployments" />}>
125
+ View all
126
+ </Button>
127
+ </CardAction>
128
+ </CardHeader>
129
+ <CardContent flush>
130
+ <DataTable
131
+ data={deployments}
132
+ rowKey={(row) => row.id}
133
+ columns={[
134
+ { key: "project", header: "Project", sortable: true, className: "text-label-md" },
135
+ { key: "commit", header: "Commit", cell: (row) => <span className="text-mono-caption text-muted-foreground">{row.commit}</span> },
136
+ { key: "status", header: "Status", cell: (row) => <Badge appearance="plain" tone={statusTone(row.status)} dot>{row.status}</Badge> },
137
+ { key: "duration", header: "Duration", align: "end", cell: (row) => <span className="text-mono-caption">{row.duration}</span> },
138
+ { key: "updated", header: "Updated", align: "end", cell: (row) => <RelativeTime date={Date.now() - row.updated} className="text-body-xs text-muted-foreground" /> },
139
+ ]}
140
+ />
141
+ </CardContent>
142
+ </Card>
143
+ <Card>
144
+ <CardHeader>
145
+ <CardTitle>Activity</CardTitle>
146
+ <CardDescription>Collects, mints, and follows.</CardDescription>
147
+ </CardHeader>
148
+ <CardList>
149
+ {activity.map((item) => (
150
+ <Entity
151
+ key={item.id}
152
+ density="compact"
153
+ leading={<Avatar size="sm" fallback={item.initials} alt={item.who} />}
154
+ title={
155
+ <span className="text-body-sm">
156
+ <span className="text-label-md">{item.who}</span>{" "}
157
+ <span className="text-muted-foreground">{item.what}</span>
158
+ </span>
159
+ }
160
+ action={<RelativeTime date={Date.now() - item.when} className="text-caption text-muted-foreground" />}
161
+ />
162
+ ))}
163
+ </CardList>
164
+ </Card>
165
+ </Grid>
166
+ </Page>
167
+ </AppShell>
168
+ )
169
+ }
170
+ ```
@@ -0,0 +1,103 @@
1
+ # FeedPage
2
+
3
+ Single-column reading feed: a feed-width region, one Card per post (Entity byline, zero-shift media, body copy, quiet action row), and explicit load-more pagination.
4
+
5
+ **Intent:** Reach for this for timelines, activity streams, and any reverse-chronological reading surface. Not for record management (DashboardPage) or side-by-side browsing (ListDetailPage).
6
+
7
+ ## Notes
8
+
9
+ - Region cap is `feed` (640px) — a single reading column; media and copy share one measure.
10
+ - Post media sits in an AspectBox so late-loading images never shift the column.
11
+ - The byline is Entity (Avatar + name + handle) in a CardList — the same row primitive as everywhere else, inset to the card padding by CardList rather than by zeroing paddings on CardHeader and Entity.
12
+ - Actions are ghost sm Buttons (icon + count) in a Row, pulled out by the button's own padding (`-ms-3`) so the first icon sits on the text edge: quiet chrome under content, not competing pills.
13
+ - Pagination is an explicit load-more Button (secondary; set `loading` while the next page fetches — `loadingLabel` names that state), not infinite scroll — predictable for keyboard and AT users.
14
+
15
+ ## Accessibility
16
+
17
+ - Each post is an <article>, so AT users can jump between posts.
18
+ - Icon-only action buttons carry aria-labels.
19
+ - Media images get meaningful alt text from the post, empty alt only if decorative.
20
+
21
+ ## Source
22
+
23
+ ```tsx
24
+ import { AspectBox, Avatar, Button, Card, CardContent, CardList, Entity, Icon, Page, PageHeader, Row, Stack } from "@sableui/react"
25
+
26
+ const posts = [
27
+ {
28
+ id: "p_01",
29
+ author: "Ada Lovelace",
30
+ handle: "@ada · 2h",
31
+ initials: "AL",
32
+ body: "New edition drops Friday — 100 signed prints, each with the on-chain original.",
33
+ image: "/media/edition-cover.svg",
34
+ alt: "Edition cover — Gold Rush series",
35
+ likes: 128,
36
+ comments: 24,
37
+ },
38
+ {
39
+ id: "p_02",
40
+ author: "Grace Hopper",
41
+ handle: "@grace · 5h",
42
+ initials: "GH",
43
+ body: "Process shot from this week's series. Compilers and copper ink.",
44
+ image: "/media/process-shot.svg",
45
+ alt: "Work-in-progress plotter drawing in copper ink",
46
+ likes: 86,
47
+ comments: 9,
48
+ },
49
+ ]
50
+
51
+ export function FeedPage() {
52
+ return (
53
+ <Page as="main" max="feed" gap={4} className="py-6">
54
+ <PageHeader title="Home" description="Latest posts from artists you follow." />
55
+ <Stack gap={3}>
56
+ {posts.map((post) => (
57
+ <article key={post.id}>
58
+ <Card className="overflow-hidden">
59
+ {/* The byline is an Entity row in CardList, which insets it to the
60
+ card's own padding on every side — no hand-zeroed paddings. */}
61
+ <CardList>
62
+ <Entity
63
+ leading={<Avatar fallback={post.initials} alt={post.author} />}
64
+ title={post.author}
65
+ subtitle={post.handle}
66
+ action={
67
+ <Button variant="ghost" size="icon-sm" aria-label="Post options">
68
+ <Icon name="ellipsis" />
69
+ </Button>
70
+ }
71
+ />
72
+ </CardList>
73
+ <AspectBox ratio={16 / 9}>
74
+ <img src={post.image} alt={post.alt} className="size-full object-cover" />
75
+ </AspectBox>
76
+ <CardContent className="flex flex-col gap-2">
77
+ <p className="text-body-md text-foreground">{post.body}</p>
78
+ {/* Pulled out by exactly a sm Button's own padding (px-3), so the
79
+ first icon lands on the text edge above it. Both are on the
80
+ spacing scale, so they stay aligned at every density. */}
81
+ <Row gap={1} className="-ms-3">
82
+ <Button variant="ghost" size="sm" aria-label={`Like — ${post.likes}`}>
83
+ <Icon name="heart" />
84
+ {post.likes}
85
+ </Button>
86
+ <Button variant="ghost" size="sm" aria-label={`Comments — ${post.comments}`}>
87
+ <Icon name="comment" />
88
+ {post.comments}
89
+ </Button>
90
+ <Button variant="ghost" size="sm" aria-label="Share">
91
+ <Icon name="share" />
92
+ </Button>
93
+ </Row>
94
+ </CardContent>
95
+ </Card>
96
+ </article>
97
+ ))}
98
+ </Stack>
99
+ <Button variant="secondary" loadingLabel="Loading…">Load more posts</Button>
100
+ </Page>
101
+ )
102
+ }
103
+ ```
@@ -0,0 +1,122 @@
1
+ # LandingPage
2
+
3
+ Marketing hero + feature grid: display-tier headline as the page's single loud moment, an eyebrow kicker, a two-action CTA row, and a three-up feature grid that reveals on scroll.
4
+
5
+ **Intent:** Reach for this for the marketing/landing route of a product — the one page where Display type and scroll reveals belong. Not for product UI (display type and one-per-view rules forbid it inside the app).
6
+
7
+ ## Notes
8
+
9
+ - Display type is the marketing voice: exactly one display headline per page, tight leading, weight 700+.
10
+ - The eyebrow is a small outline Badge (`size="sm"`) — the badge's own size, never a type class forced onto its label; hierarchy above the headline is eyebrow → display → lede.
11
+ - The lede is body-lg capped at the prose measure — the headline may be wide, reading text may not.
12
+ - Two CTAs max: one primary, one quiet outline. More actions dilute the hero.
13
+ - Reveal wraps below-the-fold sections only; it no-ops under reduced motion via the motion policy.
14
+ - Feature cards are Cards in a Grid with minItemWidth (three-up that reflows on its own width), each icon + title + body-sm copy.
15
+
16
+ ## Accessibility
17
+
18
+ - The hero headline is the page h1; feature titles are h2s restyled with the Title tier.
19
+ - Reveal renders content immediately (no animation) when prefers-reduced-motion is set.
20
+ - CTA buttons are real links or buttons with visible text — no icon-only CTAs.
21
+
22
+ ## Source
23
+
24
+ ```tsx
25
+ import { Badge, Button, Card, CardContent, Grid, Icon, Region, Reveal, Row, Stack } from "@sableui/react"
26
+
27
+ const features = [
28
+ {
29
+ icon: "sparkles" as const,
30
+ title: "Own the surface",
31
+ body: "Semantic tokens end-to-end — re-theme the whole product without touching a component.",
32
+ },
33
+ {
34
+ icon: "component" as const,
35
+ title: "Behavior included",
36
+ body: "Focus, keyboard nav, and ARIA ride on Base UI primitives under every surface.",
37
+ },
38
+ {
39
+ icon: "layout" as const,
40
+ title: "Pages, not just parts",
41
+ body: "Purpose-named regions, an 8px rhythm, and canonical page shells for the routes every product needs.",
42
+ },
43
+ ]
44
+
45
+ export function LandingPage() {
46
+ return (
47
+ <div className="min-h-dvh bg-background">
48
+ <header>
49
+ <Region as="nav" aria-label="Main" className="grid h-16 grid-cols-[1fr_auto_1fr] items-center">
50
+ <span className="justify-self-start text-title-sm">Sable</span>
51
+ <Row gap={1} className="justify-self-center max-sm:hidden">
52
+ <Button variant="ghost" size="sm" render={<a href="/docs" />}>
53
+ Docs
54
+ </Button>
55
+ <Button variant="ghost" size="sm" render={<a href="/patterns" />}>
56
+ Patterns
57
+ </Button>
58
+ <Button variant="ghost" size="sm" render={<a href="/themes" />}>
59
+ Themes
60
+ </Button>
61
+ </Row>
62
+ <Button size="sm" className="justify-self-end" render={<a href="/signup" />}>
63
+ Get started
64
+ </Button>
65
+ </Region>
66
+ </header>
67
+ <main>
68
+ <Region as="section" max="wide" className="py-24">
69
+ <Stack gap={4} align="center" className="text-center">
70
+ <Badge appearance="outline" size="sm">
71
+ Now in beta
72
+ </Badge>
73
+ <h1 className="max-w-4xl text-balance text-display-2xl">
74
+ Ship product UI your agents can't get wrong
75
+ </h1>
76
+ <p className="max-w-(--measure-prose) text-body-lg text-muted-foreground">
77
+ An owned, themeable component system with enforced tokens, typography,
78
+ and motion — documented for machines as carefully as for people.
79
+ </p>
80
+ <Row gap={2} justify="center">
81
+ <Button size="cta">Get started</Button>
82
+ <Button size="cta" variant="outline">
83
+ Read the docs
84
+ </Button>
85
+ </Row>
86
+ </Stack>
87
+ </Region>
88
+ <Region as="section" max="wide" className="pb-24">
89
+ <Reveal>
90
+ {/* Three-up that reflows on its own width (minItemWidth), one Card
91
+ per feature — never a hand-drawn bordered grid. */}
92
+ <Grid minItemWidth="calc(var(--spacing) * 64)">
93
+ {features.map((feature) => (
94
+ <Card key={feature.title}>
95
+ <CardContent className="flex flex-col gap-2">
96
+ <Icon name={feature.icon} className="text-xl text-muted-foreground" />
97
+ <h2 className="text-title-lg">{feature.title}</h2>
98
+ <p className="text-body-sm text-muted-foreground">{feature.body}</p>
99
+ </CardContent>
100
+ </Card>
101
+ ))}
102
+ </Grid>
103
+ </Reveal>
104
+ </Region>
105
+ </main>
106
+ <footer className="border-t border-border">
107
+ <Region className="flex h-16 items-center justify-between">
108
+ <span className="text-caption text-muted-foreground">© 2026 Sable</span>
109
+ <Row gap={3}>
110
+ <a href="/github" className="text-caption text-muted-foreground motion-interactive hover:text-foreground">
111
+ GitHub
112
+ </a>
113
+ <a href="/changelog" className="text-caption text-muted-foreground motion-interactive hover:text-foreground">
114
+ Changelog
115
+ </a>
116
+ </Row>
117
+ </Region>
118
+ </footer>
119
+ </div>
120
+ )
121
+ }
122
+ ```
@@ -0,0 +1,100 @@
1
+ # ListDetailPage
2
+
3
+ Two-column list-detail: a wide region split 8/4, the record list as Entity rows in the main column, and a sticky supporting rail (detail, filters, or summary) beside it.
4
+
5
+ **Intent:** Reach for this when a route lists records AND keeps one selected/summarized in view (members + member detail, inbox + message, orders + order). For pure browsing with no rail, a single-column FeedPage or DashboardPage table is simpler.
6
+
7
+ ## Notes
8
+
9
+ - Region cap is `wide`; the split is Columns 12 → Col 8 + Col 4, collapsing to stacked full-width columns below lg via breakpoint maps.
10
+ - List rows are Entity inside one bordered Card, in CardList: it insets each row to the card padding and divides them with hairlines — on a dark canvas rows need an anchoring surface, not a flat float.
11
+ - Pending records live in the same list, muted (muted title + `Invited` badge), instead of a separate section.
12
+ - The rail card holds label/value pairs as a Description list — muted terms, mono (tabular) values — plus one quiet action. Never hand-built `justify-between` rows.
13
+ - The rail is sticky (top on the 4px scale) so the summary stays in view while the list scrolls.
14
+
15
+ ## Accessibility
16
+
17
+ - Entity rows given `interactive` + `onSelect` (to open a member's detail) render as real buttons; keyboard focus follows list order. Rows here are presentational.
18
+ - The rail is an <aside> landmark; the list column is the main content.
19
+
20
+ ## Source
21
+
22
+ ```tsx
23
+ import { Avatar, Badge, Button, Card, CardContent, CardHeader, CardList, CardTitle, Col, Columns, Description, DescriptionItem, Entity, Page, PageHeader, SearchInput, Stack } from "@sableui/react"
24
+
25
+ const members = [
26
+ { id: "u_01", name: "Ada Lovelace", handle: "@ada", role: "Owner", initials: "AL", pending: false },
27
+ { id: "u_02", name: "Grace Hopper", handle: "@grace", role: "Admin", initials: "GH", pending: false },
28
+ { id: "u_03", name: "Mary Shelley", handle: "@mary", role: "Member", initials: "MS", pending: false },
29
+ { id: "u_04", name: "Nina Simone", handle: "@nina", role: "Member", initials: "NS", pending: false },
30
+ { id: "u_05", name: "Frida Kahlo", handle: "@frida", role: "Member", initials: "FK", pending: false },
31
+ { id: "u_06", name: "hopper@navy.mil", handle: "invited 2 days ago", role: "Member", initials: "?", pending: true },
32
+ ]
33
+
34
+ export function ListDetailPage() {
35
+ return (
36
+ <Page as="main" max="wide" gap={4} className="py-6">
37
+ <PageHeader
38
+ title="Members"
39
+ description="Everyone with access to this workspace."
40
+ actions={<Button>Invite member</Button>}
41
+ />
42
+ <Columns count={{ base: 1, lg: 12 }}>
43
+ <Col span={{ base: 1, lg: 8 }}>
44
+ <Stack gap={3}>
45
+ <SearchInput placeholder="Search members" aria-label="Search members" />
46
+ <Card className="overflow-hidden">
47
+ <CardList>
48
+ {members.map((member) => (
49
+ <Entity
50
+ key={member.id}
51
+ leading={
52
+ <Avatar
53
+ fallback={member.pending ? "•" : member.initials}
54
+ alt={member.pending ? "" : member.name}
55
+ />
56
+ }
57
+ title={
58
+ <span className={member.pending ? "text-muted-foreground" : undefined}>
59
+ {member.name}
60
+ </span>
61
+ }
62
+ subtitle={member.handle}
63
+ action={
64
+ member.pending ? (
65
+ <Badge>Invited</Badge>
66
+ ) : (
67
+ <Badge appearance="outline">{member.role}</Badge>
68
+ )
69
+ }
70
+ />
71
+ ))}
72
+ </CardList>
73
+ </Card>
74
+ </Stack>
75
+ </Col>
76
+ <Col span={{ base: 1, lg: 4 }}>
77
+ <aside className="sticky top-6">
78
+ <Card>
79
+ <CardHeader>
80
+ <CardTitle>Workspace</CardTitle>
81
+ </CardHeader>
82
+ <CardContent className="flex flex-col gap-3">
83
+ {/* Label/value pairs are a Description list, not hand-built rows. */}
84
+ <Description>
85
+ <DescriptionItem term="Members" detail={<span className="text-mono-md">5</span>} />
86
+ <DescriptionItem term="Pending invites" detail={<span className="text-mono-md">1</span>} />
87
+ <DescriptionItem term="Seats used" detail={<span className="text-mono-md">6 / 10</span>} />
88
+ </Description>
89
+ <Button variant="outline" size="sm" className="mt-1 self-start">
90
+ Manage plan
91
+ </Button>
92
+ </CardContent>
93
+ </Card>
94
+ </aside>
95
+ </Col>
96
+ </Columns>
97
+ </Page>
98
+ )
99
+ }
100
+ ```
@@ -0,0 +1,121 @@
1
+ # SettingsPage
2
+
3
+ Stacked settings sections inside the app shell: a content-width region, one Fieldset card per concern (each with its own save), auto-wired Form/Field rows, and a destructive zone last.
4
+
5
+ **Intent:** Reach for this for account/profile/workspace settings and any 'edit a handful of grouped preferences' route. Not for a single short form (use AuthPage's centered form shape) or bulk record editing (use DashboardPage + DataTable).
6
+
7
+ ## Notes
8
+
9
+ - Region cap is `content` (896px): settings panes hold labeled rows and the occasional 2-up field, never a full grid.
10
+ - One Fieldset per concern, each with its own footer save — per-section saves beat one page-wide submit for settings.
11
+ - Field wires label/description/error a11y automatically; never hand-roll <label> + <input> pairs here.
12
+ - The destructive section is visually last, isolated in its own Fieldset with a destructive-variant action.
13
+ - Toggles use orientation="horizontal" Fields so the label sits beside the Switch.
14
+ - Lives in the same AppShell as DashboardPage: Sidebar from lg up, AppHeader + BottomNav below it, with Settings marked active in both.
15
+
16
+ ## Accessibility
17
+
18
+ - AppShell provides the skip link and <main id="main-content"> landmark; the active SidebarItem and BottomNavItem carry aria-current.
19
+ - Each section is a real <fieldset> with a <legend> (FieldsetLegend).
20
+ - Field auto-wires htmlFor/id, aria-describedby (description + error), aria-invalid, and required.
21
+ - The danger action is a real button labeled by its visible text — no icon-only destructive controls.
22
+
23
+ ## Source
24
+
25
+ ```tsx
26
+ import { AppHeader, AppShell, BottomNav, BottomNavItem, Button, Field, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, Form, Icon, Input, Page, PageHeader, Select, SelectContent, SelectItem, SelectTrigger, Sidebar, SidebarHeader, SidebarItem, SidebarNav, Switch, Textarea } from "@sableui/react"
27
+
28
+ export function SettingsPage() {
29
+ return (
30
+ <AppShell
31
+ // Sidebar hides below lg, so the same destinations ride in the shell's
32
+ // mobile slots — without them the route has no navigation under 1024px.
33
+ header={<AppHeader title={<span className="text-title-sm">Atelier</span>} />}
34
+ bottomNav={
35
+ <BottomNav aria-label="Primary">
36
+ <BottomNavItem icon={<Icon name="grid" />} label="Overview" showLabel render={<a href="/" />} />
37
+ <BottomNavItem icon={<Icon name="image-stack" />} label="Editions" showLabel render={<a href="/editions" />} />
38
+ <BottomNavItem icon={<Icon name="user" />} label="Collectors" showLabel render={<a href="/collectors" />} />
39
+ <BottomNavItem icon={<Icon name="bell" />} label="Activity" showLabel render={<a href="/activity" />} />
40
+ <BottomNavItem icon={<Icon name="gear" />} label="Settings" showLabel active render={<a href="/settings" />} />
41
+ </BottomNav>
42
+ }
43
+ sidebar={
44
+ <Sidebar>
45
+ <SidebarHeader>
46
+ <span className="text-title-sm">Atelier</span>
47
+ </SidebarHeader>
48
+ <SidebarNav>
49
+ <SidebarItem icon={<Icon name="grid" />} label="Overview" render={<a href="/" />} />
50
+ <SidebarItem icon={<Icon name="image-stack" />} label="Editions" render={<a href="/editions" />} />
51
+ <SidebarItem icon={<Icon name="user" />} label="Collectors" render={<a href="/collectors" />} />
52
+ <SidebarItem icon={<Icon name="bell" />} label="Activity" render={<a href="/activity" />} />
53
+ <SidebarItem icon={<Icon name="gear" />} label="Settings" active render={<a href="/settings" />} />
54
+ </SidebarNav>
55
+ </Sidebar>
56
+ }
57
+ >
58
+ {/* No `as="main"`: AppShell already renders <main id="main-content">. */}
59
+ <Page max="content" gap={4} className="py-6">
60
+ <PageHeader
61
+ title="Settings"
62
+ description="Your profile, notifications, and account controls."
63
+ />
64
+ <Form onSubmit={(event) => event.preventDefault()}>
65
+ <Fieldset>
66
+ <FieldsetLegend>Profile</FieldsetLegend>
67
+ <FieldsetDescription>How you appear across the app.</FieldsetDescription>
68
+ <FieldsetContent>
69
+ <Field label="Display name" required>
70
+ <Input name="displayName" defaultValue="ada" />
71
+ </Field>
72
+ <Field label="Bio" description="Shown on your public profile.">
73
+ <Textarea name="bio" rows={3} placeholder="Tell collectors who you are" />
74
+ </Field>
75
+ <Field label="Timezone">
76
+ <Select defaultValue="utc" name="timezone">
77
+ <SelectTrigger />
78
+ <SelectContent>
79
+ <SelectItem value="utc">UTC</SelectItem>
80
+ <SelectItem value="est">Eastern (UTC−5)</SelectItem>
81
+ <SelectItem value="pst">Pacific (UTC−8)</SelectItem>
82
+ </SelectContent>
83
+ </Select>
84
+ </Field>
85
+ </FieldsetContent>
86
+ <FieldsetFooter>
87
+ <Button type="submit">Save profile</Button>
88
+ </FieldsetFooter>
89
+ </Fieldset>
90
+ </Form>
91
+ <Form onSubmit={(event) => event.preventDefault()}>
92
+ <Fieldset>
93
+ <FieldsetLegend>Notifications</FieldsetLegend>
94
+ <FieldsetDescription>What we email you about.</FieldsetDescription>
95
+ <FieldsetContent>
96
+ <Field label="New collector alerts" orientation="horizontal">
97
+ <Switch name="collectorAlerts" defaultChecked />
98
+ </Field>
99
+ <Field label="Weekly digest" orientation="horizontal">
100
+ <Switch name="weeklyDigest" />
101
+ </Field>
102
+ </FieldsetContent>
103
+ <FieldsetFooter>
104
+ <Button type="submit">Save notifications</Button>
105
+ </FieldsetFooter>
106
+ </Fieldset>
107
+ </Form>
108
+ <Fieldset>
109
+ <FieldsetLegend>Danger zone</FieldsetLegend>
110
+ <FieldsetDescription>
111
+ Deleting your account removes your posts and collections permanently.
112
+ </FieldsetDescription>
113
+ <FieldsetFooter>
114
+ <Button variant="destructive">Delete account</Button>
115
+ </FieldsetFooter>
116
+ </Fieldset>
117
+ </Page>
118
+ </AppShell>
119
+ )
120
+ }
121
+ ```