@devalok/shilp-sutra 0.41.0 → 0.42.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.
- package/MIGRATION.md +8 -0
- package/llms-full.txt +1 -1
- package/make-kit/Guidelines.md +71 -0
- package/make-kit/components/badge.md +162 -0
- package/make-kit/components/button.md +125 -0
- package/make-kit/components/card.md +147 -0
- package/make-kit/components/dialog.md +167 -0
- package/make-kit/components/dropdown-menu.md +205 -0
- package/make-kit/components/form.md +189 -0
- package/make-kit/components/icon.md +152 -0
- package/make-kit/components/input.md +154 -0
- package/make-kit/components/overview.md +308 -0
- package/make-kit/components/popover.md +201 -0
- package/make-kit/components/select.md +148 -0
- package/make-kit/components/stack.md +165 -0
- package/make-kit/components/table.md +215 -0
- package/make-kit/components/tabs.md +162 -0
- package/make-kit/components/text.md +139 -0
- package/make-kit/components/toast.md +193 -0
- package/make-kit/foundations/color.md +128 -0
- package/make-kit/foundations/dark-mode.md +81 -0
- package/make-kit/foundations/icons.md +107 -0
- package/make-kit/foundations/motion.md +134 -0
- package/make-kit/foundations/radius.md +78 -0
- package/make-kit/foundations/spacing.md +110 -0
- package/make-kit/foundations/surfaces.md +121 -0
- package/make-kit/foundations/typography.md +120 -0
- package/make-kit/setup.md +130 -0
- package/package.json +5 -2
- package/skill/SKILL.md +1 -1
- package/skill/references/components-full.md +1 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Table
|
|
2
|
+
|
|
3
|
+
Server-safe semantic wrappers around `<table>`. For static / presentational tables.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import {
|
|
7
|
+
Table,
|
|
8
|
+
TableHeader,
|
|
9
|
+
TableBody,
|
|
10
|
+
TableFooter,
|
|
11
|
+
TableRow,
|
|
12
|
+
TableHead,
|
|
13
|
+
TableCell,
|
|
14
|
+
TableCaption,
|
|
15
|
+
} from '@devalok/shilp-sutra/ui/table'
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## When to use
|
|
19
|
+
|
|
20
|
+
- Static or small data displays where you control every row and cell.
|
|
21
|
+
- Marketing / pricing comparison tables.
|
|
22
|
+
- Documentation tables (API references, prop tables).
|
|
23
|
+
- Server-rendered tables (RSC) — Table and sub-components are server-safe.
|
|
24
|
+
- Need sorting / filtering / pagination / selection / virtualization? Use `<DataTable>` from `@devalok/shilp-sutra/ui/data-table` — out of scope for this guide.
|
|
25
|
+
|
|
26
|
+
## Compound shape
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Table (<table>)
|
|
30
|
+
TableCaption (<caption>) ← optional summary for screen readers
|
|
31
|
+
TableHeader (<thead>)
|
|
32
|
+
TableRow (<tr>)
|
|
33
|
+
TableHead (<th scope="col">)
|
|
34
|
+
TableBody (<tbody>)
|
|
35
|
+
TableRow (<tr>)
|
|
36
|
+
TableCell (<td>)
|
|
37
|
+
TableFooter (<tfoot>)
|
|
38
|
+
TableRow
|
|
39
|
+
TableCell
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Each component is a thin semantic wrapper — no props beyond standard HTML attributes plus `className`.
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
|
|
46
|
+
**Standard:**
|
|
47
|
+
```tsx
|
|
48
|
+
<Table>
|
|
49
|
+
<TableHeader>
|
|
50
|
+
<TableRow>
|
|
51
|
+
<TableHead>Name</TableHead>
|
|
52
|
+
<TableHead>Status</TableHead>
|
|
53
|
+
<TableHead>Owner</TableHead>
|
|
54
|
+
<TableHead>Updated</TableHead>
|
|
55
|
+
</TableRow>
|
|
56
|
+
</TableHeader>
|
|
57
|
+
<TableBody>
|
|
58
|
+
{projects.map((p) => (
|
|
59
|
+
<TableRow key={p.id}>
|
|
60
|
+
<TableCell>{p.name}</TableCell>
|
|
61
|
+
<TableCell>
|
|
62
|
+
<Badge color={p.status === 'active' ? 'success' : 'neutral'}>
|
|
63
|
+
{p.status}
|
|
64
|
+
</Badge>
|
|
65
|
+
</TableCell>
|
|
66
|
+
<TableCell>
|
|
67
|
+
<Stack direction="horizontal" gap="ds-02" align="center">
|
|
68
|
+
<Avatar size="xs" src={p.owner.avatar} alt={p.owner.name} />
|
|
69
|
+
<Text variant="body-sm">{p.owner.name}</Text>
|
|
70
|
+
</Stack>
|
|
71
|
+
</TableCell>
|
|
72
|
+
<TableCell>
|
|
73
|
+
<Text variant="body-sm" className="text-fg-muted">
|
|
74
|
+
{formatDate(p.updatedAt)}
|
|
75
|
+
</Text>
|
|
76
|
+
</TableCell>
|
|
77
|
+
</TableRow>
|
|
78
|
+
))}
|
|
79
|
+
</TableBody>
|
|
80
|
+
</Table>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**With caption + footer:**
|
|
84
|
+
```tsx
|
|
85
|
+
<Table>
|
|
86
|
+
<TableCaption>Q4 revenue by region.</TableCaption>
|
|
87
|
+
<TableHeader>
|
|
88
|
+
<TableRow>
|
|
89
|
+
<TableHead>Region</TableHead>
|
|
90
|
+
<TableHead>Revenue</TableHead>
|
|
91
|
+
</TableRow>
|
|
92
|
+
</TableHeader>
|
|
93
|
+
<TableBody>
|
|
94
|
+
<TableRow><TableCell>NA</TableCell><TableCell>$1.2M</TableCell></TableRow>
|
|
95
|
+
<TableRow><TableCell>EU</TableCell><TableCell>$0.8M</TableCell></TableRow>
|
|
96
|
+
<TableRow><TableCell>APAC</TableCell><TableCell>$0.4M</TableCell></TableRow>
|
|
97
|
+
</TableBody>
|
|
98
|
+
<TableFooter>
|
|
99
|
+
<TableRow>
|
|
100
|
+
<TableCell><Text variant="label-sm">Total</Text></TableCell>
|
|
101
|
+
<TableCell><Text variant="label-sm">$2.4M</Text></TableCell>
|
|
102
|
+
</TableRow>
|
|
103
|
+
</TableFooter>
|
|
104
|
+
</Table>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Row actions (IconButton in last cell):**
|
|
108
|
+
```tsx
|
|
109
|
+
<Table>
|
|
110
|
+
<TableHeader>
|
|
111
|
+
<TableRow>
|
|
112
|
+
<TableHead>File</TableHead>
|
|
113
|
+
<TableHead>Size</TableHead>
|
|
114
|
+
<TableHead className="w-12" />
|
|
115
|
+
</TableRow>
|
|
116
|
+
</TableHeader>
|
|
117
|
+
<TableBody>
|
|
118
|
+
{files.map((f) => (
|
|
119
|
+
<TableRow key={f.id}>
|
|
120
|
+
<TableCell>{f.name}</TableCell>
|
|
121
|
+
<TableCell>{formatFileSize(f.size)}</TableCell>
|
|
122
|
+
<TableCell>
|
|
123
|
+
<DropdownMenu>
|
|
124
|
+
<DropdownMenuTrigger asChild>
|
|
125
|
+
<IconButton icon={<Icon icon={IconDots} />} variant="ghost" size="sm" aria-label="Actions" />
|
|
126
|
+
</DropdownMenuTrigger>
|
|
127
|
+
<DropdownMenuContent>
|
|
128
|
+
<DropdownMenuItem onSelect={() => download(f)}>Download</DropdownMenuItem>
|
|
129
|
+
<DropdownMenuSeparator />
|
|
130
|
+
<DropdownMenuItem onSelect={() => remove(f)}>Delete</DropdownMenuItem>
|
|
131
|
+
</DropdownMenuContent>
|
|
132
|
+
</DropdownMenu>
|
|
133
|
+
</TableCell>
|
|
134
|
+
</TableRow>
|
|
135
|
+
))}
|
|
136
|
+
</TableBody>
|
|
137
|
+
</Table>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Inside a Card:**
|
|
141
|
+
```tsx
|
|
142
|
+
<Card>
|
|
143
|
+
<CardHeader>
|
|
144
|
+
<CardTitle>Team members</CardTitle>
|
|
145
|
+
</CardHeader>
|
|
146
|
+
<CardContent>
|
|
147
|
+
<Table>
|
|
148
|
+
<TableHeader>
|
|
149
|
+
<TableRow>
|
|
150
|
+
<TableHead>Member</TableHead>
|
|
151
|
+
<TableHead>Role</TableHead>
|
|
152
|
+
</TableRow>
|
|
153
|
+
</TableHeader>
|
|
154
|
+
<TableBody>
|
|
155
|
+
{members.map((m) => (
|
|
156
|
+
<TableRow key={m.id}>
|
|
157
|
+
<TableCell>{m.name}</TableCell>
|
|
158
|
+
<TableCell><Badge color="neutral">{m.role}</Badge></TableCell>
|
|
159
|
+
</TableRow>
|
|
160
|
+
))}
|
|
161
|
+
</TableBody>
|
|
162
|
+
</Table>
|
|
163
|
+
</CardContent>
|
|
164
|
+
</Card>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
When inside a `<Card>`, the Table's surrounding padding comes from `CardContent`. Don't add extra padding on the Table.
|
|
168
|
+
|
|
169
|
+
**Server-rendered table (RSC):**
|
|
170
|
+
```tsx
|
|
171
|
+
// app/projects/page.tsx — no 'use client'
|
|
172
|
+
export default async function ProjectsPage() {
|
|
173
|
+
const projects = await db.projects.findMany()
|
|
174
|
+
return (
|
|
175
|
+
<Table>
|
|
176
|
+
<TableHeader>
|
|
177
|
+
<TableRow>
|
|
178
|
+
<TableHead>Name</TableHead>
|
|
179
|
+
<TableHead>Status</TableHead>
|
|
180
|
+
</TableRow>
|
|
181
|
+
</TableHeader>
|
|
182
|
+
<TableBody>
|
|
183
|
+
{projects.map((p) => (
|
|
184
|
+
<TableRow key={p.id}>
|
|
185
|
+
<TableCell>{p.name}</TableCell>
|
|
186
|
+
<TableCell><Badge>{p.status}</Badge></TableCell>
|
|
187
|
+
</TableRow>
|
|
188
|
+
))}
|
|
189
|
+
</TableBody>
|
|
190
|
+
</Table>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Table + Badge (server-safe via Badge.Group context — verify per use) + Avatar all render in RSC trees. Skip client components.
|
|
196
|
+
|
|
197
|
+
## Composability
|
|
198
|
+
|
|
199
|
+
- **Server-safe:** Table and its sub-components are pure HTML semantic wrappers. No state, no context. Use in RSC trees without `'use client'`.
|
|
200
|
+
- **TableHead scope:** Headers automatically get `scope="col"` for screen-reader navigation. Don't override it.
|
|
201
|
+
- **Composes with primitives:** Drop `<Badge>`, `<Avatar>`, `<IconButton>`, `<StatusDot>` inside cells. Check each component's server-safety if you need RSC compatibility.
|
|
202
|
+
- **TableCaption:** Renders as HTML `<caption>` — screen readers announce it before content. Use it for any non-trivial table.
|
|
203
|
+
|
|
204
|
+
See `foundations/typography.md` for the body / label variants inside cells, `foundations/surfaces.md` for table-in-card surface guidance.
|
|
205
|
+
|
|
206
|
+
## Rules
|
|
207
|
+
|
|
208
|
+
- For anything with sorting / filtering / pagination / selection / virtualization, use `<DataTable>` from `/ui/data-table`. Don't rebuild that machinery on bare Table.
|
|
209
|
+
- Always wrap header cells in `<TableHead>` (renders `<th>`). Don't use `<TableCell>` (`<td>`) in headers — breaks screen-reader column scope.
|
|
210
|
+
- Use `<TableCaption>` for any table that's not self-evident. Renders the HTML `<caption>` which screen readers announce.
|
|
211
|
+
- Inside Card, drop the Table directly in `<CardContent>` — don't add wrapper divs that fight the card's padding cascade.
|
|
212
|
+
- For wide tables on mobile, wrap in an `overflow-x-auto` container. Don't try to make a Table responsive via column stacking — switch to a card list on small viewports.
|
|
213
|
+
- Don't style cell text with raw Tailwind palette utilities. Use `text-fg-muted` from `foundations/color.md`.
|
|
214
|
+
- Compose with Badge for status, Avatar for users, IconButton for row actions. Don't invent new primitives per table.
|
|
215
|
+
- Keep TableCell content single-line where possible — multi-line cells make scanning hard. Use `<Stack>` only when each row genuinely needs two lines.
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Tabs
|
|
2
|
+
|
|
3
|
+
Switch between sibling views inside a single region. Not for top-level navigation.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@devalok/shilp-sutra/ui/tabs'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- Sub-sections of a single page / panel that share context (Overview / Activity / Settings on a project).
|
|
12
|
+
- Filtered views over the same dataset (All / Mine / Archived).
|
|
13
|
+
- Need URL-driven routes per tab? Wire `value` / `onValueChange` to router state.
|
|
14
|
+
- Top-level app navigation? Use `<Sidebar>` / `<TopBar>`, not Tabs.
|
|
15
|
+
- Multi-step flows? Use `<Stepper>` or a wizard pattern.
|
|
16
|
+
|
|
17
|
+
## Compound shape
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
Tabs (root — value, defaultValue, onValueChange)
|
|
21
|
+
TabsList (variant, size, orientation)
|
|
22
|
+
TabsTrigger (value) ← inherits variant/size/orientation from TabsList
|
|
23
|
+
TabsContent (value) ← rendered inline (not portalled)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## TabsList props
|
|
27
|
+
|
|
28
|
+
| Prop | Type | Notes |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `variant` | `'line'\|'contained'` | Default `line`. |
|
|
31
|
+
| `size` | `'sm'\|'md'\|'lg'` | Default `md`. |
|
|
32
|
+
| `orientation` | `'horizontal'\|'vertical'` | Default `horizontal`. Vertical also changes keyboard nav to ArrowUp/Down. |
|
|
33
|
+
| `color` | `'accent'\|'neutral'` | Affects the line-variant active indicator. |
|
|
34
|
+
|
|
35
|
+
## Variants
|
|
36
|
+
|
|
37
|
+
| Variant | When |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `line` (default) | Underline active indicator. Most common — pairs with section headings. |
|
|
40
|
+
| `contained` | Pill background per active trigger. Use inside cards or compact toolbars. |
|
|
41
|
+
|
|
42
|
+
## Root state props (Radix passthrough)
|
|
43
|
+
|
|
44
|
+
| Prop | Type | Notes |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `value` | `string` | Controlled active tab. |
|
|
47
|
+
| `defaultValue` | `string` | Uncontrolled initial tab. |
|
|
48
|
+
| `onValueChange` | `(value: string) => void` | Fires on tab change. |
|
|
49
|
+
|
|
50
|
+
## TabsTrigger / TabsContent
|
|
51
|
+
|
|
52
|
+
Both require a `value: string` prop. The values must match between a trigger and its content.
|
|
53
|
+
|
|
54
|
+
`TabsTrigger` reads `variant` / `size` / `orientation` from `TabsList` via context. Override per-trigger if needed, but normally don't.
|
|
55
|
+
|
|
56
|
+
## Examples
|
|
57
|
+
|
|
58
|
+
**Standard line tabs:**
|
|
59
|
+
```tsx
|
|
60
|
+
<Tabs defaultValue="overview">
|
|
61
|
+
<TabsList>
|
|
62
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
63
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
64
|
+
<TabsTrigger value="settings">Settings</TabsTrigger>
|
|
65
|
+
</TabsList>
|
|
66
|
+
<TabsContent value="overview">
|
|
67
|
+
<ProjectOverview />
|
|
68
|
+
</TabsContent>
|
|
69
|
+
<TabsContent value="activity">
|
|
70
|
+
<ActivityFeed />
|
|
71
|
+
</TabsContent>
|
|
72
|
+
<TabsContent value="settings">
|
|
73
|
+
<ProjectSettings />
|
|
74
|
+
</TabsContent>
|
|
75
|
+
</Tabs>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Contained variant inside a card:**
|
|
79
|
+
```tsx
|
|
80
|
+
<Card>
|
|
81
|
+
<CardContent>
|
|
82
|
+
<Tabs defaultValue="day">
|
|
83
|
+
<TabsList variant="contained" size="sm">
|
|
84
|
+
<TabsTrigger value="day">Day</TabsTrigger>
|
|
85
|
+
<TabsTrigger value="week">Week</TabsTrigger>
|
|
86
|
+
<TabsTrigger value="month">Month</TabsTrigger>
|
|
87
|
+
</TabsList>
|
|
88
|
+
<TabsContent value="day"><Chart range="day" /></TabsContent>
|
|
89
|
+
<TabsContent value="week"><Chart range="week" /></TabsContent>
|
|
90
|
+
<TabsContent value="month"><Chart range="month" /></TabsContent>
|
|
91
|
+
</Tabs>
|
|
92
|
+
</CardContent>
|
|
93
|
+
</Card>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Vertical orientation (settings-style):**
|
|
97
|
+
```tsx
|
|
98
|
+
<Tabs defaultValue="account" orientation="vertical">
|
|
99
|
+
<Stack direction="horizontal" gap="ds-07" align="start">
|
|
100
|
+
<TabsList orientation="vertical">
|
|
101
|
+
<TabsTrigger value="account">Account</TabsTrigger>
|
|
102
|
+
<TabsTrigger value="billing">Billing</TabsTrigger>
|
|
103
|
+
<TabsTrigger value="notifications">Notifications</TabsTrigger>
|
|
104
|
+
</TabsList>
|
|
105
|
+
<div className="flex-1">
|
|
106
|
+
<TabsContent value="account"><AccountForm /></TabsContent>
|
|
107
|
+
<TabsContent value="billing"><BillingForm /></TabsContent>
|
|
108
|
+
<TabsContent value="notifications"><NotificationsForm /></TabsContent>
|
|
109
|
+
</div>
|
|
110
|
+
</Stack>
|
|
111
|
+
</Tabs>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**With icons + badges:**
|
|
115
|
+
```tsx
|
|
116
|
+
<Tabs defaultValue="inbox">
|
|
117
|
+
<TabsList>
|
|
118
|
+
<TabsTrigger value="inbox">
|
|
119
|
+
<Icon icon={IconInbox} /> Inbox
|
|
120
|
+
<Badge size="xs" color="accent">12</Badge>
|
|
121
|
+
</TabsTrigger>
|
|
122
|
+
<TabsTrigger value="sent">
|
|
123
|
+
<Icon icon={IconSend} /> Sent
|
|
124
|
+
</TabsTrigger>
|
|
125
|
+
</TabsList>
|
|
126
|
+
<TabsContent value="inbox"><InboxList /></TabsContent>
|
|
127
|
+
<TabsContent value="sent"><SentList /></TabsContent>
|
|
128
|
+
</Tabs>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Router-driven (Next.js App Router):**
|
|
132
|
+
```tsx
|
|
133
|
+
'use client'
|
|
134
|
+
const router = useRouter()
|
|
135
|
+
const pathname = usePathname()
|
|
136
|
+
const tab = pathname.split('/').pop() ?? 'overview'
|
|
137
|
+
|
|
138
|
+
<Tabs value={tab} onValueChange={(v) => router.push(`/projects/${id}/${v}`)}>
|
|
139
|
+
<TabsList>
|
|
140
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
141
|
+
<TabsTrigger value="activity">Activity</TabsTrigger>
|
|
142
|
+
</TabsList>
|
|
143
|
+
</Tabs>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Composability
|
|
147
|
+
|
|
148
|
+
- **Context cascade:** `TabsList` propagates `variant` / `size` / `orientation` to every child `TabsTrigger`. Don't repeat those props on each trigger.
|
|
149
|
+
- **Inline content:** `TabsContent` renders inline (not portalled). Container-scoped queries in tests work.
|
|
150
|
+
- **Keyboard:** Roving tabindex via Radix — ArrowLeft/Right (horizontal) or ArrowUp/Down (vertical). Home / End jump to first / last. Don't re-implement.
|
|
151
|
+
|
|
152
|
+
See `foundations/spacing.md` for the gap between TabsList and TabsContent, `foundations/icons.md` for icon sizing inside triggers.
|
|
153
|
+
|
|
154
|
+
## Rules
|
|
155
|
+
|
|
156
|
+
- Put `variant` / `size` / `orientation` on `TabsList`, NOT on `Tabs` root or `TabsTrigger`.
|
|
157
|
+
- Every `TabsTrigger` and `TabsContent` needs a `value` — the values must match.
|
|
158
|
+
- For top-level app navigation use Sidebar / TopBar, not Tabs.
|
|
159
|
+
- Don't stack two Tabs inside the same region — pick one. Nested tabs confuse keyboard nav and section structure.
|
|
160
|
+
- For router-bound tabs, keep `value` controlled — don't mix `defaultValue` with router-driven URLs.
|
|
161
|
+
- For 5+ tabs that overflow on mobile, consider a Select dropdown on small viewports or wrap in a horizontally scrollable container.
|
|
162
|
+
- Icons in TabsTrigger don't auto-size via IconProvider — set explicit `<Icon icon={...} size="sm" />` when the trigger feels off.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Text
|
|
2
|
+
|
|
3
|
+
Typography primitive. Use instead of raw `<h1>`–`<h6>`, `<p>`, `<span>` for any visible text.
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Text } from '@devalok/shilp-sutra/ui/text'
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- Any visible text: headings, body copy, captions, section labels, inline microcopy.
|
|
12
|
+
- Inline code spans inside body text? Pair with `<Code>`.
|
|
13
|
+
- Polymorphic — `<Text as="span">`, `<Text as="div">`, etc. — to demote semantics while keeping visual weight.
|
|
14
|
+
- Server-safe — renders in RSC trees without `'use client'`.
|
|
15
|
+
|
|
16
|
+
## Variants
|
|
17
|
+
|
|
18
|
+
| Variant | Default element | Use |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `heading-2xl` | `h1` | Page title / hero. |
|
|
21
|
+
| `heading-xl` | `h2` | Section title. |
|
|
22
|
+
| `heading-lg` | `h3` | Sub-section title. |
|
|
23
|
+
| `heading-md` | `h4` | Card title (used by `<CardTitle>`). |
|
|
24
|
+
| `heading-sm` | `h5` | Compact sub-heading. |
|
|
25
|
+
| `heading-xs` | `h6` | Smallest heading. |
|
|
26
|
+
| `body-lg` | `p` | Lead paragraphs. |
|
|
27
|
+
| `body-md` (default) | `p` | Standard body. |
|
|
28
|
+
| `body-sm` | `p` | Dense body — captions inside cards, table cells. |
|
|
29
|
+
| `body-xs` | `p` | Footnotes, fine print. |
|
|
30
|
+
| `label-lg` / `md` / `sm` / `xs` | `span` | UPPERCASE section labels, eyebrows. |
|
|
31
|
+
| `label-plain-lg` / `md` / `sm` / `xs` | `span` | Mixed-case labels (form labels, inline UI text). |
|
|
32
|
+
| `caption` | `span` | Image / chart captions. |
|
|
33
|
+
| `overline` | `span` | UPPERCASE marketing eyebrow. |
|
|
34
|
+
| `code` | `code` | Inline monospace code. |
|
|
35
|
+
|
|
36
|
+
`label-*` and `overline` variants are automatically uppercase. `label-plain-*` keeps mixed case.
|
|
37
|
+
|
|
38
|
+
## Props
|
|
39
|
+
|
|
40
|
+
| Prop | Type | Notes |
|
|
41
|
+
|---|---|---|
|
|
42
|
+
| `variant` | See variants table | Default `body-md`. |
|
|
43
|
+
| `as` | `ElementType` | Override the auto-selected HTML element. |
|
|
44
|
+
| `className` | `string` | For color / alignment overrides. Use semantic tokens only. |
|
|
45
|
+
|
|
46
|
+
Plus all standard HTML attributes for whatever element it renders.
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
**Page heading + body:**
|
|
51
|
+
```tsx
|
|
52
|
+
<Stack gap="ds-04">
|
|
53
|
+
<Text variant="heading-2xl">Projects</Text>
|
|
54
|
+
<Text variant="body-md" className="text-fg-muted">
|
|
55
|
+
A workspace for everything you ship.
|
|
56
|
+
</Text>
|
|
57
|
+
</Stack>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Section label + heading pair:**
|
|
61
|
+
```tsx
|
|
62
|
+
<Stack gap="ds-02">
|
|
63
|
+
<Text variant="label-sm" className="text-fg-muted">REVENUE</Text>
|
|
64
|
+
<Text variant="heading-xl">$2.4M</Text>
|
|
65
|
+
<Text variant="body-sm" className="text-fg-muted">+18% YoY</Text>
|
|
66
|
+
</Stack>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Visual demotion via `as`:**
|
|
70
|
+
```tsx
|
|
71
|
+
{/* h2-sized heading rendered as a div — useful when the element already has a heading ancestor */}
|
|
72
|
+
<Card>
|
|
73
|
+
<CardHeader>
|
|
74
|
+
<CardTitle>Activity</CardTitle> {/* h4 */}
|
|
75
|
+
<Text variant="heading-xl" as="div">Weekly summary</Text>
|
|
76
|
+
</CardHeader>
|
|
77
|
+
</Card>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Inline span inside a paragraph:**
|
|
81
|
+
```tsx
|
|
82
|
+
<Text>
|
|
83
|
+
Press <Text as="kbd" variant="code">⌘ K</Text> to open the command palette.
|
|
84
|
+
</Text>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Caption under an image:**
|
|
88
|
+
```tsx
|
|
89
|
+
<Stack gap="ds-02">
|
|
90
|
+
<img src={chart} alt="" />
|
|
91
|
+
<Text variant="caption" className="text-fg-muted">
|
|
92
|
+
Figure 1. Active users by region.
|
|
93
|
+
</Text>
|
|
94
|
+
</Stack>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Form field label (label-plain variant):**
|
|
98
|
+
```tsx
|
|
99
|
+
<Stack gap="ds-02">
|
|
100
|
+
<Text variant="label-plain-sm" as="label" htmlFor="email">Email</Text>
|
|
101
|
+
<Input id="email" type="email" />
|
|
102
|
+
</Stack>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Use `<Label>` for form fields when wired with FormField — `Text` is for non-form labels.
|
|
106
|
+
|
|
107
|
+
**Body with inline code:**
|
|
108
|
+
```tsx
|
|
109
|
+
<Text>
|
|
110
|
+
Call <Code>onSubmit</Code> with the form values, or pass <Code>asChild</Code> to merge with a child element.
|
|
111
|
+
</Text>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Truncated single line:**
|
|
115
|
+
```tsx
|
|
116
|
+
<Text variant="body-sm" className="truncate max-w-[200px]">
|
|
117
|
+
{longUserName}
|
|
118
|
+
</Text>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Composability
|
|
122
|
+
|
|
123
|
+
- **Server-safe.** No client hooks. Use freely in RSC trees.
|
|
124
|
+
- **No context cascade** — pure typography primitive. Variants don't propagate through children.
|
|
125
|
+
- **Underpins other components.** `<CardTitle>`, `<Alert>`'s title, `<PageHeader>`, `<EmptyState>`, `<SectionHeader>` all render Text internally with specific variants. Don't wrap another Text inside them.
|
|
126
|
+
- **`as` overrides element only, not variant.** Visual weight stays. Use to demote semantics when you have a heading ancestor.
|
|
127
|
+
|
|
128
|
+
See `foundations/typography.md` for the full type scale, line-height tokens, font stack.
|
|
129
|
+
|
|
130
|
+
## Rules
|
|
131
|
+
|
|
132
|
+
- Use Text for every visible text element. Don't write raw `<h1>` / `<p>` / `<span>` with manual classes.
|
|
133
|
+
- Pick variant by semantic intent, not visual size. `heading-xl` is an `<h2>` — use it for section headings, not because you want big text in a body paragraph (use `as="div"` for that).
|
|
134
|
+
- Don't wrap a `<Text>` inside `<CardTitle>` / `<Alert>` title — they already render Text internally.
|
|
135
|
+
- `label-*` and `overline` variants are automatically uppercase. Don't add `uppercase` class.
|
|
136
|
+
- For form labels paired with controls, use `<Label htmlFor>` from `/ui/label`. `Text variant="label-plain-*"` is for non-form labels.
|
|
137
|
+
- Color overrides go through semantic tokens (`text-fg-muted`, `text-fg`, `text-fg-subtle`). Never raw Tailwind palette utilities.
|
|
138
|
+
- Don't use Text inside another semantic heading — it produces nested headings that break screen-reader navigation.
|
|
139
|
+
- For inline code, use `<Code>`. `Text variant="code"` works but Code is the dedicated primitive.
|