@tempoxyz/ds 0.3.10 → 0.3.11

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 (120) hide show
  1. package/dist/docs/AGENTS.md +9 -0
  2. package/dist/docs/components/accordion.md +489 -0
  3. package/dist/docs/components/address-card.md +147 -0
  4. package/dist/docs/components/agent-logo.md +194 -0
  5. package/dist/docs/components/alert-dialog.md +631 -0
  6. package/dist/docs/components/alert.md +448 -0
  7. package/dist/docs/components/amount-input.md +305 -0
  8. package/dist/docs/components/amount.md +794 -0
  9. package/dist/docs/components/app-shell.md +559 -0
  10. package/dist/docs/components/asset-chip.md +294 -0
  11. package/dist/docs/components/autocomplete.md +619 -0
  12. package/dist/docs/components/avatar.md +321 -0
  13. package/dist/docs/components/badge.md +339 -0
  14. package/dist/docs/components/badged-logo.md +116 -0
  15. package/dist/docs/components/banner.md +379 -0
  16. package/dist/docs/components/bar-chart.md +208 -0
  17. package/dist/docs/components/breadcrumbs.md +380 -0
  18. package/dist/docs/components/button-group.md +286 -0
  19. package/dist/docs/components/button.md +650 -0
  20. package/dist/docs/components/calendar.md +375 -0
  21. package/dist/docs/components/callout.md +96 -0
  22. package/dist/docs/components/checkbox-group.md +521 -0
  23. package/dist/docs/components/checkbox.md +467 -0
  24. package/dist/docs/components/choice-group.md +516 -0
  25. package/dist/docs/components/choicebox.md +641 -0
  26. package/dist/docs/components/code.md +100 -0
  27. package/dist/docs/components/combobox.md +939 -0
  28. package/dist/docs/components/container.md +257 -0
  29. package/dist/docs/components/copy-button.md +642 -0
  30. package/dist/docs/components/date-picker.md +514 -0
  31. package/dist/docs/components/dialog.md +1332 -0
  32. package/dist/docs/components/drawer.md +966 -0
  33. package/dist/docs/components/empty.md +340 -0
  34. package/dist/docs/components/field.md +570 -0
  35. package/dist/docs/components/fieldset.md +398 -0
  36. package/dist/docs/components/frame.md +434 -0
  37. package/dist/docs/components/gauge.md +299 -0
  38. package/dist/docs/components/get-started-visual.md +117 -0
  39. package/dist/docs/components/grid.md +288 -0
  40. package/dist/docs/components/hero-visual.md +155 -0
  41. package/dist/docs/components/hex.md +487 -0
  42. package/dist/docs/components/icon-tile.md +292 -0
  43. package/dist/docs/components/identicon.md +371 -0
  44. package/dist/docs/components/inline.md +233 -0
  45. package/dist/docs/components/inset.md +225 -0
  46. package/dist/docs/components/kbd.md +423 -0
  47. package/dist/docs/components/list-group.md +981 -0
  48. package/dist/docs/components/loading-dots.md +237 -0
  49. package/dist/docs/components/menu.md +934 -0
  50. package/dist/docs/components/native-select.md +204 -0
  51. package/dist/docs/components/network-logo.md +248 -0
  52. package/dist/docs/components/number-field.md +543 -0
  53. package/dist/docs/components/otp-field.md +616 -0
  54. package/dist/docs/components/pagination.md +516 -0
  55. package/dist/docs/components/popover.md +544 -0
  56. package/dist/docs/components/progress.md +388 -0
  57. package/dist/docs/components/qr.md +314 -0
  58. package/dist/docs/components/radio-group.md +528 -0
  59. package/dist/docs/components/secret-value.md +406 -0
  60. package/dist/docs/components/select.md +751 -0
  61. package/dist/docs/components/separator.md +379 -0
  62. package/dist/docs/components/sidebar-menu.md +922 -0
  63. package/dist/docs/components/skeleton.md +517 -0
  64. package/dist/docs/components/slider.md +539 -0
  65. package/dist/docs/components/snippet.md +424 -0
  66. package/dist/docs/components/spinner.md +216 -0
  67. package/dist/docs/components/split-bar.md +139 -0
  68. package/dist/docs/components/split-button.md +464 -0
  69. package/dist/docs/components/stack.md +299 -0
  70. package/dist/docs/components/status-chip.md +226 -0
  71. package/dist/docs/components/status-indicator.md +119 -0
  72. package/dist/docs/components/status-row.md +174 -0
  73. package/dist/docs/components/stepper.md +451 -0
  74. package/dist/docs/components/success-hero.md +157 -0
  75. package/dist/docs/components/surface.md +639 -0
  76. package/dist/docs/components/switch.md +472 -0
  77. package/dist/docs/components/table.md +967 -0
  78. package/dist/docs/components/tabs.md +627 -0
  79. package/dist/docs/components/tempo-logo.md +185 -0
  80. package/dist/docs/components/text-input.md +624 -0
  81. package/dist/docs/components/textarea.md +352 -0
  82. package/dist/docs/components/toast.md +535 -0
  83. package/dist/docs/components/token-logo.md +371 -0
  84. package/dist/docs/components/token.md +247 -0
  85. package/dist/docs/components/tooltip.md +490 -0
  86. package/dist/docs/components/verification-code.md +116 -0
  87. package/dist/docs/components/visual.md +249 -0
  88. package/dist/docs/components.md +203 -0
  89. package/dist/docs/foundations/accessibility.md +377 -0
  90. package/dist/docs/foundations/agents.md +150 -0
  91. package/dist/docs/foundations/color.md +522 -0
  92. package/dist/docs/foundations/content/tone.md +194 -0
  93. package/dist/docs/foundations/content/voice.md +194 -0
  94. package/dist/docs/foundations/content/writing-style.md +406 -0
  95. package/dist/docs/foundations/iconography.md +282 -0
  96. package/dist/docs/foundations/intent.md +360 -0
  97. package/dist/docs/foundations/layout.md +508 -0
  98. package/dist/docs/foundations/linter.md +296 -0
  99. package/dist/docs/foundations/motion.md +151 -0
  100. package/dist/docs/foundations/radius-and-padding.md +86 -0
  101. package/dist/docs/foundations/typography.md +266 -0
  102. package/dist/docs/index.md +137 -0
  103. package/dist/docs/introduction/faq.md +132 -0
  104. package/dist/docs/introduction/getting-started.md +377 -0
  105. package/dist/docs/patterns/blockchain-values.md +709 -0
  106. package/dist/docs/patterns/dashboards.md +638 -0
  107. package/dist/docs/patterns/destructive-actions.md +626 -0
  108. package/dist/docs/patterns/empty-states.md +612 -0
  109. package/dist/docs/patterns/errors-and-validation.md +808 -0
  110. package/dist/docs/patterns/feedback-and-status.md +518 -0
  111. package/dist/docs/patterns/forms.md +679 -0
  112. package/dist/docs/patterns/loading.md +630 -0
  113. package/dist/docs/patterns/navigation.md +397 -0
  114. package/dist/docs/patterns/payments.md +765 -0
  115. package/dist/docs/patterns/responsive-layout.md +997 -0
  116. package/dist/docs/patterns/tables-and-lists.md +964 -0
  117. package/dist/docs/products/tempo-console.md +671 -0
  118. package/dist/docs/products/tempo-performance.md +437 -0
  119. package/dist/docs/products/tempo-wallet.md +438 -0
  120. package/package.json +1 -1
@@ -0,0 +1,751 @@
1
+ # Select
2
+
3
+ Opens a list of options for choosing one value.
4
+
5
+ [View source and API](https://github.com/tempoxyz/ds/blob/main/src/web/components/Select.tsx)
6
+
7
+ Figma: [Inputs/Select](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-5451) (`Select.Trigger`)
8
+
9
+ ## Overview
10
+
11
+ Select renders a trigger that opens a listbox, for choosing one value from a short list. If the options fit on screen, prefer [ChoiceGroup](/docs/components/choice-group).
12
+
13
+ ## Examples
14
+
15
+ ### Basic
16
+
17
+ ```tsx
18
+ import { Field, Select, style } from '@tempoxyz/ds'
19
+ import { Coins, Ethereum, Wallet } from '@tempoxyz/ds/icons'
20
+ import { useState } from 'react'
21
+
22
+ const assets = [
23
+ { balance: '1,240.50', icon: <Coins />, label: 'pathUSD', value: 'pathusd' },
24
+ { balance: '845.00', icon: <Ethereum />, label: 'USDC.e', value: 'usdce' },
25
+ { balance: '0.00', icon: <Wallet />, label: 'Tether', value: 'usdt' },
26
+ ]
27
+
28
+ const items = Object.fromEntries(assets.map((asset) => [asset.value, asset.label]))
29
+
30
+ function Example(props: Pick<Select.Trigger.Props, 'disabled' | 'invalid' | 'variant'>) {
31
+ const [value, setValue] = useState<string | null>('pathusd')
32
+
33
+ return (
34
+ <Select.Root items={items} onValueChange={setValue} value={value}>
35
+ <Select.Trigger aria-label="Asset" {...props} {...styles.trigger()}>
36
+ <Select.Value placeholder="Select an asset" />
37
+ <Select.Icon />
38
+ </Select.Trigger>
39
+ <Select.Popup>
40
+ {assets.map((asset) => (
41
+ <Select.Item key={asset.value} label={asset.label} value={asset.value}>
42
+ <Select.ItemPrefix>{asset.icon}</Select.ItemPrefix>
43
+ <Select.ItemText>{asset.label}</Select.ItemText>
44
+ <Select.ItemSuffix>{asset.balance}</Select.ItemSuffix>
45
+ </Select.Item>
46
+ ))}
47
+ <Select.Separator />
48
+ <Select.Group>
49
+ <Select.GroupLabel>Coming soon</Select.GroupLabel>
50
+ <Select.Item disabled value="other">
51
+ <Select.ItemText>Other</Select.ItemText>
52
+ </Select.Item>
53
+ </Select.Group>
54
+ </Select.Popup>
55
+ </Select.Root>
56
+ )
57
+ }
58
+
59
+ export function Demo() {
60
+ return (
61
+ <Field.Root>
62
+ <Field.Label>Asset</Field.Label>
63
+ <Example />
64
+ </Field.Root>
65
+ )
66
+ }
67
+
68
+ namespace styles {
69
+ export const trigger = style({ maxWidth: '320px !custom' })
70
+ }
71
+ ```
72
+
73
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Select/Basic.tsx)
74
+
75
+ ### Variant
76
+
77
+ Set `variant` on `Select.Trigger` to `default` (default) or `outlined`, which draws a 1px line instead of the fill.
78
+
79
+ ```tsx
80
+ import { Field, Select, style } from '@tempoxyz/ds'
81
+
82
+ const items = { mainnet: 'Tempo', testnet: 'Tempo Testnet' }
83
+
84
+ export function Demo() {
85
+ return (
86
+ <div {...styles.grid()}>
87
+ {(['default', 'outlined'] as const).map((variant) => (
88
+ <Field.Root key={variant}>
89
+ <Field.Label>Network</Field.Label>
90
+ <Select.Root defaultValue="mainnet" items={items}>
91
+ <Select.Trigger variant={variant}>
92
+ <Select.Value />
93
+ <Select.Icon />
94
+ </Select.Trigger>
95
+ <Select.Popup>
96
+ {Object.entries(items).map(([value, label]) => (
97
+ <Select.Item key={value} label={label} value={value}>
98
+ <Select.ItemText>{label}</Select.ItemText>
99
+ </Select.Item>
100
+ ))}
101
+ </Select.Popup>
102
+ </Select.Root>
103
+ </Field.Root>
104
+ ))}
105
+ </div>
106
+ )
107
+ }
108
+
109
+ namespace styles {
110
+ export const grid = style({
111
+ alignItems: 'start',
112
+ display: 'grid',
113
+ gap: '24',
114
+ gridTemplateColumns: 'repeat(auto-fill, minmax(min(280px, 100%), 1fr)) !custom',
115
+ })
116
+ }
117
+ ```
118
+
119
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Select/Variant.tsx)
120
+
121
+ ### Prefix
122
+
123
+ Put a `Select.Prefix` before `Select.Value` to name what a filter selects, such as By in By Anyone. Name the trigger with `aria-label` too.
124
+
125
+ ```tsx
126
+ /**
127
+ * Prefix demo for the Select page: a dashboard filter whose prefix names what it filters.
128
+ * @module
129
+ */
130
+ import { Select, style } from '@tempoxyz/ds'
131
+
132
+ const owners = { anyone: 'Anyone', me: 'Me', team: 'My team' }
133
+
134
+ /** Renders a filter that hugs its prefix, value, and chevron. */
135
+ export function Demo() {
136
+ return (
137
+ <Select.Root defaultValue="anyone" items={owners}>
138
+ <Select.Trigger aria-label="Owner" {...styles.trigger()}>
139
+ <Select.Prefix>By</Select.Prefix>
140
+ <Select.Value />
141
+ <Select.Icon />
142
+ </Select.Trigger>
143
+ <Select.Popup>
144
+ {Object.entries(owners).map(([value, label]) => (
145
+ <Select.Item key={value} label={label} value={value}>
146
+ <Select.ItemText>{label}</Select.ItemText>
147
+ </Select.Item>
148
+ ))}
149
+ </Select.Popup>
150
+ </Select.Root>
151
+ )
152
+ }
153
+
154
+ namespace styles {
155
+ // A filter sizes to its content instead of filling the row.
156
+ export const trigger = style({ width: 'max-content !custom' })
157
+ }
158
+ ```
159
+
160
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Select/Prefix.tsx)
161
+
162
+ ### Field
163
+
164
+ Wrap the select in a `Field.Root` to label it and describe it.
165
+
166
+ ```tsx
167
+ import { Field, Select, style } from '@tempoxyz/ds'
168
+ import { Coins, Ethereum, Wallet } from '@tempoxyz/ds/icons'
169
+ import { useState } from 'react'
170
+
171
+ const assets = [
172
+ { balance: '1,240.50', icon: <Coins />, label: 'pathUSD', value: 'pathusd' },
173
+ { balance: '845.00', icon: <Ethereum />, label: 'USDC.e', value: 'usdce' },
174
+ { balance: '0.00', icon: <Wallet />, label: 'Tether', value: 'usdt' },
175
+ ]
176
+
177
+ const items = Object.fromEntries(assets.map((asset) => [asset.value, asset.label]))
178
+
179
+ function Example(props: Pick<Select.Trigger.Props, 'disabled' | 'invalid' | 'variant'>) {
180
+ const [value, setValue] = useState<string | null>('pathusd')
181
+
182
+ return (
183
+ <Select.Root items={items} onValueChange={setValue} value={value}>
184
+ <Select.Trigger aria-label="Asset" {...props} {...styles.trigger()}>
185
+ <Select.Value placeholder="Select an asset" />
186
+ <Select.Icon />
187
+ </Select.Trigger>
188
+ <Select.Popup>
189
+ {assets.map((asset) => (
190
+ <Select.Item key={asset.value} label={asset.label} value={asset.value}>
191
+ <Select.ItemPrefix>{asset.icon}</Select.ItemPrefix>
192
+ <Select.ItemText>{asset.label}</Select.ItemText>
193
+ <Select.ItemSuffix>{asset.balance}</Select.ItemSuffix>
194
+ </Select.Item>
195
+ ))}
196
+ <Select.Separator />
197
+ <Select.Group>
198
+ <Select.GroupLabel>Coming soon</Select.GroupLabel>
199
+ <Select.Item disabled value="other">
200
+ <Select.ItemText>Other</Select.ItemText>
201
+ </Select.Item>
202
+ </Select.Group>
203
+ </Select.Popup>
204
+ </Select.Root>
205
+ )
206
+ }
207
+
208
+ export function Demo() {
209
+ return (
210
+ <div {...styles.grid()}>
211
+ <Field.Root>
212
+ <Field.Label>Asset</Field.Label>
213
+ <Example />
214
+ <Field.Description>Pays the network fee.</Field.Description>
215
+ </Field.Root>
216
+ </div>
217
+ )
218
+ }
219
+
220
+ namespace styles {
221
+ export const grid = style({
222
+ alignItems: 'start',
223
+ display: 'grid',
224
+ gap: '12',
225
+ gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr)) !custom',
226
+ })
227
+
228
+ export const trigger = style({ maxWidth: '320px !custom' })
229
+ }
230
+ ```
231
+
232
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Select/Field.tsx)
233
+
234
+ ### Invalid
235
+
236
+ Set `invalid` on `Select.Trigger` to mark the value as invalid.
237
+
238
+ ```tsx
239
+ import { Select, style } from '@tempoxyz/ds'
240
+ import { Coins, Ethereum, Wallet } from '@tempoxyz/ds/icons'
241
+ import { useState } from 'react'
242
+
243
+ const assets = [
244
+ { balance: '1,240.50', icon: <Coins />, label: 'pathUSD', value: 'pathusd' },
245
+ { balance: '845.00', icon: <Ethereum />, label: 'USDC.e', value: 'usdce' },
246
+ { balance: '0.00', icon: <Wallet />, label: 'Tether', value: 'usdt' },
247
+ ]
248
+
249
+ const items = Object.fromEntries(assets.map((asset) => [asset.value, asset.label]))
250
+
251
+ function Example(props: Pick<Select.Trigger.Props, 'disabled' | 'invalid' | 'variant'>) {
252
+ const [value, setValue] = useState<string | null>('pathusd')
253
+
254
+ return (
255
+ <Select.Root items={items} onValueChange={setValue} value={value}>
256
+ <Select.Trigger aria-label="Asset" {...props} {...styles.trigger()}>
257
+ <Select.Value placeholder="Select an asset" />
258
+ <Select.Icon />
259
+ </Select.Trigger>
260
+ <Select.Popup>
261
+ {assets.map((asset) => (
262
+ <Select.Item key={asset.value} label={asset.label} value={asset.value}>
263
+ <Select.ItemPrefix>{asset.icon}</Select.ItemPrefix>
264
+ <Select.ItemText>{asset.label}</Select.ItemText>
265
+ <Select.ItemSuffix>{asset.balance}</Select.ItemSuffix>
266
+ </Select.Item>
267
+ ))}
268
+ <Select.Separator />
269
+ <Select.Group>
270
+ <Select.GroupLabel>Coming soon</Select.GroupLabel>
271
+ <Select.Item disabled value="other">
272
+ <Select.ItemText>Other</Select.ItemText>
273
+ </Select.Item>
274
+ </Select.Group>
275
+ </Select.Popup>
276
+ </Select.Root>
277
+ )
278
+ }
279
+
280
+ export function Demo() {
281
+ return <Example invalid />
282
+ }
283
+
284
+ namespace styles {
285
+ export const trigger = style({ maxWidth: '320px !custom' })
286
+ }
287
+ ```
288
+
289
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Select/Invalid.tsx)
290
+
291
+ ### Disabled
292
+
293
+ ```tsx
294
+ import { Select, style } from '@tempoxyz/ds'
295
+ import { Coins, Ethereum, Wallet } from '@tempoxyz/ds/icons'
296
+ import { useState } from 'react'
297
+
298
+ const assets = [
299
+ { balance: '1,240.50', icon: <Coins />, label: 'pathUSD', value: 'pathusd' },
300
+ { balance: '845.00', icon: <Ethereum />, label: 'USDC.e', value: 'usdce' },
301
+ { balance: '0.00', icon: <Wallet />, label: 'Tether', value: 'usdt' },
302
+ ]
303
+
304
+ const items = Object.fromEntries(assets.map((asset) => [asset.value, asset.label]))
305
+
306
+ function Example(props: Pick<Select.Trigger.Props, 'disabled' | 'invalid' | 'variant'>) {
307
+ const [value, setValue] = useState<string | null>('pathusd')
308
+
309
+ return (
310
+ <Select.Root items={items} onValueChange={setValue} value={value}>
311
+ <Select.Trigger aria-label="Asset" {...props} {...styles.trigger()}>
312
+ <Select.Value placeholder="Select an asset" />
313
+ <Select.Icon />
314
+ </Select.Trigger>
315
+ <Select.Popup>
316
+ {assets.map((asset) => (
317
+ <Select.Item key={asset.value} label={asset.label} value={asset.value}>
318
+ <Select.ItemPrefix>{asset.icon}</Select.ItemPrefix>
319
+ <Select.ItemText>{asset.label}</Select.ItemText>
320
+ <Select.ItemSuffix>{asset.balance}</Select.ItemSuffix>
321
+ </Select.Item>
322
+ ))}
323
+ <Select.Separator />
324
+ <Select.Group>
325
+ <Select.GroupLabel>Coming soon</Select.GroupLabel>
326
+ <Select.Item disabled value="other">
327
+ <Select.ItemText>Other</Select.ItemText>
328
+ </Select.Item>
329
+ </Select.Group>
330
+ </Select.Popup>
331
+ </Select.Root>
332
+ )
333
+ }
334
+
335
+ export function Demo() {
336
+ return <Example disabled />
337
+ }
338
+
339
+ namespace styles {
340
+ export const trigger = style({ maxWidth: '320px !custom' })
341
+ }
342
+ ```
343
+
344
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Select/Disabled.tsx)
345
+
346
+ ## Anatomy
347
+
348
+ Import the component and assemble its parts.
349
+
350
+ ```tsx
351
+ import { Select } from '@tempoxyz/ds'
352
+
353
+ <Select.Root>
354
+ <Select.Trigger>
355
+ <Select.Prefix />
356
+ <Select.Value />
357
+ <Select.Icon />
358
+ </Select.Trigger>
359
+ <Select.Popup>
360
+ <Select.Item>
361
+ <Select.ItemPrefix />
362
+ <Select.ItemText />
363
+ <Select.ItemSuffix />
364
+ </Select.Item>
365
+ <Select.Separator />
366
+ <Select.Group>
367
+ <Select.GroupLabel />
368
+ </Select.Group>
369
+ </Select.Popup>
370
+ </Select.Root>
371
+ ```
372
+
373
+ ## Guidelines
374
+
375
+ ### When to Use
376
+
377
+ * Choose one value from more than five scannable options, such as an asset or a network.
378
+ * Filter a dashboard or table where space is tight, such as a chart's time range.
379
+ * Set `multiple` to pick several values when a [CheckboxGroup](/docs/components/checkbox-group) takes too much room.
380
+
381
+ ### When Not to Use
382
+
383
+ * Use [RadioGroup](/docs/components/radio-group) for two to five options compared side by side.
384
+ * Use [ChoiceGroup](/docs/components/choice-group) for two to four short options that switch a view, such as 1H, 24H, and 7D.
385
+ * Use [Combobox](/docs/components/combobox) for a long list filtered by typing, such as every token on Tempo.
386
+ * Use [Autocomplete](/docs/components/autocomplete) when any typed text is a valid value.
387
+ * Use [Menu](/docs/components/menu) for a list of actions. A select holds a value.
388
+
389
+ ### Show Labels, Not Raw Values
390
+
391
+ Pass `items` to `Select.Root` to map each value to its label. Without it, `Select.Value` renders the raw value.
392
+
393
+ **Do:** Pass `items` so the trigger shows the asset's name.
394
+
395
+ ```tsx
396
+ /**
397
+ * Do example for "Show Labels, Not Raw Values" on the Select page.
398
+ * Pass `items` so the trigger shows the asset's name.
399
+ * @module
400
+ */
401
+ import { Field, Select, style } from '@tempoxyz/ds'
402
+
403
+ const items = { pathusd: 'pathUSD', usdce: 'USDC.e' }
404
+
405
+ /** Renders the static preview, which the guideline card shows inert. */
406
+ export function Demo() {
407
+ return (
408
+ <Field.Root {...styles.field()}>
409
+ <Field.Label>Asset</Field.Label>
410
+ <Select.Root defaultValue="usdce" items={items}>
411
+ <Select.Trigger>
412
+ <Select.Value placeholder="Select an asset" />
413
+ <Select.Icon />
414
+ </Select.Trigger>
415
+ <Select.Popup>
416
+ <Select.Item value="pathusd">
417
+ <Select.ItemText>pathUSD</Select.ItemText>
418
+ </Select.Item>
419
+ <Select.Item value="usdce">
420
+ <Select.ItemText>USDC.e</Select.ItemText>
421
+ </Select.Item>
422
+ </Select.Popup>
423
+ </Select.Root>
424
+ </Field.Root>
425
+ )
426
+ }
427
+
428
+ namespace styles {
429
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
430
+ }
431
+ ```
432
+
433
+ **Don't:** Leave the trigger showing an internal value such as `usdce`.
434
+
435
+ ```tsx
436
+ /**
437
+ * Don't example for "Show Labels, Not Raw Values" on the Select page.
438
+ * Don't leave the trigger showing an internal value such as `usdce`.
439
+ * @module
440
+ */
441
+ import { Field, Select, style } from '@tempoxyz/ds'
442
+
443
+ /** Renders the static preview, which the guideline card shows inert. */
444
+ export function Demo() {
445
+ return (
446
+ <Field.Root {...styles.field()}>
447
+ <Field.Label>Asset</Field.Label>
448
+ <Select.Root defaultValue="usdce">
449
+ <Select.Trigger>
450
+ <Select.Value placeholder="Select an asset" />
451
+ <Select.Icon />
452
+ </Select.Trigger>
453
+ <Select.Popup>
454
+ <Select.Item value="pathusd">
455
+ <Select.ItemText>pathUSD</Select.ItemText>
456
+ </Select.Item>
457
+ <Select.Item value="usdce">
458
+ <Select.ItemText>USDC.e</Select.ItemText>
459
+ </Select.Item>
460
+ </Select.Popup>
461
+ </Select.Root>
462
+ </Field.Root>
463
+ )
464
+ }
465
+
466
+ namespace styles {
467
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
468
+ }
469
+ ```
470
+
471
+ ### Order Options Predictably
472
+
473
+ Pick one ordering rule per list and keep it everywhere the list appears.
474
+
475
+ * **Numeric or Chronological (smallest first):** For values with a natural order, such as time ranges, sizes, and block heights.
476
+ * **Alphabetical (Select.Group):** For tokens, networks, and countries, with the common ones grouped first.
477
+ * **Most Used First:** For a few options with one clear favorite first, then the rest in a fixed order.
478
+
479
+ * Don't reorder options by recent use. People learn positions and typeahead order.
480
+ * Split long lists into `Select.Group` sections with a `Select.GroupLabel`, divided by `Select.Separator`.
481
+ * Hide options that can never be chosen. Show a `disabled` item only when its group label or text states why, such as Coming soon.
482
+
483
+ ### Choose the Default Deliberately
484
+
485
+ If one choice suits most people, preselect it with `defaultValue`, such as the largest balance or the last 24 hours. Always give a filter a default.
486
+
487
+ **Do:** Give a dashboard filter a default that shows data right away.
488
+
489
+ ```tsx
490
+ /**
491
+ * Do example for "Choose the Default Deliberately" on the Select page.
492
+ * Give a dashboard filter a default that shows data right away.
493
+ * @module
494
+ */
495
+ import { Field, Select, style } from '@tempoxyz/ds'
496
+
497
+ const items = { '1h': 'Last hour', '24h': 'Last 24 hours', '7d': 'Last 7 days' }
498
+
499
+ /** Renders the static preview, which the guideline card shows inert. */
500
+ export function Demo() {
501
+ return (
502
+ <Field.Root {...styles.field()}>
503
+ <Field.Label>Time range</Field.Label>
504
+ <Select.Root defaultValue="24h" items={items}>
505
+ <Select.Trigger>
506
+ <Select.Value placeholder="Select a time range" />
507
+ <Select.Icon />
508
+ </Select.Trigger>
509
+ <Select.Popup size="small">
510
+ {Object.entries(items).map(([value, label]) => (
511
+ <Select.Item key={value} value={value}>
512
+ <Select.ItemText>{label}</Select.ItemText>
513
+ </Select.Item>
514
+ ))}
515
+ </Select.Popup>
516
+ </Select.Root>
517
+ </Field.Root>
518
+ )
519
+ }
520
+
521
+ namespace styles {
522
+ export const field = style({ width: '12rem !custom' })
523
+ }
524
+ ```
525
+
526
+ **Don't:** Leave a filter empty with a generic placeholder.
527
+
528
+ ```tsx
529
+ /**
530
+ * Don't example for "Choose the Default Deliberately" on the Select page.
531
+ * Don't leave a filter empty with a generic placeholder.
532
+ * @module
533
+ */
534
+ import { Field, Select, style } from '@tempoxyz/ds'
535
+
536
+ const items = { '1h': 'Last hour', '24h': 'Last 24 hours', '7d': 'Last 7 days' }
537
+
538
+ /** Renders the static preview, which the guideline card shows inert. */
539
+ export function Demo() {
540
+ return (
541
+ <Field.Root {...styles.field()}>
542
+ <Field.Label>Time range</Field.Label>
543
+ <Select.Root items={items}>
544
+ <Select.Trigger>
545
+ <Select.Value placeholder="Select" />
546
+ <Select.Icon />
547
+ </Select.Trigger>
548
+ <Select.Popup size="small">
549
+ {Object.entries(items).map(([value, label]) => (
550
+ <Select.Item key={value} value={value}>
551
+ <Select.ItemText>{label}</Select.ItemText>
552
+ </Select.Item>
553
+ ))}
554
+ </Select.Popup>
555
+ </Select.Root>
556
+ </Field.Root>
557
+ )
558
+ }
559
+
560
+ namespace styles {
561
+ export const field = style({ width: '12rem !custom' })
562
+ }
563
+ ```
564
+
565
+ If a guess could cost something, such as a withdrawal network, leave the select empty. Set a `placeholder` on `Select.Value` that names the choice, such as Select a network, and validate on submit. Add a None option only if none is a valid answer.
566
+
567
+ ### Keep Option Rows Scannable
568
+
569
+ * **Prefix and suffix:** Put a token icon or [Avatar](/docs/components/avatar) in `Select.ItemPrefix`, and a secondary value such as a balance in `Select.ItemSuffix`.
570
+ * **Typeahead label:** If a row holds a prefix or suffix, give `Select.Item` a `label`.
571
+ * **Text length:** Keep `Select.ItemText` short. It truncates beside a prefix or suffix.
572
+ * **First words:** Start options with distinct words, in a parallel format, such as Last hour and Last 24 hours. Typeahead jumps by first letters.
573
+
574
+ **Do:** Put the icon and balance in the item's prefix and suffix, and keep the text short.
575
+
576
+ ```tsx
577
+ /**
578
+ * Do example for "Keep Option Rows Scannable" on the Select page.
579
+ * Put the icon and balance in the item's prefix and suffix, and keep the text short.
580
+ * @module
581
+ */
582
+ import { Check } from '@tempoxyz/ds/icons'
583
+ import { style } from '@tempoxyz/ds'
584
+ import pathusd from '../../coins/PathUSD.svg'
585
+ import usdce from '../../coins/USDCe.svg'
586
+ import usdt0 from '../../coins/USDT0.svg'
587
+
588
+ const assets = [
589
+ { balance: '1,250.00', icon: pathusd, name: 'pathUSD', selected: true },
590
+ { balance: '480.25', icon: usdce, name: 'USDC.e', selected: false },
591
+ { balance: '12.00', icon: usdt0, name: 'USDT0', selected: false },
592
+ ]
593
+
594
+ /** Renders the static preview, which the guideline card shows inert. */
595
+ export function Demo() {
596
+ return (
597
+ <div {...styles.popup()}>
598
+ {assets.map((asset) => (
599
+ <div key={asset.name} {...styles.item()}>
600
+ <img alt="" height={20} src={asset.icon} width={20} />
601
+ <span {...styles.text()}>{asset.name}</span>
602
+ <span {...styles.suffix()}>{asset.balance}</span>
603
+ <span {...styles.check()}>{asset.selected && <Check />}</span>
604
+ </div>
605
+ ))}
606
+ </div>
607
+ )
608
+ }
609
+
610
+ namespace styles {
611
+ export const check = style({ display: 'flex', width: '16px !custom' })
612
+
613
+ export const item = style({
614
+ alignItems: 'center',
615
+ borderRadius: 'xs',
616
+ color: 'content.primary',
617
+ display: 'flex',
618
+ gap: '8',
619
+ minHeight: '40',
620
+ paddingInline: '12',
621
+ typography: 'body.b2',
622
+ })
623
+
624
+ export const popup = style({
625
+ backgroundColor: 'background.elevated',
626
+ borderRadius: 'sm',
627
+ boxShadow: 'regular',
628
+ boxSizing: 'border-box',
629
+ padding: '8',
630
+ width: '16rem !custom',
631
+ })
632
+
633
+ export const suffix = style({ color: 'content.secondary', typography: 'body.b3' })
634
+
635
+ export const text = style({ flex: '1 !custom' })
636
+ }
637
+ ```
638
+
639
+ **Don't:** Pack names, details, and balances into the option text.
640
+
641
+ ```tsx
642
+ /**
643
+ * Don't example for "Keep Option Rows Scannable" on the Select page.
644
+ * Don't pack names, details, and balances into the option text.
645
+ * @module
646
+ */
647
+ import { Check } from '@tempoxyz/ds/icons'
648
+ import { style } from '@tempoxyz/ds'
649
+
650
+ const assets = [
651
+ { label: 'pathUSD, Tempo stablecoin, balance 1,250.00', selected: true },
652
+ { label: 'USDC.e, bridged USD Coin, balance 480.25', selected: false },
653
+ { label: 'USDT0, bridged Tether, balance 12.00', selected: false },
654
+ ]
655
+
656
+ /** Renders the static preview, which the guideline card shows inert. */
657
+ export function Demo() {
658
+ return (
659
+ <div {...styles.popup()}>
660
+ {assets.map((asset) => (
661
+ <div key={asset.label} {...styles.item()}>
662
+ <span {...styles.text()}>{asset.label}</span>
663
+ <span {...styles.check()}>{asset.selected && <Check />}</span>
664
+ </div>
665
+ ))}
666
+ </div>
667
+ )
668
+ }
669
+
670
+ namespace styles {
671
+ export const check = style({ display: 'flex', flexShrink: '0 !custom', width: '16px !custom' })
672
+
673
+ export const item = style({
674
+ alignItems: 'center',
675
+ borderRadius: 'xs',
676
+ color: 'content.primary',
677
+ display: 'flex',
678
+ gap: '8',
679
+ minHeight: '40',
680
+ paddingInline: '12',
681
+ typography: 'body.b2',
682
+ })
683
+
684
+ export const popup = style({
685
+ backgroundColor: 'background.elevated',
686
+ borderRadius: 'sm',
687
+ boxShadow: 'regular',
688
+ boxSizing: 'border-box',
689
+ padding: '8',
690
+ width: '16rem !custom',
691
+ })
692
+
693
+ export const text = style({
694
+ flex: '1 !custom',
695
+ minWidth: '0 !custom',
696
+ overflow: 'hidden',
697
+ textOverflow: 'ellipsis',
698
+ whiteSpace: 'nowrap',
699
+ })
700
+ }
701
+ ```
702
+
703
+ ## Accessibility
704
+
705
+ Select handles arrow keys, typeahead, and Escape. It follows the [WAI-ARIA Authoring Practices select-only combobox pattern](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/examples/combobox-select-only/).
706
+
707
+ ### Semantics
708
+
709
+ * **Select.Trigger (combobox):** A button that announces its popup and whether the list is open.
710
+ * **Select.Popup and Select.Item (listbox):** The list renders in a portal. The selected option shows a check, not only a color.
711
+ * **Select.Group (group):** A set of related options, named by the group label inside it.
712
+ * **Invalid and Disabled (aria-invalid):** `invalid` marks the trigger and fills it red. `disabled` removes it from the tab order.
713
+ * **Select.Icon (aria-hidden):** Decorative, so it is hidden from assistive technology and not announced.
714
+ * **Target Size (48px):** The trigger matches the 48px field, above the 24px WCAG 2.2 minimum.
715
+
716
+ ### Keyboard
717
+
718
+ * **Move to the Trigger (Tab):** Moves focus to the trigger.
719
+ * **Open the List (Enter, Space, or Down Arrow):** Opens the list and focuses the selected option.
720
+ * **Move Between Options (Down Arrow and Up Arrow):** Move focus to the next or previous option.
721
+ * **Jump to the Ends (Home and End):** Move focus to the first or last option.
722
+ * **Type to Find (Character):** Focuses the next option starting with that key.
723
+ * **Select an Option (Enter or Space):** On an option, selects it and closes the list.
724
+ * **Close the List (Escape):** Keeps the value and returns focus to the trigger.
725
+
726
+ ### Your Responsibilities
727
+
728
+ * **Name the Trigger (Field.Label):** Use a visible field label. If none fits, set `aria-label`.
729
+ * **Explain Invalid Values (Field.Error):** Explain invalid values in text, not only the red fill.
730
+
731
+ ## Related
732
+
733
+ [Combobox](/docs/components/combobox)
734
+
735
+ Use for long, filterable lists.
736
+
737
+ [RadioGroup](/docs/components/radio-group)
738
+
739
+ Use for a few options shown at once.
740
+
741
+ [ChoiceGroup](/docs/components/choice-group)
742
+
743
+ Use for switching views.
744
+
745
+ [Menu](/docs/components/menu)
746
+
747
+ Use for lists of actions.
748
+
749
+ [Dashboards](/docs/patterns/dashboards)
750
+
751
+ Covers filter bars and controls.