@tempoxyz/ds 0.3.10 → 0.3.13

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 (210) hide show
  1. package/README.md +7 -6
  2. package/dist/docs/AGENTS.md +12 -0
  3. package/dist/docs/index.md +145 -0
  4. package/dist/docs/marketing/introduction/getting-started.md +241 -0
  5. package/dist/docs/product/components/accordion.md +489 -0
  6. package/dist/docs/product/components/address-card.md +147 -0
  7. package/dist/docs/product/components/agent-logo.md +194 -0
  8. package/dist/docs/product/components/alert-dialog.md +631 -0
  9. package/dist/docs/product/components/alert.md +448 -0
  10. package/dist/docs/product/components/amount-input.md +305 -0
  11. package/dist/docs/product/components/amount.md +794 -0
  12. package/dist/docs/product/components/app-shell.md +559 -0
  13. package/dist/docs/product/components/asset-chip.md +294 -0
  14. package/dist/docs/product/components/autocomplete.md +620 -0
  15. package/dist/docs/product/components/avatar.md +321 -0
  16. package/dist/docs/product/components/badge.md +339 -0
  17. package/dist/docs/product/components/badged-logo.md +116 -0
  18. package/dist/docs/product/components/banner.md +379 -0
  19. package/dist/docs/product/components/bar-chart.md +208 -0
  20. package/dist/docs/product/components/breadcrumbs.md +380 -0
  21. package/dist/docs/product/components/button-group.md +286 -0
  22. package/dist/docs/product/components/button.md +650 -0
  23. package/dist/docs/product/components/calendar.md +375 -0
  24. package/dist/docs/product/components/callout.md +96 -0
  25. package/dist/docs/product/components/checkbox-group.md +521 -0
  26. package/dist/docs/product/components/checkbox.md +467 -0
  27. package/dist/docs/product/components/choice-group.md +516 -0
  28. package/dist/docs/product/components/choicebox.md +641 -0
  29. package/dist/docs/product/components/code.md +100 -0
  30. package/dist/docs/product/components/combobox.md +939 -0
  31. package/dist/docs/product/components/container.md +257 -0
  32. package/dist/docs/product/components/copy-button.md +642 -0
  33. package/dist/docs/product/components/date-picker.md +514 -0
  34. package/dist/docs/product/components/dialog.md +1332 -0
  35. package/dist/docs/product/components/drawer.md +966 -0
  36. package/dist/docs/product/components/empty.md +340 -0
  37. package/dist/docs/product/components/field.md +570 -0
  38. package/dist/docs/product/components/fieldset.md +398 -0
  39. package/dist/docs/product/components/frame.md +434 -0
  40. package/dist/docs/product/components/gauge.md +299 -0
  41. package/dist/docs/product/components/get-started-visual.md +117 -0
  42. package/dist/docs/product/components/grid.md +288 -0
  43. package/dist/docs/product/components/hero-visual.md +155 -0
  44. package/dist/docs/product/components/hex.md +487 -0
  45. package/dist/docs/product/components/icon-tile.md +292 -0
  46. package/dist/docs/product/components/identicon.md +371 -0
  47. package/dist/docs/product/components/inline.md +233 -0
  48. package/dist/docs/product/components/inset.md +225 -0
  49. package/dist/docs/product/components/kbd.md +423 -0
  50. package/dist/docs/product/components/list-group.md +981 -0
  51. package/dist/docs/product/components/loading-dots.md +237 -0
  52. package/dist/docs/product/components/menu.md +934 -0
  53. package/dist/docs/product/components/native-select.md +204 -0
  54. package/dist/docs/product/components/network-logo.md +248 -0
  55. package/dist/docs/product/components/number-field.md +543 -0
  56. package/dist/docs/product/components/otp-field.md +616 -0
  57. package/dist/docs/product/components/pagination.md +516 -0
  58. package/dist/docs/product/components/popover.md +544 -0
  59. package/dist/docs/product/components/progress.md +388 -0
  60. package/dist/docs/product/components/qr.md +314 -0
  61. package/dist/docs/product/components/radio-group.md +528 -0
  62. package/dist/docs/product/components/secret-value.md +406 -0
  63. package/dist/docs/product/components/select.md +751 -0
  64. package/dist/docs/product/components/separator.md +379 -0
  65. package/dist/docs/product/components/sidebar-menu.md +922 -0
  66. package/dist/docs/product/components/skeleton.md +517 -0
  67. package/dist/docs/product/components/slider.md +539 -0
  68. package/dist/docs/product/components/snippet.md +424 -0
  69. package/dist/docs/product/components/spinner.md +216 -0
  70. package/dist/docs/product/components/split-bar.md +139 -0
  71. package/dist/docs/product/components/split-button.md +464 -0
  72. package/dist/docs/product/components/stack.md +299 -0
  73. package/dist/docs/product/components/status-chip.md +226 -0
  74. package/dist/docs/product/components/status-indicator.md +119 -0
  75. package/dist/docs/product/components/status-row.md +174 -0
  76. package/dist/docs/product/components/stepper.md +451 -0
  77. package/dist/docs/product/components/success-hero.md +157 -0
  78. package/dist/docs/product/components/surface.md +639 -0
  79. package/dist/docs/product/components/switch.md +472 -0
  80. package/dist/docs/product/components/table.md +967 -0
  81. package/dist/docs/product/components/tabs.md +627 -0
  82. package/dist/docs/product/components/tempo-logo.md +185 -0
  83. package/dist/docs/product/components/text-input.md +624 -0
  84. package/dist/docs/product/components/textarea.md +352 -0
  85. package/dist/docs/product/components/toast.md +535 -0
  86. package/dist/docs/product/components/token-logo.md +371 -0
  87. package/dist/docs/product/components/token.md +247 -0
  88. package/dist/docs/product/components/tooltip.md +490 -0
  89. package/dist/docs/product/components/verification-code.md +116 -0
  90. package/dist/docs/product/components/visual.md +249 -0
  91. package/dist/docs/product/components.md +203 -0
  92. package/dist/docs/product/foundations/accessibility.md +377 -0
  93. package/dist/docs/product/foundations/agents.md +150 -0
  94. package/dist/docs/product/foundations/color.md +522 -0
  95. package/dist/docs/product/foundations/content/tone.md +194 -0
  96. package/dist/docs/product/foundations/content/voice.md +194 -0
  97. package/dist/docs/product/foundations/content/writing-style.md +406 -0
  98. package/dist/docs/product/foundations/iconography.md +282 -0
  99. package/dist/docs/product/foundations/intent.md +360 -0
  100. package/dist/docs/product/foundations/layout.md +508 -0
  101. package/dist/docs/product/foundations/linter.md +496 -0
  102. package/dist/docs/product/foundations/motion.md +151 -0
  103. package/dist/docs/product/foundations/radius-and-padding.md +86 -0
  104. package/dist/docs/product/foundations/typography.md +266 -0
  105. package/dist/docs/product/introduction/faq.md +133 -0
  106. package/dist/docs/product/introduction/getting-started.md +377 -0
  107. package/dist/docs/product/patterns/blockchain-values.md +709 -0
  108. package/dist/docs/product/patterns/dashboards.md +638 -0
  109. package/dist/docs/product/patterns/destructive-actions.md +626 -0
  110. package/dist/docs/product/patterns/empty-states.md +612 -0
  111. package/dist/docs/product/patterns/errors-and-validation.md +808 -0
  112. package/dist/docs/product/patterns/feedback-and-status.md +518 -0
  113. package/dist/docs/product/patterns/forms.md +679 -0
  114. package/dist/docs/product/patterns/loading.md +630 -0
  115. package/dist/docs/product/patterns/navigation.md +397 -0
  116. package/dist/docs/product/patterns/payments.md +765 -0
  117. package/dist/docs/product/patterns/responsive-layout.md +997 -0
  118. package/dist/docs/product/patterns/tables-and-lists.md +964 -0
  119. package/dist/docs/product/products/tempo-console.md +671 -0
  120. package/dist/docs/product/products/tempo-performance.md +437 -0
  121. package/dist/docs/product/products/tempo-wallet.md +438 -0
  122. package/dist/marketing/index.d.ts +4 -0
  123. package/dist/marketing/index.d.ts.map +1 -0
  124. package/dist/marketing/index.js +4 -0
  125. package/dist/marketing/index.js.map +1 -0
  126. package/dist/marketing/index.js.zyzz.json +1 -0
  127. package/dist/marketing/vars.d.ts +907 -0
  128. package/dist/marketing/vars.d.ts.map +1 -0
  129. package/dist/marketing/vars.js +15 -0
  130. package/dist/marketing/vars.js.map +1 -0
  131. package/dist/marketing/vars.js.zyzz.json +1 -0
  132. package/dist/marketing/zyzz.config.d.ts +4512 -0
  133. package/dist/marketing/zyzz.config.d.ts.map +1 -0
  134. package/dist/marketing/zyzz.config.js +63 -0
  135. package/dist/marketing/zyzz.config.js.map +1 -0
  136. package/dist/marketing/zyzz.config.js.zyzz.json +1 -0
  137. package/dist/oxlint/index.d.ts +12 -1
  138. package/dist/oxlint/index.d.ts.map +1 -1
  139. package/dist/oxlint/index.js +39 -1
  140. package/dist/oxlint/index.js.map +1 -1
  141. package/dist/oxlint/internal/Docs.d.ts +9 -0
  142. package/dist/oxlint/internal/Docs.d.ts.map +1 -0
  143. package/dist/oxlint/internal/Docs.js +15 -0
  144. package/dist/oxlint/internal/Docs.js.map +1 -0
  145. package/dist/oxlint/internal/Imports.d.ts +1 -1
  146. package/dist/oxlint/internal/Imports.d.ts.map +1 -1
  147. package/dist/oxlint/internal/Imports.js +2 -0
  148. package/dist/oxlint/internal/Imports.js.map +1 -1
  149. package/dist/oxlint/internal/Styles.d.ts +40 -0
  150. package/dist/oxlint/internal/Styles.d.ts.map +1 -0
  151. package/dist/oxlint/internal/Styles.js +194 -0
  152. package/dist/oxlint/internal/Styles.js.map +1 -0
  153. package/dist/oxlint/internal/Tokens.d.ts +16 -0
  154. package/dist/oxlint/internal/Tokens.d.ts.map +1 -0
  155. package/dist/oxlint/internal/Tokens.js +39 -0
  156. package/dist/oxlint/internal/Tokens.js.map +1 -0
  157. package/dist/oxlint/rules/NoCrossSurface.d.ts +2 -2
  158. package/dist/oxlint/rules/NoCrossSurface.js +3 -3
  159. package/dist/oxlint/rules/NoCrossSurface.js.map +1 -1
  160. package/dist/oxlint/rules/NoCustomValues.d.ts +11 -0
  161. package/dist/oxlint/rules/NoCustomValues.d.ts.map +1 -0
  162. package/dist/oxlint/rules/NoCustomValues.js +150 -0
  163. package/dist/oxlint/rules/NoCustomValues.js.map +1 -0
  164. package/dist/oxlint/rules/NoSingleKeyShortcuts.d.ts +11 -0
  165. package/dist/oxlint/rules/NoSingleKeyShortcuts.d.ts.map +1 -0
  166. package/dist/oxlint/rules/NoSingleKeyShortcuts.js +158 -0
  167. package/dist/oxlint/rules/NoSingleKeyShortcuts.js.map +1 -0
  168. package/dist/oxlint/rules/PreferComponent.d.ts +3 -2
  169. package/dist/oxlint/rules/PreferComponent.d.ts.map +1 -1
  170. package/dist/oxlint/rules/PreferComponent.js +11 -4
  171. package/dist/oxlint/rules/PreferComponent.js.map +1 -1
  172. package/dist/oxlint/rules/PreferLayout.d.ts +9 -0
  173. package/dist/oxlint/rules/PreferLayout.d.ts.map +1 -0
  174. package/dist/oxlint/rules/PreferLayout.js +90 -0
  175. package/dist/oxlint/rules/PreferLayout.js.map +1 -0
  176. package/dist/oxlint/rules/PreferProgress.d.ts +9 -0
  177. package/dist/oxlint/rules/PreferProgress.d.ts.map +1 -0
  178. package/dist/oxlint/rules/PreferProgress.js +43 -0
  179. package/dist/oxlint/rules/PreferProgress.js.map +1 -0
  180. package/dist/oxlint/rules/PreferSurface.d.ts +9 -0
  181. package/dist/oxlint/rules/PreferSurface.d.ts.map +1 -0
  182. package/dist/oxlint/rules/PreferSurface.js +55 -0
  183. package/dist/oxlint/rules/PreferSurface.js.map +1 -0
  184. package/dist/oxlint/rules/RequireAccessibleName.d.ts.map +1 -1
  185. package/dist/oxlint/rules/RequireAccessibleName.js +3 -1
  186. package/dist/oxlint/rules/RequireAccessibleName.js.map +1 -1
  187. package/dist/oxlint/rules/RequireScrollRegion.d.ts +11 -0
  188. package/dist/oxlint/rules/RequireScrollRegion.d.ts.map +1 -0
  189. package/dist/oxlint/rules/RequireScrollRegion.js +71 -0
  190. package/dist/oxlint/rules/RequireScrollRegion.js.map +1 -0
  191. package/dist/web/icons/index.js.zyzz.json +1 -0
  192. package/package.json +6 -1
  193. package/src/marketing/README.md +21 -0
  194. package/src/marketing/index.ts +3 -0
  195. package/src/marketing/vars.ts +751 -0
  196. package/src/marketing/zyzz.config.ts +376 -0
  197. package/src/oxlint/index.ts +40 -1
  198. package/src/oxlint/internal/Docs.ts +18 -0
  199. package/src/oxlint/internal/Imports.ts +2 -1
  200. package/src/oxlint/internal/Styles.ts +240 -0
  201. package/src/oxlint/internal/Tokens.ts +43 -0
  202. package/src/oxlint/rules/NoCrossSurface.ts +3 -3
  203. package/src/oxlint/rules/NoCustomValues.ts +170 -0
  204. package/src/oxlint/rules/NoSingleKeyShortcuts.ts +190 -0
  205. package/src/oxlint/rules/PreferComponent.ts +11 -4
  206. package/src/oxlint/rules/PreferLayout.ts +96 -0
  207. package/src/oxlint/rules/PreferProgress.ts +49 -0
  208. package/src/oxlint/rules/PreferSurface.ts +70 -0
  209. package/src/oxlint/rules/RequireAccessibleName.ts +3 -1
  210. package/src/oxlint/rules/RequireScrollRegion.ts +92 -0
@@ -0,0 +1,967 @@
1
+ # Table
2
+
3
+ Displays tabular data in rows and columns.
4
+
5
+ [View source and API](https://github.com/tempoxyz/ds/blob/main/src/web/components/Table.tsx)
6
+
7
+ Figma: [Data/Table Card](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3500) (`Table.Root`), [Data/Table Cell](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3635) (`Table.Cell`), [Data/Table Empty](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3723) (`Table.Cell`), [Data/Table Footer](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3719) (`Table.Caption`), [Data/Table Header Cell](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3681) (`Table.HeaderCell`), [Data/Table Lead Tile](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3661) (`Table.LeadTile`), [Data/Table Row](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3589) (`Table.Row`), [Dense Table](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=3506-2536) (`Table.Root`), [Simple Table](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=3506-2513) (`Table.Root`)
8
+
9
+ ## Overview
10
+
11
+ A table is a native `<table>` with styled parts. Use it for tabular data only, never for layout.
12
+
13
+ ## Examples
14
+
15
+ ### Basic
16
+
17
+ ```tsx
18
+ import { style, Surface, Table } from '@tempoxyz/ds'
19
+
20
+ export const fill = true
21
+ function Transactions(props: Table.Root.Props) {
22
+ return (
23
+ <Surface>
24
+ <Table.Root {...props}>
25
+ <Table.Head>
26
+ <Table.Row>
27
+ <Table.HeaderCell>ID</Table.HeaderCell>
28
+ <Table.HeaderCell>Amount</Table.HeaderCell>
29
+ <Table.HeaderCell>Status</Table.HeaderCell>
30
+ </Table.Row>
31
+ </Table.Head>
32
+ <Table.Body>
33
+ {(
34
+ [
35
+ ['0x4f2a…91c3', '$1,200.00', 'Confirmed', 'positive'],
36
+ ['0x91bd…07e1', '$48.50', 'Confirmed', 'positive'],
37
+ ['0xc3e0…5a22', '$310.00', 'Pending', 'warning'],
38
+ ['0x77a1…c0d9', '$9.99', 'Failed', 'negative'],
39
+ ] as const
40
+ ).map(([id, amount, status, tone]) => (
41
+ <Table.Row key={id}>
42
+ <Table.Cell mono>{id}</Table.Cell>
43
+ <Table.Cell numeric>{amount}</Table.Cell>
44
+ <Table.Cell status={tone}>{status}</Table.Cell>
45
+ </Table.Row>
46
+ ))}
47
+ </Table.Body>
48
+ </Table.Root>
49
+ </Surface>
50
+ )
51
+ }
52
+
53
+ export function Demo() {
54
+ return (
55
+ <div {...styles.grid()}>
56
+ <Transactions />
57
+ </div>
58
+ )
59
+ }
60
+
61
+ namespace styles {
62
+ export const grid = style({
63
+ backgroundColor: 'background.primary',
64
+ display: 'grid',
65
+ gap: '24',
66
+ gridTemplateColumns: 'repeat(auto-fill, minmax(24rem, 1fr)) !custom',
67
+ padding: '16',
68
+ })
69
+ }
70
+ ```
71
+
72
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/Basic.tsx)
73
+
74
+ ### Two Lines
75
+
76
+ Set a second line in `Table.Subtitle`, lead the first cell with a `Table.LeadTile` or a [TokenLogo](token-logo.md), and set `muted` on a value that matters less, such as a zero amount.
77
+
78
+ ```tsx
79
+ import { Inline, StatusChip, style, Surface, Table } from '@tempoxyz/ds'
80
+ import { ArrowDownLeft, ArrowUpRight, CreditCard } from '@tempoxyz/ds/icons'
81
+
82
+ export const fill = true
83
+ const activity = [
84
+ {
85
+ amount: '+$48.50',
86
+ detail: '48.50 USDC',
87
+ icon: <ArrowDownLeft />,
88
+ status: ['Settled', 'positive'],
89
+ time: 'Today, 09:41',
90
+ title: 'Received from Bob',
91
+ },
92
+ {
93
+ amount: '$120.00',
94
+ detail: '120.00 USDC',
95
+ icon: <ArrowUpRight />,
96
+ status: ['Pending', 'warning'],
97
+ time: 'Today, 08:12',
98
+ title: 'Sent to Ada',
99
+ },
100
+ {
101
+ amount: '$0.00',
102
+ detail: 'Declined',
103
+ icon: <CreditCard />,
104
+ status: ['Failed', 'negative'],
105
+ time: 'Yesterday, 18:30',
106
+ title: 'Card payment',
107
+ },
108
+ ] as const
109
+
110
+ export function Demo() {
111
+ return (
112
+ <div {...styles.grid()}>
113
+ <Surface>
114
+ <Table.Root size="large">
115
+ <Table.Body>
116
+ {activity.map((entry) => (
117
+ <Table.Row key={entry.title}>
118
+ <Table.Cell>
119
+ <Inline gap="12">
120
+ <Table.LeadTile>{entry.icon}</Table.LeadTile>
121
+ <div>
122
+ {entry.title}
123
+ <Table.Subtitle>{entry.time}</Table.Subtitle>
124
+ </div>
125
+ </Inline>
126
+ </Table.Cell>
127
+ <Table.Cell>
128
+ <StatusChip intent={entry.status[1]} size="small">
129
+ {entry.status[0]}
130
+ </StatusChip>
131
+ </Table.Cell>
132
+ <Table.Cell
133
+ muted={entry.amount === '$0.00'}
134
+ status={entry.amount.startsWith('+') ? 'positive' : undefined}
135
+ >
136
+ {entry.amount}
137
+ <Table.Subtitle>{entry.detail}</Table.Subtitle>
138
+ </Table.Cell>
139
+ </Table.Row>
140
+ ))}
141
+ </Table.Body>
142
+ </Table.Root>
143
+ </Surface>
144
+ </div>
145
+ )
146
+ }
147
+
148
+ namespace styles {
149
+ export const grid = style({
150
+ backgroundColor: 'background.primary',
151
+ display: 'grid',
152
+ gap: '24',
153
+ gridTemplateColumns: 'repeat(auto-fill, minmax(24rem, 1fr)) !custom',
154
+ padding: '16',
155
+ })
156
+ }
157
+ ```
158
+
159
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/TwoLines.tsx)
160
+
161
+ ### Sortable Columns
162
+
163
+ Put a `Table.SortButton` in a `Table.HeaderCell` and set `aria-sort` on the header cell. The chevron shows on hover and once the column is sorted.
164
+
165
+ ```tsx
166
+ import { style, Surface, Table } from '@tempoxyz/ds'
167
+ import { useState } from 'react'
168
+
169
+ export const fill = true
170
+ const assets = [
171
+ ['USDC', 3420.18],
172
+ ['USDT0', 3150],
173
+ ['pathUSD', 2424.24],
174
+ ] as const
175
+
176
+ const format = new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' })
177
+
178
+ type Sort = { column: 'asset' | 'balance'; order: 'ascending' | 'descending' }
179
+
180
+ export function Demo() {
181
+ const [sort, setSort] = useState<Sort>({ column: 'balance', order: 'descending' })
182
+
183
+ const rows = [...assets].sort((a, b) => {
184
+ const order = sort.order === 'ascending' ? 1 : -1
185
+ if (sort.column === 'asset') return a[0].localeCompare(b[0]) * order
186
+ return (a[1] - b[1]) * order
187
+ })
188
+ const toggle = (column: Sort['column']) =>
189
+ setSort({
190
+ column,
191
+ order: sort.column === column && sort.order === 'descending' ? 'ascending' : 'descending',
192
+ })
193
+
194
+ return (
195
+ <div {...styles.grid()}>
196
+ <Surface>
197
+ <Table.Root interactive>
198
+ <Table.Head>
199
+ <Table.Row>
200
+ <Table.HeaderCell aria-sort={sort.column === 'asset' ? sort.order : 'none'}>
201
+ <Table.SortButton onClick={() => toggle('asset')}>Asset</Table.SortButton>
202
+ </Table.HeaderCell>
203
+ <Table.HeaderCell aria-sort={sort.column === 'balance' ? sort.order : 'none'}>
204
+ <Table.SortButton onClick={() => toggle('balance')}>Balance</Table.SortButton>
205
+ </Table.HeaderCell>
206
+ </Table.Row>
207
+ </Table.Head>
208
+ <Table.Body>
209
+ {rows.map(([symbol, balance]) => (
210
+ <Table.Row key={symbol}>
211
+ <Table.Cell>{symbol}</Table.Cell>
212
+ <Table.Cell>{format.format(balance)}</Table.Cell>
213
+ </Table.Row>
214
+ ))}
215
+ </Table.Body>
216
+ </Table.Root>
217
+ </Surface>
218
+ </div>
219
+ )
220
+ }
221
+
222
+ namespace styles {
223
+ export const grid = style({
224
+ backgroundColor: 'background.primary',
225
+ display: 'grid',
226
+ gap: '24',
227
+ gridTemplateColumns: 'repeat(auto-fill, minmax(24rem, 1fr)) !custom',
228
+ padding: '16',
229
+ })
230
+ }
231
+ ```
232
+
233
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/Sortable.tsx)
234
+
235
+ ### Interactive
236
+
237
+ Set `interactive` to fill a hovered row and outline a focus-visible one.
238
+
239
+ ```tsx
240
+ import { style, Surface, Table } from '@tempoxyz/ds'
241
+
242
+ export const fill = true
243
+ function Transactions(props: Table.Root.Props) {
244
+ return (
245
+ <Surface>
246
+ <Table.Root {...props}>
247
+ <Table.Head>
248
+ <Table.Row>
249
+ <Table.HeaderCell>ID</Table.HeaderCell>
250
+ <Table.HeaderCell>Amount</Table.HeaderCell>
251
+ <Table.HeaderCell>Status</Table.HeaderCell>
252
+ </Table.Row>
253
+ </Table.Head>
254
+ <Table.Body>
255
+ {(
256
+ [
257
+ ['0x4f2a…91c3', '$1,200.00', 'Confirmed', 'positive'],
258
+ ['0x91bd…07e1', '$48.50', 'Confirmed', 'positive'],
259
+ ['0xc3e0…5a22', '$310.00', 'Pending', 'warning'],
260
+ ['0x77a1…c0d9', '$9.99', 'Failed', 'negative'],
261
+ ] as const
262
+ ).map(([id, amount, status, tone]) => (
263
+ <Table.Row key={id} tabIndex={0}>
264
+ <Table.Cell mono>{id}</Table.Cell>
265
+ <Table.Cell numeric>{amount}</Table.Cell>
266
+ <Table.Cell status={tone}>{status}</Table.Cell>
267
+ </Table.Row>
268
+ ))}
269
+ </Table.Body>
270
+ </Table.Root>
271
+ </Surface>
272
+ )
273
+ }
274
+
275
+ export function Demo() {
276
+ return (
277
+ <div {...styles.grid()}>
278
+ <Transactions interactive />
279
+ </div>
280
+ )
281
+ }
282
+
283
+ namespace styles {
284
+ export const grid = style({
285
+ backgroundColor: 'background.primary',
286
+ display: 'grid',
287
+ gap: '24',
288
+ gridTemplateColumns: 'repeat(auto-fill, minmax(24rem, 1fr)) !custom',
289
+ padding: '16',
290
+ })
291
+ }
292
+ ```
293
+
294
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/Interactive.tsx)
295
+
296
+ ### Dividers
297
+
298
+ Rows draw a divider inset by their padding, except the last row. Set `dividers={false}` to remove them.
299
+
300
+ ```tsx
301
+ import { style, Surface, Table } from '@tempoxyz/ds'
302
+
303
+ export const fill = true
304
+ function Transactions(props: Table.Root.Props) {
305
+ return (
306
+ <Surface>
307
+ <Table.Root {...props}>
308
+ <Table.Head>
309
+ <Table.Row>
310
+ <Table.HeaderCell>ID</Table.HeaderCell>
311
+ <Table.HeaderCell>Amount</Table.HeaderCell>
312
+ <Table.HeaderCell>Status</Table.HeaderCell>
313
+ </Table.Row>
314
+ </Table.Head>
315
+ <Table.Body>
316
+ {(
317
+ [
318
+ ['0x4f2a…91c3', '$1,200.00', 'Confirmed', 'positive'],
319
+ ['0x91bd…07e1', '$48.50', 'Confirmed', 'positive'],
320
+ ['0xc3e0…5a22', '$310.00', 'Pending', 'warning'],
321
+ ['0x77a1…c0d9', '$9.99', 'Failed', 'negative'],
322
+ ] as const
323
+ ).map(([id, amount, status, tone]) => (
324
+ <Table.Row key={id}>
325
+ <Table.Cell mono>{id}</Table.Cell>
326
+ <Table.Cell numeric>{amount}</Table.Cell>
327
+ <Table.Cell status={tone}>{status}</Table.Cell>
328
+ </Table.Row>
329
+ ))}
330
+ </Table.Body>
331
+ </Table.Root>
332
+ </Surface>
333
+ )
334
+ }
335
+
336
+ export function Demo() {
337
+ return (
338
+ <div {...styles.grid()}>
339
+ <Transactions dividers={false} />
340
+ </div>
341
+ )
342
+ }
343
+
344
+ namespace styles {
345
+ export const grid = style({
346
+ backgroundColor: 'background.primary',
347
+ display: 'grid',
348
+ gap: '24',
349
+ gridTemplateColumns: 'repeat(auto-fill, minmax(24rem, 1fr)) !custom',
350
+ padding: '16',
351
+ })
352
+ }
353
+ ```
354
+
355
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/Dividers.tsx)
356
+
357
+ ### Size
358
+
359
+ Set `size` to `small`, `medium` (default), or `large`.
360
+
361
+ ```tsx
362
+ import { style, Surface, Table } from '@tempoxyz/ds'
363
+
364
+ export const fill = true
365
+ function Transactions(props: Table.Root.Props) {
366
+ return (
367
+ <Surface>
368
+ <Table.Root {...props}>
369
+ <Table.Head>
370
+ <Table.Row>
371
+ <Table.HeaderCell>ID</Table.HeaderCell>
372
+ <Table.HeaderCell>Amount</Table.HeaderCell>
373
+ <Table.HeaderCell>Status</Table.HeaderCell>
374
+ </Table.Row>
375
+ </Table.Head>
376
+ <Table.Body>
377
+ {(
378
+ [
379
+ ['0x4f2a…91c3', '$1,200.00', 'Confirmed', 'positive'],
380
+ ['0x91bd…07e1', '$48.50', 'Confirmed', 'positive'],
381
+ ['0xc3e0…5a22', '$310.00', 'Pending', 'warning'],
382
+ ['0x77a1…c0d9', '$9.99', 'Failed', 'negative'],
383
+ ] as const
384
+ ).map(([id, amount, status, tone]) => (
385
+ <Table.Row key={id}>
386
+ <Table.Cell mono>{id}</Table.Cell>
387
+ <Table.Cell numeric>{amount}</Table.Cell>
388
+ <Table.Cell status={tone}>{status}</Table.Cell>
389
+ </Table.Row>
390
+ ))}
391
+ </Table.Body>
392
+ </Table.Root>
393
+ </Surface>
394
+ )
395
+ }
396
+
397
+ export function Demo() {
398
+ return (
399
+ <div {...styles.grid()}>
400
+ <Transactions interactive size="small" />
401
+ <Transactions size="large" />
402
+ </div>
403
+ )
404
+ }
405
+
406
+ namespace styles {
407
+ export const grid = style({
408
+ backgroundColor: 'background.primary',
409
+ display: 'grid',
410
+ gap: '24',
411
+ gridTemplateColumns: 'repeat(auto-fill, minmax(24rem, 1fr)) !custom',
412
+ padding: '16',
413
+ })
414
+ }
415
+ ```
416
+
417
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/Size.tsx)
418
+
419
+ ### Dense
420
+
421
+ Set `variant="dense"` for a row of labels over rows of details, such as a comparison of plans. Columns share the width equally.
422
+
423
+ ```tsx
424
+ import { Table } from '@tempoxyz/ds'
425
+
426
+ export const fill = true
427
+ const plans = [
428
+ ['Starter', '0.10%', 'T+1', 'Email', 'USDC'],
429
+ ['Growth', '0.08%', 'Same day', 'Chat', 'USDC, USDT0'],
430
+ ['Scale', 'Custom', 'Instant', 'Dedicated', 'Any stablecoin'],
431
+ ] as const
432
+
433
+ export function Demo() {
434
+ return (
435
+ <Table variant="dense">
436
+ <Table.Head>
437
+ <Table.Row>
438
+ <Table.HeaderCell>Plan</Table.HeaderCell>
439
+ <Table.HeaderCell>Fee</Table.HeaderCell>
440
+ <Table.HeaderCell>Settlement</Table.HeaderCell>
441
+ <Table.HeaderCell>Support</Table.HeaderCell>
442
+ <Table.HeaderCell>Assets</Table.HeaderCell>
443
+ </Table.Row>
444
+ </Table.Head>
445
+ <Table.Body>
446
+ {plans.map((plan) => (
447
+ <Table.Row key={plan[0]}>
448
+ {plan.map((value) => (
449
+ <Table.Cell key={value}>{value}</Table.Cell>
450
+ ))}
451
+ </Table.Row>
452
+ ))}
453
+ </Table.Body>
454
+ </Table>
455
+ )
456
+ }
457
+ ```
458
+
459
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/Dense.tsx)
460
+
461
+ ### Simple
462
+
463
+ Set `variant="simple"` for rows that each lead with a row header beside a paragraph. Set `scope="row"` on each `Table.HeaderCell`.
464
+
465
+ ```tsx
466
+ import { Table } from '@tempoxyz/ds'
467
+
468
+ export const fill = true
469
+ const uses = [
470
+ [
471
+ 'Global payouts and payroll',
472
+ 'Platforms can pay merchants, contractors, or employees in stablecoins or local currency.',
473
+ ],
474
+ [
475
+ 'Treasury management',
476
+ 'Companies can hold balances in stablecoins and move them between entities in seconds.',
477
+ ],
478
+ [
479
+ 'Cross-border settlement',
480
+ 'Banks can settle with partners abroad without waiting on correspondent accounts.',
481
+ ],
482
+ ] as const
483
+
484
+ export function Demo() {
485
+ return (
486
+ <Table variant="simple">
487
+ <Table.Caption>Use cases</Table.Caption>
488
+ <Table.Body>
489
+ {uses.map(([title, description]) => (
490
+ <Table.Row key={title}>
491
+ <Table.HeaderCell scope="row">{title}</Table.HeaderCell>
492
+ <Table.Cell>{description}</Table.Cell>
493
+ </Table.Row>
494
+ ))}
495
+ </Table.Body>
496
+ </Table>
497
+ )
498
+ }
499
+ ```
500
+
501
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Table/Simple.tsx)
502
+
503
+ ## Anatomy
504
+
505
+ Import the component and assemble its parts.
506
+
507
+ ```tsx
508
+ import { Table } from '@tempoxyz/ds'
509
+
510
+ <Table>
511
+ <Table.Caption />
512
+ <Table.Head>
513
+ <Table.Row>
514
+ <Table.HeaderCell>
515
+ <Table.SortButton />
516
+ </Table.HeaderCell>
517
+ </Table.Row>
518
+ </Table.Head>
519
+ <Table.Body>
520
+ <Table.Row>
521
+ <Table.Cell>
522
+ <Table.LeadTile />
523
+ <Table.Subtitle />
524
+ </Table.Cell>
525
+ </Table.Row>
526
+ </Table.Body>
527
+ </Table>
528
+ <Table.Root />
529
+ ```
530
+
531
+ ## Guidelines
532
+
533
+ ### When to Use
534
+
535
+ * Compare many records with the same fields, such as wallet activity (date, recipient, amount, status) or benchmark runs (commit, TPS, latency).
536
+ * Let people scan a column or find one row among many.
537
+ * List API keys, webhooks, or team members.
538
+
539
+ ### When Not to Use
540
+
541
+ * Use [ListGroup](list-group.md) with `ListGroup.Label` for the fields of one record, such as a transaction's network, fee, and status.
542
+ * Use [ListGroup](list-group.md) with `action="navigation"` for a short list of pages or settings to open.
543
+ * Use [Grid](grid.md) or [Stack](stack.md) to arrange content.
544
+ * Use a [Surface](surface.md) with a large [Amount](amount.md) for one headline number, such as a balance or the current TPS.
545
+
546
+ ### Align Numbers to the End
547
+
548
+ * **Figures:** Set `numeric` on number cells for tabular figures.
549
+ * **Alignment:** Set `justify="end"` on those cells and on their `Table.HeaderCell`. The last column already aligns to the end, unless its cells set `justify="start"`.
550
+ * **Decimals:** Keep one decimal count per column, such as `41.2` and `102.5`.
551
+
552
+ **Do:** Set `numeric` and `justify="end"` on number columns and their headers.
553
+
554
+ ```tsx
555
+ /**
556
+ * Do example for "Align Numbers to the End" on the Table page.
557
+ * Set `numeric` and `justify="end"` on number columns and their headers.
558
+ * @module
559
+ */
560
+ import { Table } from '@tempoxyz/ds'
561
+
562
+ const runs = [
563
+ ['4f2a91c', '12,480', '41.2'],
564
+ ['91bd07e', '9,315', '38.7'],
565
+ ['c3e05a2', '10,072', '102.5'],
566
+ ] as const
567
+
568
+ /** Renders the static preview, which the guideline card shows inert. */
569
+ export function Demo() {
570
+ return (
571
+ <Table size="small">
572
+ <Table.Head>
573
+ <Table.Row>
574
+ <Table.HeaderCell>Commit</Table.HeaderCell>
575
+ <Table.HeaderCell justify="end">TPS</Table.HeaderCell>
576
+ <Table.HeaderCell>p99 (ms)</Table.HeaderCell>
577
+ </Table.Row>
578
+ </Table.Head>
579
+ <Table.Body>
580
+ {runs.map(([commit, tps, latency]) => (
581
+ <Table.Row key={commit}>
582
+ <Table.Cell mono>{commit}</Table.Cell>
583
+ <Table.Cell justify="end" numeric>
584
+ {tps}
585
+ </Table.Cell>
586
+ <Table.Cell numeric>{latency}</Table.Cell>
587
+ </Table.Row>
588
+ ))}
589
+ </Table.Body>
590
+ </Table>
591
+ )
592
+ }
593
+ ```
594
+
595
+ **Don't:** Start-align numbers or leave them in proportional figures.
596
+
597
+ ```tsx
598
+ /**
599
+ * Don't example for "Align Numbers to the End" on the Table page.
600
+ * Don't start-align numbers or leave them in proportional figures.
601
+ * @module
602
+ */
603
+ import { Table } from '@tempoxyz/ds'
604
+
605
+ const runs = [
606
+ ['4f2a91c', '12,480', '41.2'],
607
+ ['91bd07e', '9,315', '38.7'],
608
+ ['c3e05a2', '10,072', '102.5'],
609
+ ] as const
610
+
611
+ /** Renders the static preview, which the guideline card shows inert. */
612
+ export function Demo() {
613
+ return (
614
+ <Table size="small">
615
+ <Table.Head>
616
+ <Table.Row>
617
+ <Table.HeaderCell>Commit</Table.HeaderCell>
618
+ <Table.HeaderCell>TPS</Table.HeaderCell>
619
+ <Table.HeaderCell>p99 (ms)</Table.HeaderCell>
620
+ </Table.Row>
621
+ </Table.Head>
622
+ <Table.Body>
623
+ {runs.map(([commit, tps, latency]) => (
624
+ <Table.Row key={commit}>
625
+ <Table.Cell>{commit}</Table.Cell>
626
+ <Table.Cell>{tps}</Table.Cell>
627
+ <Table.Cell>{latency}</Table.Cell>
628
+ </Table.Row>
629
+ ))}
630
+ </Table.Body>
631
+ </Table>
632
+ )
633
+ }
634
+ ```
635
+
636
+ An [Amount](amount.md) in a cell renders as a block and sets its own alignment. Give it `align="right"` in an end-aligned column.
637
+
638
+ ### Truncate Hashes in the Middle
639
+
640
+ Set `mono` on cells with hashes, addresses, and commit IDs, and shorten them with `formatHex`. Link the row to a details view that shows the full value in a [Hex](hex.md) with `copy`.
641
+
642
+ **Do:** Keep the `0x` prefix and four characters on each side, such as `0x4f2a…91c3`.
643
+
644
+ ```tsx
645
+ /**
646
+ * Do example for "Truncate Hashes in the Middle" on the Table page.
647
+ * Keep the `0x` prefix and four characters on each side, such as `0x4f2a…91c3`.
648
+ * @module
649
+ */
650
+ import { formatHex, Table } from '@tempoxyz/ds'
651
+
652
+ const transfers = [
653
+ ['0x4f2a8c1e9b7d3a6f5e2c0b8d7a1f4e6c9b3d2a5f8e7c6b1d0a9f3e2c4b5091c3', '25.00'],
654
+ ['0x91bd07e3a5c8f2d6b4e1a9c7f3d5b2e8a6c4f1d9b7e3a5c2f8d6b4e1a9c70e17', '120.00'],
655
+ ] as const
656
+
657
+ /** Renders the static preview, which the guideline card shows inert. */
658
+ export function Demo() {
659
+ return (
660
+ <Table size="small">
661
+ <Table.Head>
662
+ <Table.Row>
663
+ <Table.HeaderCell>Transaction</Table.HeaderCell>
664
+ <Table.HeaderCell>Amount</Table.HeaderCell>
665
+ </Table.Row>
666
+ </Table.Head>
667
+ <Table.Body>
668
+ {transfers.map(([hash, amount]) => (
669
+ <Table.Row key={hash}>
670
+ <Table.Cell mono>{formatHex(hash)}</Table.Cell>
671
+ <Table.Cell numeric>{amount}</Table.Cell>
672
+ </Table.Row>
673
+ ))}
674
+ </Table.Body>
675
+ </Table>
676
+ )
677
+ }
678
+ ```
679
+
680
+ **Don't:** Show full hashes, which crowd out the other columns.
681
+
682
+ ```tsx
683
+ /**
684
+ * Don't example for "Truncate Hashes in the Middle" on the Table page.
685
+ * Don't show full hashes, which crowd out the other columns.
686
+ * @module
687
+ */
688
+ import { Table } from '@tempoxyz/ds'
689
+
690
+ const transfers = [
691
+ ['0x4f2a8c1e9b7d3a6f5e2c0b8d7a1f4e6c9b3d2a5f8e7c6b1d0a9f3e2c4b5091c3', '25.00'],
692
+ ['0x91bd07e3a5c8f2d6b4e1a9c7f3d5b2e8a6c4f1d9b7e3a5c2f8d6b4e1a9c70e17', '120.00'],
693
+ ] as const
694
+
695
+ /** Renders the static preview, which the guideline card shows inert. */
696
+ export function Demo() {
697
+ return (
698
+ <Table size="small">
699
+ <Table.Head>
700
+ <Table.Row>
701
+ <Table.HeaderCell>Transaction</Table.HeaderCell>
702
+ <Table.HeaderCell>Amount</Table.HeaderCell>
703
+ </Table.Row>
704
+ </Table.Head>
705
+ <Table.Body>
706
+ {transfers.map(([hash, amount]) => (
707
+ <Table.Row key={hash}>
708
+ <Table.Cell mono>{hash}</Table.Cell>
709
+ <Table.Cell numeric>{amount}</Table.Cell>
710
+ </Table.Row>
711
+ ))}
712
+ </Table.Body>
713
+ </Table>
714
+ )
715
+ }
716
+ ```
717
+
718
+ ### Pair Status Color With Words
719
+
720
+ `status` colors a cell's text `positive`, `negative`, or `warning`. If the status needs more weight, use a [Badge](badge.md) in the cell.
721
+
722
+ **Do:** Keep a word with each color, such as Failed.
723
+
724
+ ```tsx
725
+ /**
726
+ * Do example for "Pair Status Color With Words" on the Table page.
727
+ * Keep a word with each color, such as Failed.
728
+ * @module
729
+ */
730
+ import { Table } from '@tempoxyz/ds'
731
+
732
+ /** Renders the static preview, which the guideline card shows inert. */
733
+ export function Demo() {
734
+ return (
735
+ <Table size="small">
736
+ <Table.Head>
737
+ <Table.Row>
738
+ <Table.HeaderCell>Recipient</Table.HeaderCell>
739
+ <Table.HeaderCell>Status</Table.HeaderCell>
740
+ </Table.Row>
741
+ </Table.Head>
742
+ <Table.Body>
743
+ <Table.Row>
744
+ <Table.Cell>Alice</Table.Cell>
745
+ <Table.Cell status="positive">Confirmed</Table.Cell>
746
+ </Table.Row>
747
+ <Table.Row>
748
+ <Table.Cell>Bob</Table.Cell>
749
+ <Table.Cell status="warning">Pending</Table.Cell>
750
+ </Table.Row>
751
+ <Table.Row>
752
+ <Table.Cell>Carol</Table.Cell>
753
+ <Table.Cell status="negative">Failed</Table.Cell>
754
+ </Table.Row>
755
+ </Table.Body>
756
+ </Table>
757
+ )
758
+ }
759
+ ```
760
+
761
+ **Don't:** Show status by color alone.
762
+
763
+ ```tsx
764
+ /** Don't example for "Pair Status Color With Words" on the Table page. Don't show status by color alone. @module */
765
+ import { Table } from '@tempoxyz/ds'
766
+
767
+ /** Renders the static preview, which the guideline card shows inert. */
768
+ export function Demo() {
769
+ return (
770
+ <Table size="small">
771
+ <Table.Head>
772
+ <Table.Row>
773
+ <Table.HeaderCell>Recipient</Table.HeaderCell>
774
+ <Table.HeaderCell>Status</Table.HeaderCell>
775
+ </Table.Row>
776
+ </Table.Head>
777
+ <Table.Body>
778
+ <Table.Row>
779
+ <Table.Cell>Alice</Table.Cell>
780
+ <Table.Cell status="positive">●</Table.Cell>
781
+ </Table.Row>
782
+ <Table.Row>
783
+ <Table.Cell>Bob</Table.Cell>
784
+ <Table.Cell status="warning">●</Table.Cell>
785
+ </Table.Row>
786
+ <Table.Row>
787
+ <Table.Cell>Carol</Table.Cell>
788
+ <Table.Cell status="negative">●</Table.Cell>
789
+ </Table.Row>
790
+ </Table.Body>
791
+ </Table>
792
+ )
793
+ }
794
+ ```
795
+
796
+ ### Make Clickable Rows Reachable by Keyboard
797
+
798
+ `interactive` adds styles only, with no click or key handling. If the whole row also responds to a click, keep other buttons out of it.
799
+
800
+ **Do:** Put a link to the record in the first cell.
801
+
802
+ ```tsx
803
+ /**
804
+ * Do example for "Make Clickable Rows Reachable by Keyboard" on the Table page.
805
+ * Put a link to the record in the first cell.
806
+ * @module
807
+ */
808
+ import { style, Table } from '@tempoxyz/ds'
809
+
810
+ const transfers = [
811
+ ['0x4f2a…91c3', '25.00'],
812
+ ['0x91bd…07e1', '120.00'],
813
+ ['0xc3e0…5a22', '8.50'],
814
+ ] as const
815
+
816
+ /** Renders the static preview, which the guideline card shows inert. */
817
+ export function Demo() {
818
+ return (
819
+ <Table interactive size="small">
820
+ <Table.Head>
821
+ <Table.Row>
822
+ <Table.HeaderCell>Transaction</Table.HeaderCell>
823
+ <Table.HeaderCell>Amount</Table.HeaderCell>
824
+ </Table.Row>
825
+ </Table.Head>
826
+ <Table.Body>
827
+ {transfers.map(([hash, amount]) => (
828
+ <Table.Row key={hash}>
829
+ <Table.Cell mono>
830
+ <a href="#" {...styles.link()}>
831
+ {hash}
832
+ </a>
833
+ </Table.Cell>
834
+ <Table.Cell numeric>{amount}</Table.Cell>
835
+ </Table.Row>
836
+ ))}
837
+ </Table.Body>
838
+ </Table>
839
+ )
840
+ }
841
+
842
+ namespace styles {
843
+ export const link = style({
844
+ color: 'content.primary',
845
+ textDecorationLine: 'underline !custom',
846
+ textUnderlineOffset: '4px !custom',
847
+ })
848
+ }
849
+ ```
850
+
851
+ **Don't:** Rely on a row click handler that the keyboard can't reach.
852
+
853
+ ```tsx
854
+ /**
855
+ * Don't example for "Make Clickable Rows Reachable by Keyboard" on the Table page.
856
+ * Don't rely on a row click handler that the keyboard can't reach.
857
+ * @module
858
+ */
859
+ import { Table } from '@tempoxyz/ds'
860
+
861
+ const transfers = [
862
+ ['0x4f2a…91c3', '25.00'],
863
+ ['0x91bd…07e1', '120.00'],
864
+ ['0xc3e0…5a22', '8.50'],
865
+ ] as const
866
+
867
+ /** Renders the static preview, which the guideline card shows inert. */
868
+ export function Demo() {
869
+ return (
870
+ <Table interactive size="small">
871
+ <Table.Head>
872
+ <Table.Row>
873
+ <Table.HeaderCell>Transaction</Table.HeaderCell>
874
+ <Table.HeaderCell>Amount</Table.HeaderCell>
875
+ </Table.Row>
876
+ </Table.Head>
877
+ <Table.Body>
878
+ {transfers.map(([hash, amount]) => (
879
+ <Table.Row key={hash} onClick={() => {}}>
880
+ <Table.Cell mono>{hash}</Table.Cell>
881
+ <Table.Cell numeric>{amount}</Table.Cell>
882
+ </Table.Row>
883
+ ))}
884
+ </Table.Body>
885
+ </Table>
886
+ )
887
+ }
888
+ ```
889
+
890
+ ### Keep Long Tables Readable
891
+
892
+ * **Sticky headers:** Set `sticky` on `Table.Head`, and place the table in a scrolling container with a fixed height.
893
+ * **Long histories:** Split them with [Pagination](pagination.md).
894
+
895
+ ### Match Density to the Data
896
+
897
+ * **Small (32px):** Dense dashboards, such as benchmark results.
898
+ * **Medium (40px):** The default.
899
+ * **Large (56px):** Avatars or token icons beside two lines of text.
900
+
901
+ ## Content
902
+
903
+ * Write column headers as short nouns, such as Recipient, Amount, and Block time.
904
+ * Put the unit of a metric in the header, such as `p99 (ms)`. Keep the currency or token on each amount, since one table can mix assets.
905
+ * Use one date format per column. Use relative times, such as 5 min ago, only for recent activity, and show the full date in the details view.
906
+ * Fill empty cells with one value, such as None or 0, so a blank cell doesn't read as a loading error.
907
+
908
+ ## Accessibility
909
+
910
+ A table renders native table elements, so screen readers announce rows, columns, and headers. It follows the [WAI-ARIA table pattern](https://www.w3.org/WAI/ARIA/apg/patterns/table/).
911
+
912
+ ### Semantics
913
+
914
+ * **Table.Root (table):** Uses native table elements for every part, from the root to each cell.
915
+ * **Table.HeaderCell (th):** Column headers. If a body row's first cell names the record, such as a commit ID, make it a row header.
916
+ * **Appearance Props (numeric, mono, muted, status):** These and `interactive` change styles only. Status colors aren't announced.
917
+ * **Table.SortButton (button):** A native button in the header cell. The header cell announces its `aria-sort` order.
918
+
919
+ ### Keyboard
920
+
921
+ A table has no keys of its own. Screen reader users move through cells with table commands.
922
+
923
+ For a sort button, and for a row you make focusable with `tabIndex={0}` in an `interactive` table:
924
+
925
+ * **Sort the Column (Enter or Space):** Runs the `onClick` of the focused sort button.
926
+ * **Move Focus to the Row (Tab):** Moves focus to the row.
927
+
928
+ ### Focus
929
+
930
+ With `interactive`, a focused body row shows an outline inside its edges. A focused sort button shows an outline around the label of its header cell. The table adds no other tab stops.
931
+
932
+ ### Your Responsibilities
933
+
934
+ * **Name Every Table (Table.Caption):** Caption it, or use `aria-labelledby` for a visible heading. The caption renders below the rows.
935
+ * **Update the Sort Order (aria-sort):** Sort the rows in `onClick`, and set `aria-sort` on the sorted header cell and `none` on the rest.
936
+ * **Mark Headers (scope):** Mark every column header, and the cell that identifies each row.
937
+ * **Reflow Wide Tables (SC 1.4.10):** Put a wide table in a focusable [scrolling container](https://www.w3.org/WAI/WCAG22/Understanding/reflow.html) with an `aria-label`.
938
+
939
+ ## Related
940
+
941
+ [ListGroup](list-group.md)
942
+
943
+ Use for the details of one record.
944
+
945
+ [Amount](amount.md)
946
+
947
+ Use for money and token amounts in cells.
948
+
949
+ [Hex](hex.md)
950
+
951
+ Use for addresses and hashes.
952
+
953
+ [Badge](badge.md)
954
+
955
+ Use for statuses.
956
+
957
+ [Pagination](pagination.md)
958
+
959
+ Use for long histories.
960
+
961
+ [Tables and Lists](../patterns/tables-and-lists.md)
962
+
963
+ Covers choosing a layout.
964
+
965
+ [Dashboards](../patterns/dashboards.md)
966
+
967
+ Covers benchmark and metric views.