@sableui/react 0.1.1 → 0.3.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 (137) hide show
  1. package/AGENTS.md +3 -1
  2. package/CHANGELOG.md +120 -0
  3. package/CONTRIBUTING.md +8 -0
  4. package/README.md +1 -0
  5. package/bin/sable-lint.mjs +0 -0
  6. package/bin/sable.mjs +68 -26
  7. package/dist/{_contract-CYScOdf5.d.ts → _contract-o3tUc8Ig.d.ts} +1 -1
  8. package/dist/blocks/index.d.ts +396 -0
  9. package/dist/blocks/index.js +1997 -0
  10. package/dist/chunk-6LCIBMMG.js +4908 -0
  11. package/dist/{chunk-J23ELQQZ.js → chunk-CKNM7KSB.js} +1664 -4270
  12. package/dist/chunk-K6LII3OE.js +5093 -0
  13. package/dist/{chunk-RWP4XBSK.js → chunk-KFZ43RKU.js} +1 -1
  14. package/dist/{chunk-PAOZBZME.js → chunk-NGBDP3BM.js} +1 -1
  15. package/dist/{chunk-5Z5KLOAU.js → chunk-TH563WLW.js} +14 -1
  16. package/dist/{chunk-JI7WJP2L.js → chunk-X276MF2W.js} +5 -0
  17. package/dist/foundations/index.js +1 -1
  18. package/dist/icon/index.js +2 -2
  19. package/dist/icon-sets/reicon.js +1 -1
  20. package/dist/index.d.ts +624 -133
  21. package/dist/index.js +767 -4964
  22. package/dist/ios/index.d.ts +14 -2
  23. package/dist/ios/index.js +10 -9
  24. package/dist/ios/preview.d.ts +11 -1
  25. package/dist/ios/preview.js +14 -9
  26. package/dist/ios/styles.css +26 -6
  27. package/dist/{meta-6dUzgC6M.d.ts → meta-CcE_SeFu.d.ts} +37 -1
  28. package/dist/patterns/index.d.ts +1 -1
  29. package/dist/patterns/index.js +1684 -471
  30. package/dist/presets/index.d.ts +1 -1
  31. package/dist/presets/index.js +1 -1
  32. package/dist/styles.css +238 -40
  33. package/dist/{theme-provider-BWcizhAt.d.ts → theme-provider-BE-KqUG-.d.ts} +1 -1
  34. package/dist/themes/atelier/atelier.css +3 -0
  35. package/dist/themes/atelier/index.d.ts +1 -1
  36. package/dist/themes/base/base.css +34 -4
  37. package/dist/themes/base/index.d.ts +1 -1
  38. package/dist/themes/ember/ember.css +3 -0
  39. package/dist/themes/ember/index.d.ts +1 -1
  40. package/dist/themes/index.d.ts +2 -2
  41. package/dist/themes/index.js +2 -2
  42. package/dist/themes/meridian/index.d.ts +1 -1
  43. package/dist/themes/meridian/meridian.css +10 -5
  44. package/dist/themes/poppy/index.d.ts +1 -1
  45. package/dist/themes/poppy/poppy.css +16 -0
  46. package/dist/themes/prism/index.d.ts +1 -1
  47. package/dist/themes/prism/prism.css +4 -1
  48. package/dist/themes/verdant/index.d.ts +1 -1
  49. package/dist/themes/verdant/verdant.css +7 -2
  50. package/llms/blocks/api-keys-block.md +79 -0
  51. package/llms/blocks/chat-thread-block.md +51 -0
  52. package/llms/blocks/command-palette-block.md +48 -0
  53. package/llms/blocks/cta-band-block.md +62 -0
  54. package/llms/blocks/danger-zone-block.md +69 -0
  55. package/llms/blocks/device-comparison-block.md +91 -0
  56. package/llms/blocks/event-card-block.md +84 -0
  57. package/llms/blocks/faq-block.md +72 -0
  58. package/llms/blocks/feature-grid-block.md +75 -0
  59. package/llms/blocks/feedback-form-block.md +66 -0
  60. package/llms/blocks/goal-progress-block.md +54 -0
  61. package/llms/blocks/hero-block.md +65 -0
  62. package/llms/blocks/invite-team-block.md +61 -0
  63. package/llms/blocks/invoice-history-block.md +84 -0
  64. package/llms/blocks/kpi-sparkline-block.md +57 -0
  65. package/llms/blocks/media-player-block.md +100 -0
  66. package/llms/blocks/newsletter-block.md +64 -0
  67. package/llms/blocks/not-found-block.md +56 -0
  68. package/llms/blocks/notification-list-block.md +63 -0
  69. package/llms/blocks/notification-settings-block.md +54 -0
  70. package/llms/blocks/order-tracking-block.md +62 -0
  71. package/llms/blocks/pricing-card-block.md +82 -0
  72. package/llms/blocks/pricing-table-block.md +129 -0
  73. package/llms/blocks/product-card-block.md +69 -0
  74. package/llms/blocks/profile-card-block.md +103 -0
  75. package/llms/blocks/profile-form-block.md +114 -0
  76. package/llms/blocks/revenue-breakdown-block.md +70 -0
  77. package/llms/blocks/sign-in-block.md +64 -0
  78. package/llms/blocks/sign-up-block.md +78 -0
  79. package/llms/blocks/site-footer-block.md +96 -0
  80. package/llms/blocks/stat-row-block.md +59 -0
  81. package/llms/blocks/steps-block.md +73 -0
  82. package/llms/blocks/task-list-block.md +81 -0
  83. package/llms/blocks/team-members-block.md +91 -0
  84. package/llms/blocks/testimonial-block.md +78 -0
  85. package/llms/blocks/traffic-sources-block.md +63 -0
  86. package/llms/blocks/traffic-trend-block.md +95 -0
  87. package/llms/blocks/upload-list-block.md +66 -0
  88. package/llms/blocks/verify-code-block.md +59 -0
  89. package/llms/blocks/wallet-block.md +84 -0
  90. package/llms/components/area-chart.md +112 -0
  91. package/llms/components/bar-chart.md +131 -0
  92. package/llms/components/bubble.md +5 -5
  93. package/llms/components/button.md +1 -1
  94. package/llms/components/card.md +2 -2
  95. package/llms/components/checkbox-card.md +1 -1
  96. package/llms/components/context-card.md +2 -2
  97. package/llms/components/context-menu.md +1 -1
  98. package/llms/components/date-tile.md +79 -0
  99. package/llms/components/dialog.md +1 -1
  100. package/llms/components/empty-state.md +4 -4
  101. package/llms/components/entity.md +5 -3
  102. package/llms/components/fieldset.md +1 -1
  103. package/llms/components/kbd.md +18 -6
  104. package/llms/components/line-chart.md +101 -0
  105. package/llms/components/menu.md +1 -1
  106. package/llms/components/{dots-menu.md → overflow-menu.md} +9 -9
  107. package/llms/components/pie-chart.md +103 -0
  108. package/llms/components/pill.md +1 -1
  109. package/llms/components/radar-chart.md +106 -0
  110. package/llms/components/radial-chart.md +102 -0
  111. package/llms/components/segmented-bar.md +102 -0
  112. package/llms/components/settings-menu.md +2 -2
  113. package/llms/components/side-nav.md +1 -1
  114. package/llms/components/skeleton.md +1 -1
  115. package/llms/components/sparkline.md +80 -0
  116. package/llms/components/table.md +1 -1
  117. package/llms/components/theme-selector.md +1 -1
  118. package/llms/foundations/shape.md +1 -1
  119. package/llms/patterns/auth-page.md +132 -42
  120. package/llms/patterns/brand-landing-page.md +271 -175
  121. package/llms/patterns/chat-panel.md +120 -39
  122. package/llms/patterns/dashboard-page.md +276 -65
  123. package/llms/patterns/feed-page.md +245 -64
  124. package/llms/patterns/landing-page.md +473 -75
  125. package/llms/patterns/list-detail-page.md +377 -68
  126. package/llms/patterns/settings-page.md +268 -67
  127. package/llms/reference/token-intent.md +4 -2
  128. package/llms/reference/utilities.md +20 -5
  129. package/llms/tasks/building-an-ios-prototype.md +4 -1
  130. package/llms/tasks/choosing-a-component.md +2 -2
  131. package/llms-full.txt +6428 -892
  132. package/llms.txt +67 -16
  133. package/package.json +5 -1
  134. package/registry.json +3217 -552
  135. package/targets/ios.json +88 -18
  136. package/llms/components/description.md +0 -96
  137. package/dist/{chunk-L6Y46C62.js → chunk-MXJX3NZI.js} +1 -1
@@ -1,119 +1,517 @@
1
1
  # LandingPage
2
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.
3
+ Product / open-source landing page: a centered hero with an eyebrow Badge, one display headline, lede, two CTAs and a copyable install command; a Browser-framed product preview composed from real components; a six-up feature grid; a setup section with tabbed code; an agent-workflow section with terminal output; a CTA card; and a link-column footer.
4
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).
5
+ **Intent:** Reach for this for the marketing route of a developer product, library, or tool: the page where the product shows itself working and the reader installs or signs up. For a branded, editorial marketing site use BrandLandingPage. Not for product UI (display type and one-per-view rules forbid it inside the app).
6
6
 
7
7
  ## Notes
8
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.
9
+ - Display type is the marketing voice: exactly one display headline per page (`text-display-lg` on phones, `text-display-2xl` from `sm`). Every section below uses `text-heading-1` with a `text-overline` kicker.
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 fold is eyebrow, display, lede, CTAs, install command.
11
+ - The lede is body-lg capped at the prose measure. The headline may be wide; reading text may not.
12
+ - Two CTAs max per row: one primary, one quiet outline. The install line is a `CodeBlock` with `prompt="$"`, so it truncates on one line and copy takes only the command.
13
+ - Show the product instead of describing it. The preview is real components (PageHeader, metric Cards, a Meter, a CardList of Entity rows, Message and Bubble, ChatComposer) inside `Browser`, wrapped in an `inert` div so the picture's controls never take focus. Secondary columns hide on small screens rather than stacking into a long mock.
14
+ - Features are Cards in a fixed `Grid cols={{ base: 1, sm: 2, lg: 3 }}`, so six items always land as full rows (3 x 2, 2 x 3, 1 x 6) without an orphan.
15
+ - Multi-line code uses `CodeBlock` with a filename; switch related snippets with `Tabs`, defaulting to the most complete one. Terminal output passes `highlight={false}` so a code highlighter does not colour prose.
16
+ - Reveal wraps below-the-fold blocks only (features, code, CTA). It no-ops under reduced motion via the motion policy.
17
+ - Hide nav groups on narrow screens by wrapping them in a `max-md:hidden` div: a `Row` or `Button` sets its own display, which a `hidden` class on the same element does not reliably override.
15
18
 
16
19
  ## Accessibility
17
20
 
18
- - The hero headline is the page h1; feature titles are h2s restyled with the Title tier.
21
+ - The hero headline is the page h1; section titles are h2s and feature and step titles are h3s. Footer column labels are h2s inside the footer nav.
22
+ - The product preview is a `figure` with a visible figcaption. Its content is `inert`, which removes it from the tab order and the accessibility tree, so its headings and buttons do not pollute the outline.
19
23
  - 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.
24
+ - CTA buttons are real links with visible text, with no icon-only CTAs. Footer links keep a 36px minimum row height for touch.
21
25
 
22
26
  ## Source
23
27
 
24
28
  ```tsx
25
- import { Badge, Button, Card, CardContent, Grid, Icon, Region, Reveal, Row, Stack } from "@sableui/react"
29
+ import { Avatar, Badge, Browser, Bubble, Button, Card, CardAction, CardContent, CardDescription, CardHeader, CardList, CardTitle, ChatComposer, Code, CodeBlock, Entity, Grid, Icon, Message, Meter, MeterLabel, MeterTrack, MeterValue, PageHeader, Region, Reveal, Row, Stack, Tabs, TabsList, TabsPanel, TabsTrigger } from "@sableui/react"
26
30
 
27
- const features = [
31
+ const navLinks = [
32
+ { label: "Docs", href: "/docs" },
33
+ { label: "Components", href: "/components" },
34
+ { label: "Patterns", href: "/patterns" },
35
+ { label: "Themes", href: "/themes" },
36
+ ]
37
+
38
+ const deploys = [
39
+ {
40
+ initials: "MO",
41
+ name: "Maya Ortiz",
42
+ title: "Add usage-based billing",
43
+ meta: "main · a3f9c21 · 2m ago",
44
+ status: "Building",
45
+ tone: "info" as const,
46
+ pulse: true,
47
+ },
48
+ {
49
+ initials: "SR",
50
+ name: "Sam Reyes",
51
+ title: "Fix search ranking for long queries",
52
+ meta: "main · 81be04d · 18m ago",
53
+ status: "Ready",
54
+ tone: "success" as const,
55
+ pulse: false,
56
+ },
28
57
  {
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.",
58
+ initials: "JL",
59
+ name: "Jun Lee",
60
+ title: "Rotate webhook signing keys",
61
+ meta: "main · 5c7e9aa · 1h ago",
62
+ status: "Ready",
63
+ tone: "success" as const,
64
+ pulse: false,
32
65
  },
66
+ ]
67
+
68
+ const features = [
33
69
  {
34
70
  icon: "component" as const,
35
- title: "Behavior included",
36
- body: "Focus, keyboard nav, and ARIA ride on Base UI primitives under every surface.",
71
+ title: "Behavior built in",
72
+ body: "Focus management, keyboard navigation, and ARIA come from Base UI primitives under every control.",
73
+ },
74
+ {
75
+ icon: "eyedropper" as const,
76
+ title: "Semantic tokens end to end",
77
+ body: "Re-theme the whole product, light and dark, from one preset without touching a component.",
37
78
  },
38
79
  {
39
80
  icon: "layout" as const,
40
81
  title: "Pages, not just parts",
41
- body: "Purpose-named regions, an 8px rhythm, and canonical page shells for the routes every product needs.",
82
+ body: "Landing, dashboard, settings, auth, and list-detail shells to copy and adapt.",
83
+ },
84
+ {
85
+ icon: "book" as const,
86
+ title: "A contract agents can read",
87
+ body: "registry.json, llms.txt, and a CLI hand agents the exact props, examples, and rules.",
88
+ },
89
+ {
90
+ icon: "circle-check" as const,
91
+ title: "Lint that knows the design",
92
+ body: "sable-lint flags raw colors, hand-built controls, and motion that ignores reduced motion.",
93
+ },
94
+ {
95
+ icon: "play" as const,
96
+ title: "Motion with an off switch",
97
+ body: "Named recipes run on one scale variable, so reduced motion stops every transition at once.",
98
+ },
99
+ ]
100
+
101
+ const steps = [
102
+ {
103
+ title: "Install the package",
104
+ body: "One dependency. React 19 and Tailwind v4 are the only peers.",
105
+ },
106
+ {
107
+ title: "Import the styles",
108
+ body: "Tailwind first, then Sable's tokens, then a source glob over the package.",
109
+ },
110
+ {
111
+ title: "Compose your screens",
112
+ body: "Build from components and layout primitives. Your theme does the rest.",
113
+ },
114
+ ]
115
+
116
+ const stylesCode = `@import "tailwindcss";
117
+ @import "@sableui/react/styles.css";
118
+ @source "../node_modules/@sableui/react/dist/**/*.{js,mjs}";`
119
+
120
+ // design-lint-disable-next-line self-import -- a consumer code sample displayed on the page, not an import
121
+ const composeCode = `import { Button, Card, CardContent, CardHeader, CardTitle } from "@sableui/react"
122
+
123
+ export function DeployCard() {
124
+ return (
125
+ <Card>
126
+ <CardHeader>
127
+ <CardTitle>Production</CardTitle>
128
+ </CardHeader>
129
+ <CardContent>
130
+ <Button>Deploy</Button>
131
+ </CardContent>
132
+ </Card>
133
+ )
134
+ }`
135
+
136
+ const terminalCode = `$ npx sable search "confirm before deleting"
137
+ component DestructiveActionModal
138
+ component Dialog
139
+
140
+ $ npx sable component DestructiveActionModal --props
141
+ props, a11y contract, composed examples
142
+
143
+ $ npx sable lint src
144
+ 0 findings in 42 files`
145
+
146
+ const agentPoints = [
147
+ "Search ranks components, patterns, tokens, and docs for a plain-language need.",
148
+ "Every component ships its props, accessibility contract, and composed examples.",
149
+ "The same lint runs in the editor, in CI, and inside the agent's own loop.",
150
+ ]
151
+
152
+ const footerGroups = [
153
+ {
154
+ title: "Product",
155
+ links: [
156
+ { label: "Components", href: "/components" },
157
+ { label: "Patterns", href: "/patterns" },
158
+ { label: "Themes", href: "/themes" },
159
+ { label: "Canvas", href: "/canvas" },
160
+ ],
161
+ },
162
+ {
163
+ title: "Resources",
164
+ links: [
165
+ { label: "Documentation", href: "/docs" },
166
+ { label: "Changelog", href: "/changelog" },
167
+ { label: "llms.txt", href: "/llms.txt" },
168
+ { label: "registry.json", href: "/registry.json" },
169
+ ],
170
+ },
171
+ {
172
+ title: "Community",
173
+ links: [
174
+ { label: "GitHub", href: "/github" },
175
+ { label: "npm", href: "/npm" },
176
+ { label: "Discussions", href: "/discussions" },
177
+ ],
42
178
  },
43
179
  ]
44
180
 
45
181
  export function LandingPage() {
46
182
  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
183
+ <div className="min-h-dvh bg-background text-foreground">
184
+ <header className="border-b border-border">
185
+ <Region as="nav" aria-label="Main" max="wide" className="grid h-16 grid-cols-[1fr_auto_1fr] items-center">
186
+ <a href="/" className="justify-self-start text-title-lg motion-interactive hover:text-muted-foreground">
187
+ Sable
188
+ </a>
189
+ <div className="justify-self-center max-md:hidden">
190
+ <Row gap={1}>
191
+ {navLinks.map((link) => (
192
+ <Button key={link.label} variant="ghost" size="sm" render={<a href={link.href} />}>
193
+ {link.label}
194
+ </Button>
195
+ ))}
196
+ </Row>
197
+ </div>
198
+ <Row gap={1} className="col-start-3 justify-self-end">
199
+ <div className="max-sm:hidden">
200
+ <Button variant="ghost" size="sm" render={<a href="/github" />}>
201
+ GitHub
202
+ </Button>
203
+ </div>
204
+ <Button size="sm" render={<a href="/docs/getting-started" />}>
205
+ Get started
60
206
  </Button>
61
207
  </Row>
62
- <Button size="sm" className="justify-self-end" render={<a href="/signup" />}>
63
- Get started
64
- </Button>
65
208
  </Region>
66
209
  </header>
210
+
67
211
  <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>
212
+ <Region as="section" max="wide" className="pb-16 pt-16 sm:pt-24">
213
+ <Stack gap={8} align="center">
214
+ <Stack gap={4} align="center" className="text-center">
215
+ <Badge appearance="outline" size="sm" icon={<Icon name="sparkles" />}>
216
+ Open source · MIT licensed
217
+ </Badge>
218
+ <h1 className="max-w-4xl text-balance text-display-lg sm:text-display-2xl">
219
+ Product UI your agents can't get wrong
220
+ </h1>
221
+ <p className="max-w-(--measure-prose) text-pretty text-body-lg text-muted-foreground">
222
+ Sable is a themeable React component library with enforced tokens,
223
+ typography, and motion, documented for coding agents as carefully
224
+ as for people.
225
+ </p>
226
+ <Row gap={2} justify="center" wrap className="pt-2">
227
+ <Button size="cta" render={<a href="/docs/getting-started" />}>
228
+ Get started
229
+ </Button>
230
+ <Button size="cta" variant="outline" render={<a href="/components" />}>
231
+ Browse components
232
+ </Button>
233
+ </Row>
234
+ </Stack>
235
+ <div className="w-full max-w-sm">
236
+ <CodeBlock prompt="$" code="pnpm add @sableui/react" />
237
+ </div>
86
238
  </Stack>
87
239
  </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>
240
+
241
+ <Region as="section" aria-label="Product preview" max="wide" className="pb-20 sm:pb-28">
242
+ <figure>
243
+ <Browser url="atlas.example/deploys" className="shadow-xl">
244
+ {/* A picture of software, not live UI: inert keeps its controls
245
+ out of the tab order and the accessibility tree. */}
246
+ <div inert className="grid gap-4 p-4 sm:p-6 lg:grid-cols-[minmax(0,1fr)_20rem]">
247
+ <Stack gap={4} className="min-w-0">
248
+ <PageHeader
249
+ titleAs="h2"
250
+ title="Deployments"
251
+ description="atlas-web · production"
252
+ actions={
253
+ <Row gap={2}>
254
+ <Button variant="outline" size="sm">
255
+ View logs
256
+ </Button>
257
+ <Button size="sm" iconStart={<Icon name="upload" />}>
258
+ Deploy
259
+ </Button>
260
+ </Row>
261
+ }
262
+ />
263
+ <Grid cols={{ base: 2, md: 3 }} gap={3}>
264
+ <Card density="compact">
265
+ <CardHeader>
266
+ <CardTitle>Uptime</CardTitle>
267
+ <CardDescription>Last 30 days</CardDescription>
268
+ </CardHeader>
269
+ <CardContent>
270
+ <p className="text-heading-3">99.98%</p>
271
+ </CardContent>
272
+ </Card>
273
+ <Card density="compact">
274
+ <CardHeader>
275
+ <CardTitle>p95 latency</CardTitle>
276
+ <CardDescription>Edge, all regions</CardDescription>
277
+ </CardHeader>
278
+ <CardContent>
279
+ <p className="text-heading-3">182 ms</p>
280
+ </CardContent>
281
+ </Card>
282
+ <Card density="compact" className="max-md:hidden">
283
+ <CardHeader>
284
+ <CardTitle>Build minutes</CardTitle>
285
+ <CardDescription>Team plan</CardDescription>
286
+ </CardHeader>
287
+ <CardContent>
288
+ <Meter value={64} tone="standard">
289
+ <Row justify="between" wrap className="mb-2">
290
+ <MeterLabel>Used</MeterLabel>
291
+ <MeterValue>{(_, value) => `${(value * 30).toLocaleString("en-US")} of 3,000`}</MeterValue>
292
+ </Row>
293
+ <MeterTrack />
294
+ </Meter>
295
+ </CardContent>
296
+ </Card>
297
+ </Grid>
298
+ <Card>
299
+ <CardHeader>
300
+ <CardTitle>Recent deploys</CardTitle>
301
+ <CardDescription>Production branch</CardDescription>
302
+ <CardAction>
303
+ <Badge appearance="plain" tone="success" dot>
304
+ Healthy
305
+ </Badge>
306
+ </CardAction>
307
+ </CardHeader>
308
+ <CardList>
309
+ {deploys.map((deploy) => (
310
+ <Entity
311
+ key={deploy.meta}
312
+ leading={<Avatar size="sm" fallback={deploy.initials} alt={deploy.name} />}
313
+ title={deploy.title}
314
+ subtitle={deploy.meta}
315
+ action={
316
+ <Badge appearance="plain" tone={deploy.tone} dot pulse={deploy.pulse}>
317
+ {deploy.status}
318
+ </Badge>
319
+ }
320
+ />
321
+ ))}
322
+ </CardList>
323
+ </Card>
324
+ </Stack>
325
+ <Card className="max-lg:hidden">
326
+ <CardHeader>
327
+ <CardTitle>Release assistant</CardTitle>
328
+ <CardDescription>Summaries from today's deploys</CardDescription>
329
+ </CardHeader>
330
+ <CardContent className="flex flex-col gap-4">
331
+ <Message align="end">
332
+ <Bubble variant="primary" align="end">
333
+ What shipped to production today?
334
+ </Bubble>
335
+ </Message>
336
+ <Message>
337
+ <Bubble>
338
+ Three deploys. Search ranking is fixed, webhook keys are
339
+ rotated, and usage-based billing is building now.
340
+ </Bubble>
341
+ </Message>
342
+ <ChatComposer placeholder="Ask about a deploy" />
99
343
  </CardContent>
100
344
  </Card>
101
- ))}
102
- </Grid>
345
+ </div>
346
+ </Browser>
347
+ <figcaption className="mt-4 text-center text-body-sm text-muted-foreground">
348
+ Every surface in this preview is a Sable component, styled by the active theme.
349
+ </figcaption>
350
+ </figure>
351
+ </Region>
352
+
353
+ <section aria-labelledby="landing-features" className="border-y border-border bg-muted/40 py-20 sm:py-28">
354
+ <Region max="wide">
355
+ <Stack gap={10}>
356
+ <Stack gap={3} className="max-w-2xl">
357
+ <p className="text-overline text-muted-foreground">Why Sable</p>
358
+ <h2 id="landing-features" className="text-balance text-heading-1">
359
+ A design system with the rules written down
360
+ </h2>
361
+ <p className="text-body-lg text-muted-foreground">
362
+ The decisions a senior designer would make are encoded once, so every
363
+ screen your team or your agent builds starts from them.
364
+ </p>
365
+ </Stack>
366
+ <Reveal>
367
+ <Grid cols={{ base: 1, sm: 2, lg: 3 }} gap={4}>
368
+ {features.map((feature) => (
369
+ <Card key={feature.title} render={<article />} className="h-full">
370
+ <CardContent className="flex flex-col gap-3">
371
+ <span className="grid size-10 place-items-center rounded-md sable-well">
372
+ <Icon name={feature.icon} className="text-xl" />
373
+ </span>
374
+ <h3 className="pt-2 text-title-lg">{feature.title}</h3>
375
+ <p className="text-body-sm text-muted-foreground">{feature.body}</p>
376
+ </CardContent>
377
+ </Card>
378
+ ))}
379
+ </Grid>
380
+ </Reveal>
381
+ </Stack>
382
+ </Region>
383
+ </section>
384
+
385
+ <Region as="section" aria-labelledby="landing-setup" max="wide" className="py-20 sm:py-28">
386
+ <div className="grid items-start gap-10 lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-16">
387
+ <Stack gap={8}>
388
+ <Stack gap={3}>
389
+ <p className="text-overline text-muted-foreground">Setup</p>
390
+ <h2 id="landing-setup" className="text-balance text-heading-1">
391
+ From install to first screen in three steps
392
+ </h2>
393
+ </Stack>
394
+ <ol className="flex flex-col gap-6">
395
+ {steps.map((step, index) => (
396
+ <li key={step.title} className="grid grid-cols-[auto_1fr] gap-4">
397
+ <span className="pt-0.5 text-mono-md text-muted-foreground">0{index + 1}</span>
398
+ <div>
399
+ <h3 className="text-title-lg">{step.title}</h3>
400
+ <p className="mt-1 text-body-md text-muted-foreground">{step.body}</p>
401
+ </div>
402
+ </li>
403
+ ))}
404
+ </ol>
405
+ </Stack>
406
+ <Reveal className="min-w-0">
407
+ <Tabs defaultValue="compose">
408
+ <TabsList>
409
+ <TabsTrigger value="install">Install</TabsTrigger>
410
+ <TabsTrigger value="styles">Styles</TabsTrigger>
411
+ <TabsTrigger value="compose">Compose</TabsTrigger>
412
+ </TabsList>
413
+ <TabsPanel value="install" className="pt-4">
414
+ <CodeBlock prompt="$" code="pnpm add @sableui/react" />
415
+ </TabsPanel>
416
+ <TabsPanel value="styles" className="pt-4">
417
+ <CodeBlock filename="app.css" code={stylesCode} />
418
+ </TabsPanel>
419
+ <TabsPanel value="compose" className="pt-4">
420
+ <CodeBlock filename="deploy-card.tsx" code={composeCode} />
421
+ </TabsPanel>
422
+ </Tabs>
423
+ </Reveal>
424
+ </div>
425
+ </Region>
426
+
427
+ <section aria-labelledby="landing-agents" className="border-t border-border py-20 sm:py-28">
428
+ <Region max="wide">
429
+ <div className="grid items-center gap-10 lg:grid-cols-2 lg:gap-16">
430
+ <Stack gap={5}>
431
+ <Stack gap={3}>
432
+ <p className="text-overline text-muted-foreground">Built for agents</p>
433
+ <h2 id="landing-agents" className="text-balance text-heading-1">
434
+ Your agent reads the same contract you do
435
+ </h2>
436
+ <p className="text-body-lg text-muted-foreground">
437
+ Point Claude Code, Codex, or Cursor at the installed package. The{" "}
438
+ <Code>sable</Code> CLI answers what to use, how to use it, and whether
439
+ the result holds up.
440
+ </p>
441
+ </Stack>
442
+ <ul className="sable-list">
443
+ {agentPoints.map((point) => (
444
+ <li key={point}>{point}</li>
445
+ ))}
446
+ </ul>
447
+ </Stack>
448
+ <Reveal className="min-w-0">
449
+ <CodeBlock filename="Terminal" code={terminalCode} highlight={false} />
450
+ </Reveal>
451
+ </div>
452
+ </Region>
453
+ </section>
454
+
455
+ <Region as="section" aria-labelledby="landing-cta" max="wide" className="pb-20 sm:pb-28">
456
+ <Reveal>
457
+ <Card>
458
+ <CardContent>
459
+ <Stack gap={4} align="center" className="py-8 text-center sm:py-12">
460
+ <h2 id="landing-cta" className="max-w-2xl text-balance text-heading-1">
461
+ Build the product, not another component library
462
+ </h2>
463
+ <p className="max-w-(--measure-prose) text-body-lg text-muted-foreground">
464
+ Install Sable, pick a preset, and ship the first screen today.
465
+ </p>
466
+ <Row gap={2} justify="center" wrap className="pt-2">
467
+ <Button size="cta" render={<a href="/docs/getting-started" />}>
468
+ Get started
469
+ </Button>
470
+ <Button size="cta" variant="outline" render={<a href="/github" />}>
471
+ Star on GitHub
472
+ </Button>
473
+ </Row>
474
+ </Stack>
475
+ </CardContent>
476
+ </Card>
103
477
  </Reveal>
104
478
  </Region>
105
479
  </main>
480
+
106
481
  <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>
482
+ <Region max="wide" className="py-12">
483
+ <div className="grid gap-10 md:grid-cols-[minmax(0,2fr)_minmax(0,3fr)]">
484
+ <Stack gap={3} className="max-w-xs">
485
+ <a href="/" className="w-fit text-title-lg motion-interactive hover:text-muted-foreground">
486
+ Sable
487
+ </a>
488
+ <p className="text-body-sm text-muted-foreground">
489
+ React components, patterns, and tokens for products built with AI agents.
490
+ </p>
491
+ </Stack>
492
+ <nav aria-label="Footer" className="grid grid-cols-2 gap-8 sm:grid-cols-3">
493
+ {footerGroups.map((group) => (
494
+ <div key={group.title}>
495
+ <h2 className="text-label-md text-muted-foreground">{group.title}</h2>
496
+ <ul className="mt-3 flex flex-col gap-1">
497
+ {group.links.map((link) => (
498
+ <li key={link.label}>
499
+ <a
500
+ href={link.href}
501
+ className="flex min-h-9 items-center text-body-sm motion-interactive hover:text-muted-foreground"
502
+ >
503
+ {link.label}
504
+ </a>
505
+ </li>
506
+ ))}
507
+ </ul>
508
+ </div>
509
+ ))}
510
+ </nav>
511
+ </div>
512
+ <div className="mt-12 border-t border-border pt-6">
513
+ <p className="text-caption text-muted-foreground">© 2026 Sable. Released under the MIT license.</p>
514
+ </div>
117
515
  </Region>
118
516
  </footer>
119
517
  </div>