@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,679 @@
1
+ # Forms
2
+
3
+ Order fields, labels, and actions so a person completes a form in one pass.
4
+
5
+ ## Overview
6
+
7
+ Ask only for the values the task needs. Remove every field you can derive, default, or ask for later.
8
+
9
+ This page covers layout, labels, save models, and actions. [Errors and Validation](/docs/patterns/errors-and-validation) covers checking values and reporting problems.
10
+
11
+ ## Choosing a Control
12
+
13
+ Choose the control from the shape of the value, not from its look.
14
+
15
+ * **Free Text (TextInput):** For a name or address. Use a [Textarea](/docs/components/textarea) for a longer memo.
16
+ * **An Amount of Money (AmountInput):** [AmountInput](/docs/components/amount-input) sets a decimal keyboard and rejects anything that is not a number.
17
+ * **Five or Fewer Options (RadioGroup):** A [RadioGroup](/docs/components/radio-group) keeps every choice visible.
18
+ * **Many Options (Select):** A [Select](/docs/components/select). If people need to search, use a [Combobox](/docs/components/combobox).
19
+
20
+ **Do:** Show a short set of exclusive options in a [RadioGroup](/docs/components/radio-group).
21
+
22
+ ```tsx
23
+ <Fieldset.Root>
24
+ <Fieldset.Legend>Network fee</Fieldset.Legend>
25
+ <RadioGroup.Root aria-label="Network fee" defaultValue="standard">
26
+ <Field.Root>
27
+ <Field.Item>
28
+ <RadioGroup.Item value="standard" />
29
+ <Field.Label>Standard</Field.Label>
30
+ </Field.Item>
31
+ </Field.Root>
32
+ <Field.Root>
33
+ <Field.Item>
34
+ <RadioGroup.Item value="fast" />
35
+ <Field.Label>Fast</Field.Label>
36
+ </Field.Item>
37
+ </Field.Root>
38
+ </RadioGroup.Root>
39
+ </Fieldset.Root>
40
+ ```
41
+
42
+ **Don't:** Use a [Checkbox](/docs/components/checkbox) for options that exclude each other.
43
+
44
+ ```tsx
45
+ <Fieldset.Root>
46
+ <Fieldset.Legend>Network fee</Fieldset.Legend>
47
+ <Field.Root>
48
+ <Field.Item>
49
+ <Checkbox defaultChecked />
50
+ <Field.Label>Standard</Field.Label>
51
+ </Field.Item>
52
+ </Field.Root>
53
+ <Field.Root>
54
+ <Field.Item>
55
+ <Checkbox />
56
+ <Field.Label>Fast</Field.Label>
57
+ </Field.Item>
58
+ </Field.Root>
59
+ </Fieldset.Root>
60
+ ```
61
+
62
+ ### Help Browsers Fill Values
63
+
64
+ Set `type`, `inputMode`, and `autoComplete` on each `TextInput.Control`, so browsers suggest saved values and phones show the matching keyboard.
65
+
66
+ * **Personal Data (autoComplete):** Name each value, such as `autoComplete="email"`, as WCAG 2.2 requires for personal data.
67
+ * **Machine Values (spellCheck):** Turn off spell check and set `autoComplete="off"` for addresses, API keys, and other machine values.
68
+
69
+ ## Structure
70
+
71
+ 1. **Title:** Names the task, such as New webhook, and states the result of submit.
72
+ 2. **Fields:** One column, in answer order, with related fields grouped.
73
+ 3. **Each field:** Label, control, then a description or an error.
74
+ 4. **Actions:** After the last field, in the [action order](#order-actions).
75
+
76
+ ```tsx
77
+ import { Button, Checkbox, Field, Fieldset, Frame, Stack, style, Surface, Textarea, TextInput, Toast } from '@tempoxyz/ds'
78
+ import { useState } from 'react'
79
+
80
+ export const fill = true
81
+ const events = [
82
+ ['payment.received', 'Payment received'],
83
+ ['payment.sent', 'Payment sent'],
84
+ ['transfer.failed', 'Transfer failed'],
85
+ ] as const
86
+
87
+ function valid(url: string) {
88
+ return /^https:\/\/[^\s/]+\.[^\s]+$/.test(url)
89
+ }
90
+
91
+ function WebhookForm() {
92
+ const toast = Toast.useToast()
93
+ const [invalid, setInvalid] = useState(false)
94
+ const [pending, setPending] = useState(false)
95
+ const [submitted, setSubmitted] = useState(false)
96
+ const [url, setUrl] = useState('')
97
+
98
+ return (
99
+ <Surface
100
+ {...styles.form()}
101
+ render={
102
+ <form
103
+ noValidate
104
+ onSubmit={(event) => {
105
+ event.preventDefault()
106
+ setSubmitted(true)
107
+
108
+ if (!valid(url)) {
109
+ setInvalid(true)
110
+ event.currentTarget.querySelector<HTMLInputElement>('input[name="url"]')?.focus()
111
+ return
112
+ }
113
+
114
+ setPending(true)
115
+ setTimeout(() => {
116
+ setPending(false)
117
+ toast.add({ description: url, intent: 'positive', title: 'Webhook created' })
118
+ }, 1200)
119
+ }}
120
+ />
121
+ }
122
+ >
123
+ <Frame.Root>
124
+ <Frame.Header>
125
+ <Frame.Title>New webhook</Frame.Title>
126
+ <Frame.Description>
127
+ Tempo sends a POST request to your endpoint for each event you choose.
128
+ </Frame.Description>
129
+ </Frame.Header>
130
+ <Fieldset.Root disabled={pending}>
131
+ <Fieldset.Legend>Endpoint</Fieldset.Legend>
132
+ <Field.Root invalid={invalid}>
133
+ <Field.Label>Endpoint URL</Field.Label>
134
+ <TextInput.Group>
135
+ <TextInput.Control
136
+ autoComplete="off"
137
+ name="url"
138
+ onValueChange={(value) => {
139
+ setUrl(value)
140
+ // After the first submit, the error clears as soon as the value is fixed.
141
+ if (submitted) setInvalid(!valid(value))
142
+ }}
143
+ placeholder="https://api.example.com/tempo"
144
+ required
145
+ spellCheck={false}
146
+ type="url"
147
+ value={url}
148
+ />
149
+ </TextInput.Group>
150
+ <Field.Description>Use an HTTPS URL on a server you control.</Field.Description>
151
+ <Field.Error match={invalid}>Enter a URL that starts with https://.</Field.Error>
152
+ </Field.Root>
153
+ <Field.Root>
154
+ <Field.Label>Description (optional)</Field.Label>
155
+ <Textarea name="description" placeholder="Order fulfillment" rows={2} />
156
+ </Field.Root>
157
+ </Fieldset.Root>
158
+ <Fieldset.Root disabled={pending}>
159
+ <Fieldset.Legend>Events</Fieldset.Legend>
160
+ <Stack gap="12">
161
+ {events.map(([value, label]) => (
162
+ <Field.Root key={value}>
163
+ <Field.Item>
164
+ <Checkbox defaultChecked={value === 'payment.received'} name={value} />
165
+ <Field.Label>{label}</Field.Label>
166
+ </Field.Item>
167
+ </Field.Root>
168
+ ))}
169
+ </Stack>
170
+ </Fieldset.Root>
171
+ <Frame.Footer>
172
+ <Frame.Actions {...styles.actions()}>
173
+ <Button type="button" variant="secondary">
174
+ Cancel
175
+ </Button>
176
+ <Button loading={pending} type="submit">
177
+ Create webhook
178
+ </Button>
179
+ </Frame.Actions>
180
+ </Frame.Footer>
181
+ </Frame.Root>
182
+ </Surface>
183
+ )
184
+ }
185
+
186
+ export function Demo() {
187
+ return (
188
+ <div {...styles.page()}>
189
+ <Toast.Provider>
190
+ <WebhookForm />
191
+ <Toast.Viewport />
192
+ </Toast.Provider>
193
+ </div>
194
+ )
195
+ }
196
+
197
+ namespace styles {
198
+ // Below 640px the actions stack at full width with the primary action on top. From 640px they sit
199
+ // at the end at their content width.
200
+ export const actions = style({
201
+ flexDirection: 'column-reverse',
202
+ rowGap: '8',
203
+ selectors: { '& > *': { flexBasis: 'auto !custom', width: '100% !custom' } },
204
+ '@media (min-width: 40rem)': {
205
+ flexDirection: 'row',
206
+ justifyContent: 'flex-end',
207
+ selectors: { '& > *': { flexGrow: '0 !custom', width: 'auto !custom' } },
208
+ },
209
+ })
210
+
211
+ export const form = style({
212
+ boxSizing: 'border-box',
213
+ maxWidth: '30rem !custom',
214
+ width: '100% !custom',
215
+ })
216
+
217
+ export const page = style({
218
+ backgroundColor: 'background.primary',
219
+ display: 'grid',
220
+ justifyItems: 'center',
221
+ padding: '16',
222
+ })
223
+ }
224
+ ```
225
+
226
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/Patterns/WebhookForm.tsx)
227
+
228
+ This reference layout uses [Surface](/docs/components/surface), [Frame](/docs/components/frame), [Fieldset](/docs/components/fieldset), and [Field](/docs/components/field). Submit an empty URL to show the error, then fix the URL to clear it.
229
+
230
+ ### Use One Column
231
+
232
+ Stack fields in a single column, in the order a person expects, such as recipient, then amount, then memo. Put two fields on one row only if they form one short value, such as a month and a year.
233
+
234
+ **Do:** Stack fields in one column, in the order you answer them.
235
+
236
+ ```tsx
237
+ /**
238
+ * Do example for "Use One Column" on the Forms page.
239
+ * Stack fields in one column, in the order you answer them.
240
+ * @module
241
+ */
242
+ import { Field, Stack, style, TextInput } from '@tempoxyz/ds'
243
+
244
+ /** Renders the static preview, which the guideline card shows inert. */
245
+ export function Demo() {
246
+ return (
247
+ <Stack gap="24" {...styles.fill()}>
248
+ <Field.Root>
249
+ <Field.Label>Recipient</Field.Label>
250
+ <TextInput.Group>
251
+ <TextInput.Control placeholder="0x…" />
252
+ </TextInput.Group>
253
+ </Field.Root>
254
+ <Field.Root>
255
+ <Field.Label>Amount</Field.Label>
256
+ <TextInput.Group>
257
+ <TextInput.Control inputMode="decimal" placeholder="0.00" />
258
+ <TextInput.Suffix>USDC</TextInput.Suffix>
259
+ </TextInput.Group>
260
+ </Field.Root>
261
+ </Stack>
262
+ )
263
+ }
264
+
265
+ namespace styles {
266
+ export const fill = style({ width: '100% !custom' })
267
+ }
268
+ ```
269
+
270
+ **Don't:** Arrange unrelated fields in a grid, where the reading order is unclear.
271
+
272
+ ```tsx
273
+ /**
274
+ * Don't example for "Use One Column" on the Forms page.
275
+ * Don't arrange unrelated fields in a grid, where the reading order is unclear.
276
+ * @module
277
+ */
278
+ import { Field, style, TextInput } from '@tempoxyz/ds'
279
+
280
+ /** Renders the static preview, which the guideline card shows inert. */
281
+ export function Demo() {
282
+ return (
283
+ <div {...styles.grid()}>
284
+ <Field.Root>
285
+ <Field.Label>Recipient</Field.Label>
286
+ <TextInput.Group>
287
+ <TextInput.Control placeholder="0x…" />
288
+ </TextInput.Group>
289
+ </Field.Root>
290
+ <Field.Root>
291
+ <Field.Label>Amount</Field.Label>
292
+ <TextInput.Group>
293
+ <TextInput.Control inputMode="decimal" placeholder="0.00" />
294
+ </TextInput.Group>
295
+ </Field.Root>
296
+ <Field.Root>
297
+ <Field.Label>Memo</Field.Label>
298
+ <TextInput.Group>
299
+ <TextInput.Control placeholder="Invoice 1042" />
300
+ </TextInput.Group>
301
+ </Field.Root>
302
+ <Field.Root>
303
+ <Field.Label>Fee token</Field.Label>
304
+ <TextInput.Group>
305
+ <TextInput.Control placeholder="PathUSD" />
306
+ </TextInput.Group>
307
+ </Field.Root>
308
+ </div>
309
+ )
310
+ }
311
+
312
+ namespace styles {
313
+ export const grid = style({
314
+ display: 'grid',
315
+ gap: '16',
316
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr)) !custom',
317
+ width: '100% !custom',
318
+ })
319
+ }
320
+ ```
321
+
322
+ ### Group Related Fields
323
+
324
+ Wrap the parts of one idea, such as an address, in a [Fieldset](/docs/components/fieldset) with a `Fieldset.Legend`. Set `variant` once on the fieldset, which passes it to every nested `Field.Root`. If a form outgrows two or three groups, split it into steps with a [Stepper](/docs/components/stepper).
325
+
326
+ **Do:** Group related fields under one legend and one variant.
327
+
328
+ ```tsx
329
+ <Fieldset.Root>
330
+ <Fieldset.Legend>Recipient</Fieldset.Legend>
331
+ <Field.Root>
332
+ <Field.Label>Name</Field.Label>
333
+ <TextInput.Group>
334
+ <TextInput.Control />
335
+ </TextInput.Group>
336
+ </Field.Root>
337
+ <Field.Root>
338
+ <Field.Label>Email</Field.Label>
339
+ <TextInput.Group>
340
+ <TextInput.Control type="email" />
341
+ </TextInput.Group>
342
+ </Field.Root>
343
+ </Fieldset.Root>
344
+ ```
345
+
346
+ **Don't:** Mix field variants within one group.
347
+
348
+ ```tsx
349
+ <Fieldset.Root>
350
+ <Fieldset.Legend>Recipient</Fieldset.Legend>
351
+ <Field.Root>
352
+ <Field.Label>Name</Field.Label>
353
+ <TextInput.Group>
354
+ <TextInput.Control />
355
+ </TextInput.Group>
356
+ </Field.Root>
357
+ <Field.Root>
358
+ <Field.Label>Email</Field.Label>
359
+ <TextInput.Group>
360
+ <TextInput.Control type="email" />
361
+ </TextInput.Group>
362
+ </Field.Root>
363
+ </Fieldset.Root>
364
+ ```
365
+
366
+ ## Labels and Help Text
367
+
368
+ Every control needs a visible `Field.Label`, written as a short noun. Put format hints in `Field.Description`. A placeholder holds only an example value, such as `0x…` or `https://`.
369
+
370
+ ### Put Labels Above Fields
371
+
372
+ [Field](/docs/components/field) puts the label 12px above every control. The label stays visible during input, and long labels wrap without narrowing the control.
373
+
374
+ * **Text Field (TextInput):** The label sits above the field, and the message below it.
375
+ * **Switch (Switch):** The label sits above the switch, as it does above a text field.
376
+
377
+ **Do:** Give every control a visible `Field.Label`, with format hints in `Field.Description`.
378
+
379
+ ```tsx
380
+ /**
381
+ * Do example for "Put Labels Above Fields" on the Forms page.
382
+ * Give every control a visible `Field.Label`, with format hints in `Field.Description`.
383
+ * @module
384
+ */
385
+ import { Field, style, TextInput } from '@tempoxyz/ds'
386
+
387
+ /** Renders the static preview, which the guideline card shows inert. */
388
+ export function Demo() {
389
+ return (
390
+ <Field.Root {...styles.fill()}>
391
+ <Field.Label>Recipient address</Field.Label>
392
+ <TextInput.Group>
393
+ <TextInput.Control placeholder="0x…" />
394
+ </TextInput.Group>
395
+ <Field.Description>A Tempo address that starts with 0x.</Field.Description>
396
+ </Field.Root>
397
+ )
398
+ }
399
+
400
+ namespace styles {
401
+ export const fill = style({ width: '100% !custom' })
402
+ }
403
+ ```
404
+
405
+ **Don't:** Use a placeholder as the label. It disappears on input.
406
+
407
+ ```tsx
408
+ /**
409
+ * Don't example for "Put Labels Above Fields" on the Forms page.
410
+ * Don't use a placeholder as the label. It disappears on input.
411
+ * @module
412
+ */
413
+ import { style, TextInput } from '@tempoxyz/ds'
414
+
415
+ /** Renders the static preview, which the guideline card shows inert. */
416
+ export function Demo() {
417
+ return (
418
+ <TextInput.Group {...styles.fill()}>
419
+ <TextInput.Control aria-label="Recipient address" placeholder="Recipient address" />
420
+ </TextInput.Group>
421
+ )
422
+ }
423
+
424
+ namespace styles {
425
+ export const fill = style({ width: '100% !custom' })
426
+ }
427
+ ```
428
+
429
+ ### Mark Optional Fields
430
+
431
+ Most fields are required, so mark the exceptions. Set `required` on required controls, so assistive technology announces them. Set `noValidate` on the `<form>`, so browser validation popups do not replace `Field.Error`. If every field is optional, such as on a filter form, state this once in the description.
432
+
433
+ **Do:** Write (optional) in the label of each optional field.
434
+
435
+ ```tsx
436
+ /**
437
+ * Do example for "Mark Optional Fields" on the Forms page.
438
+ * Write (optional) in the label of each optional field.
439
+ * @module
440
+ */
441
+ import { Field, Stack, style, TextInput } from '@tempoxyz/ds'
442
+
443
+ /** Renders the static preview, which the guideline card shows inert. */
444
+ export function Demo() {
445
+ return (
446
+ <Stack gap="24" {...styles.fill()}>
447
+ <Field.Root>
448
+ <Field.Label>Key name</Field.Label>
449
+ <TextInput.Group>
450
+ <TextInput.Control defaultValue="Production" required />
451
+ </TextInput.Group>
452
+ </Field.Root>
453
+ <Field.Root>
454
+ <Field.Label>Expires (optional)</Field.Label>
455
+ <TextInput.Group>
456
+ <TextInput.Control placeholder="YYYY-MM-DD" />
457
+ </TextInput.Group>
458
+ </Field.Root>
459
+ </Stack>
460
+ )
461
+ }
462
+
463
+ namespace styles {
464
+ export const fill = style({ width: '100% !custom' })
465
+ }
466
+ ```
467
+
468
+ **Don't:** Mark required fields with an asterisk and leave optional fields unmarked.
469
+
470
+ ```tsx
471
+ /**
472
+ * Don't example for "Mark Optional Fields" on the Forms page.
473
+ * Don't mark required fields with an asterisk and leave optional fields unmarked.
474
+ * @module
475
+ */
476
+ import { Field, Stack, style, TextInput } from '@tempoxyz/ds'
477
+
478
+ /** Renders the static preview, which the guideline card shows inert. */
479
+ export function Demo() {
480
+ return (
481
+ <Stack gap="24" {...styles.fill()}>
482
+ <Field.Root>
483
+ <Field.Label>Key name *</Field.Label>
484
+ <TextInput.Group>
485
+ <TextInput.Control defaultValue="Production" />
486
+ </TextInput.Group>
487
+ </Field.Root>
488
+ <Field.Root>
489
+ <Field.Label>Expires</Field.Label>
490
+ <TextInput.Group>
491
+ <TextInput.Control placeholder="YYYY-MM-DD" />
492
+ </TextInput.Group>
493
+ </Field.Root>
494
+ </Stack>
495
+ )
496
+ }
497
+
498
+ namespace styles {
499
+ export const fill = style({ width: '100% !custom' })
500
+ }
501
+ ```
502
+
503
+ ## Settings
504
+
505
+ ### Choose a Save Model
506
+
507
+ * **Save on Submit (Checkbox):** For values that work together. Nothing changes until you press the primary action.
508
+ * **Save on Change (Switch):** For independent settings. Confirm each change with a [Toast](/docs/components/toast), and revert it if it fails.
509
+
510
+ Do not mix the two in one section. If a person leaves a long form with unsaved changes, confirm the discard (see [Destructive Actions](/docs/patterns/destructive-actions)).
511
+
512
+ **Do:** Use a [Switch](/docs/components/switch) for a setting that applies immediately.
513
+
514
+ ```tsx
515
+ <Field.Root>
516
+ <Field.Label>Payment alerts</Field.Label>
517
+ <Switch defaultChecked />
518
+ <Field.Description>Email me when a payment is confirmed.</Field.Description>
519
+ </Field.Root>
520
+ ```
521
+
522
+ **Don't:** Pair a switch with a save button. If a change waits for submit, use a checkbox.
523
+
524
+ ```tsx
525
+ <Field.Root>
526
+ <Field.Label>Payment alerts</Field.Label>
527
+ <Switch defaultChecked />
528
+ <Field.Description>Email me when a payment is confirmed.</Field.Description>
529
+ </Field.Root>
530
+ <Button size="small">Save changes</Button>
531
+ ```
532
+
533
+ ## Submitting
534
+
535
+ Keep the submit button enabled and validate on press (see [Errors and Validation](/docs/patterns/errors-and-validation#keep-submit-buttons-enabled)). Set `loading` on it while the request runs, to block a second submit.
536
+
537
+ ### Order Actions
538
+
539
+ This order applies to every action group, including forms, dialog footers, and toolbars. Keep it the same across every page of a product.
540
+
541
+ * **Alignment:** Align the group to the end, after the last field.
542
+ * **Order:** Put Cancel or Back before the primary action, so the primary action comes last.
543
+ * **Below 640px:** Stack the actions at full width, with the primary action first.
544
+
545
+ **Do:** Align actions to the end, with Cancel before the primary action.
546
+
547
+ ```tsx
548
+ /**
549
+ * Do example for "Order Actions" on the Forms page.
550
+ * Align actions to the end, with Cancel before the primary action.
551
+ * @module
552
+ */
553
+ import { Button, Field, Stack, style, TextInput } from '@tempoxyz/ds'
554
+
555
+ /** Renders the static preview, which the guideline card shows inert. */
556
+ export function Demo() {
557
+ return (
558
+ <Stack gap="24" {...styles.fill()}>
559
+ <Field.Root>
560
+ <Field.Label>Endpoint URL</Field.Label>
561
+ <TextInput.Group>
562
+ <TextInput.Control defaultValue="https://api.example.com/tempo" />
563
+ </TextInput.Group>
564
+ </Field.Root>
565
+ <div {...styles.actions()}>
566
+ <Button variant="secondary">Cancel</Button>
567
+ <Button>Create webhook</Button>
568
+ </div>
569
+ </Stack>
570
+ )
571
+ }
572
+
573
+ namespace styles {
574
+ export const actions = style({ columnGap: '8', display: 'flex', justifyContent: 'flex-end' })
575
+
576
+ export const fill = style({ width: '100% !custom' })
577
+ }
578
+ ```
579
+
580
+ **Don't:** Put the primary action first and push Cancel to the far edge.
581
+
582
+ ```tsx
583
+ /**
584
+ * Don't example for "Order Actions" on the Forms page.
585
+ * Don't put the primary action first and push Cancel to the far edge.
586
+ * @module
587
+ */
588
+ import { Button, Field, Stack, style, TextInput } from '@tempoxyz/ds'
589
+
590
+ /** Renders the static preview, which the guideline card shows inert. */
591
+ export function Demo() {
592
+ return (
593
+ <Stack gap="24" {...styles.fill()}>
594
+ <Field.Root>
595
+ <Field.Label>Endpoint URL</Field.Label>
596
+ <TextInput.Group>
597
+ <TextInput.Control defaultValue="https://api.example.com/tempo" />
598
+ </TextInput.Group>
599
+ </Field.Root>
600
+ <div {...styles.actions()}>
601
+ <Button>Create webhook</Button>
602
+ <Button variant="secondary">Cancel</Button>
603
+ </div>
604
+ </Stack>
605
+ )
606
+ }
607
+
608
+ namespace styles {
609
+ export const actions = style({ display: 'flex', justifyContent: 'space-between' })
610
+
611
+ export const fill = style({ width: '100% !custom' })
612
+ }
613
+ ```
614
+
615
+ ### Name the Result in the Button
616
+
617
+ Use one primary button, so Enter submits the form. Label it with the result, such as Create webhook.
618
+
619
+ **Do:** Name the result of the form in the button.
620
+
621
+ ```tsx
622
+ <Button>Send payment</Button>
623
+ ```
624
+
625
+ **Don't:** Use a generic label that hides what happens.
626
+
627
+ ```tsx
628
+ <Button>Submit</Button>
629
+ ```
630
+
631
+ ## States
632
+
633
+ * **Default:** Prefill a value when it is known, such as the connected address.
634
+ * **Loading (Skeleton):** Wrap each control in a [Skeleton](/docs/components/skeleton) while saved values load.
635
+ * **Error (Field.Error):** An error under each invalid field (see [Errors and Validation](/docs/patterns/errors-and-validation)).
636
+ * **Disabled (disabled):** Set it on a fieldset to disable a whole group, and state the reason nearby.
637
+ * **Success (success):** Close the form and confirm with a [Toast](/docs/components/toast). `success` sets no ARIA state, so confirm in text too.
638
+
639
+ ## Content
640
+
641
+ * **Nouns and Verbs:** Label fields with nouns and buttons with verbs.
642
+ * **One Sentence:** Keep each description to a single sentence.
643
+
644
+ ## Accessibility
645
+
646
+ * **Visible Labels (label):** Pressing a label focuses its control. A field without a visible label, such as search, needs an `aria-label`.
647
+ * **Announced Help (aria-describedby):** Screen readers read the description and any shown error with the control.
648
+ * **Named Groups (Fieldset.Legend):** Names the native fieldset. Write it as the topic of the group.
649
+ * **Matching Order (Tab):** Keep the visual order the same as the DOM order, so focus moves down one field at a time.
650
+
651
+ ## Related
652
+
653
+ [Field](/docs/components/field)
654
+
655
+ Use for labels, descriptions, and errors.
656
+
657
+ [Fieldset](/docs/components/fieldset)
658
+
659
+ Use for grouping fields.
660
+
661
+ [TextInput](/docs/components/text-input)
662
+
663
+ Use for text.
664
+
665
+ [AmountInput](/docs/components/amount-input)
666
+
667
+ Use for amounts of money.
668
+
669
+ [Button](/docs/components/button)
670
+
671
+ Use for submit and cancel actions.
672
+
673
+ [Errors and Validation](/docs/patterns/errors-and-validation)
674
+
675
+ Covers checking values.
676
+
677
+ [Loading](/docs/patterns/loading)
678
+
679
+ Covers submit and load states.