@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,616 @@
1
+ # OtpField
2
+
3
+ Accepts a one-time passcode, one character per slot.
4
+
5
+ [View source and API](https://github.com/tempoxyz/ds/blob/main/src/web/components/OtpField.tsx)
6
+
7
+ Figma: [Inputs/Code Input](https://www.figma.com/design/yE96kcymnNR88PJp6G1sPs?node-id=5227-3204) (`OtpField.Root`)
8
+
9
+ ## Overview
10
+
11
+ OtpField renders one slot per character of a one-time passcode. Label it with `Field.Label` or a `<label>`, which also names the first slot.
12
+
13
+ ## Examples
14
+
15
+ ### Basic
16
+
17
+ ```tsx
18
+ import { OtpField, style } from '@tempoxyz/ds'
19
+ import { useState } from 'react'
20
+
21
+ const code = '123456'
22
+
23
+ const messages = {
24
+ idle: `Enter ${code} to verify`,
25
+ invalid: 'Wrong code. Delete a digit and try again.',
26
+ success: 'Code accepted',
27
+ }
28
+
29
+ export function Demo() {
30
+ const [value, setValue] = useState('')
31
+ const status = (() => {
32
+ if (value.length < code.length) return 'idle'
33
+ if (value === code) return 'success'
34
+ return 'invalid'
35
+ })()
36
+
37
+ return (
38
+ <div {...styles.column()}>
39
+ <OtpField.Root
40
+ aria-label="Verification code"
41
+ invalid={status === 'invalid'}
42
+ length={6}
43
+ onValueChange={setValue}
44
+ success={status === 'success'}
45
+ value={value}
46
+ />
47
+ <output>{messages[status]}</output>
48
+ </div>
49
+ )
50
+ }
51
+
52
+ namespace styles {
53
+ export const column = style({
54
+ alignItems: 'flex-start',
55
+ display: 'flex',
56
+ flexDirection: 'column',
57
+ gap: '24',
58
+ })
59
+ }
60
+ ```
61
+
62
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Basic.tsx)
63
+
64
+ ### Invalid
65
+
66
+ Set `invalid` to fill the slots red and nudge them sideways.
67
+
68
+ ```tsx
69
+ import { OtpField, style } from '@tempoxyz/ds'
70
+
71
+ export function Demo() {
72
+ return (
73
+ <div {...styles.column()}>
74
+ <OtpField.Root aria-label="Invalid code" defaultValue="000000" invalid length={6} />
75
+ </div>
76
+ )
77
+ }
78
+
79
+ namespace styles {
80
+ export const column = style({
81
+ alignItems: 'flex-start',
82
+ display: 'flex',
83
+ flexDirection: 'column',
84
+ gap: '24',
85
+ })
86
+ }
87
+ ```
88
+
89
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Invalid.tsx)
90
+
91
+ ### Success
92
+
93
+ Set `success` to fill the slots green.
94
+
95
+ ```tsx
96
+ import { OtpField, style } from '@tempoxyz/ds'
97
+
98
+ export function Demo() {
99
+ return (
100
+ <div {...styles.column()}>
101
+ <OtpField.Root aria-label="Verified code" defaultValue="123456" length={6} success />
102
+ </div>
103
+ )
104
+ }
105
+
106
+ namespace styles {
107
+ export const column = style({
108
+ alignItems: 'flex-start',
109
+ display: 'flex',
110
+ flexDirection: 'column',
111
+ gap: '24',
112
+ })
113
+ }
114
+ ```
115
+
116
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Success.tsx)
117
+
118
+ ### Disabled
119
+
120
+ ```tsx
121
+ import { OtpField, style } from '@tempoxyz/ds'
122
+
123
+ export function Demo() {
124
+ return (
125
+ <div {...styles.column()}>
126
+ <OtpField.Root aria-label="Disabled code" defaultValue="123" disabled length={6} />
127
+ </div>
128
+ )
129
+ }
130
+
131
+ namespace styles {
132
+ export const column = style({
133
+ alignItems: 'flex-start',
134
+ display: 'flex',
135
+ flexDirection: 'column',
136
+ gap: '24',
137
+ })
138
+ }
139
+ ```
140
+
141
+ [View example source](https://github.com/tempoxyz/ds/blob/main/docs/src/examples/demos/OtpField/Disabled.tsx)
142
+
143
+ ## Anatomy
144
+
145
+ Import the component and assemble its parts.
146
+
147
+ ```tsx
148
+ import { OtpField } from '@tempoxyz/ds'
149
+
150
+ <OtpField.Root>
151
+ <OtpField.Input />
152
+ </OtpField.Root>
153
+ ```
154
+
155
+ ## Guidelines
156
+
157
+ ### When to Use
158
+
159
+ * Verify an email address or phone number with a code sent to it.
160
+ * Confirm a sensitive action with an authenticator code, such as a new API key or withdrawal address.
161
+ * Enter a fixed-length backup code. If it mixes letters and digits, set `validationType="alphanumeric"`.
162
+
163
+ ### When Not to Use
164
+
165
+ * Use [TextInput](/docs/components/text-input) for codes without a fixed length, such as invite or promo codes.
166
+ * Use [TextInput](/docs/components/text-input) with `type="password"` for passwords.
167
+ * Use [SecretValue](/docs/components/secret-value) to show a key or recovery phrase.
168
+
169
+ ### Support Autofill and Paste
170
+
171
+ A paste drops spaces and rejected characters, so 482 913 and 482-913 both work. Some people can't type the code at all.
172
+
173
+ * Set `length` to the exact length of the code.
174
+ * End SMS messages with an origin-bound line, such as `@wallet.tempo.xyz #482913`. Supporting browsers then offer the code only on your domain.
175
+ * Use `onValueInvalid` to report characters dropped from pasted text.
176
+
177
+ **Do:** Use one OtpField, so autofill and paste fill every slot.
178
+
179
+ ```tsx
180
+ /**
181
+ * Do example for "Support Autofill and Paste" on the OtpField page.
182
+ * Use one OtpField, so autofill and paste fill every slot.
183
+ * @module
184
+ */
185
+ import { Badge, Field, OtpField, style } from '@tempoxyz/ds'
186
+
187
+ /** Renders the static preview, which the guideline card shows inert. */
188
+ export function Demo() {
189
+ return (
190
+ <Field.Root {...styles.field()}>
191
+ <Field.Label>Verification code</Field.Label>
192
+ <OtpField.Root defaultValue="482913" length={6} />
193
+ <span {...styles.suggestion()}>
194
+ <Badge size="small">From Messages: 482913</Badge>
195
+ </span>
196
+ </Field.Root>
197
+ )
198
+ }
199
+
200
+ namespace styles {
201
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
202
+
203
+ export const suggestion = style({ display: 'flex' })
204
+ }
205
+ ```
206
+
207
+ **Don't:** Split the code across separate inputs or intercept paste.
208
+
209
+ ```tsx
210
+ /**
211
+ * Don't example for "Support Autofill and Paste" on the OtpField page.
212
+ * Don't split the code across separate inputs or intercept paste.
213
+ * @module
214
+ */
215
+ import { Field, style, TextInput } from '@tempoxyz/ds'
216
+
217
+ const digits = ['4', '8', '2', '9', '1', '3']
218
+
219
+ /** Renders the static preview, which the guideline card shows inert. */
220
+ export function Demo() {
221
+ return (
222
+ <Field.Root {...styles.field()}>
223
+ <Field.Label>Verification code</Field.Label>
224
+ <div {...styles.row()}>
225
+ {digits.map((digit, index) => (
226
+ <TextInput.Group key={index} {...styles.slot()}>
227
+ <TextInput.Control
228
+ aria-label={`Digit ${index + 1}`}
229
+ defaultValue={digit}
230
+ maxLength={1}
231
+ />
232
+ </TextInput.Group>
233
+ ))}
234
+ </div>
235
+ </Field.Root>
236
+ )
237
+ }
238
+
239
+ namespace styles {
240
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
241
+
242
+ export const row = style({ display: 'flex', gap: '8' })
243
+
244
+ export const slot = style({ minWidth: '0 !custom', width: '2.5rem !custom' })
245
+ }
246
+ ```
247
+
248
+ ### Say Where the Code Went
249
+
250
+ Name the destination and the expiry in `Field.Description`.
251
+
252
+ **Do:** Say where you sent the code and when it expires.
253
+
254
+ ```tsx
255
+ /**
256
+ * Do example for "Say Where the Code Went" on the OtpField page.
257
+ * Say where you sent the code and when it expires.
258
+ * @module
259
+ */
260
+ import { Field, OtpField, style } from '@tempoxyz/ds'
261
+
262
+ /** Renders the static preview, which the guideline card shows inert. */
263
+ export function Demo() {
264
+ return (
265
+ <Field.Root {...styles.field()}>
266
+ <Field.Label>Verification code</Field.Label>
267
+ <OtpField.Root length={6} />
268
+ <Field.Description>Sent to alice@example.com. It expires in 10 minutes.</Field.Description>
269
+ </Field.Root>
270
+ )
271
+ }
272
+
273
+ namespace styles {
274
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
275
+ }
276
+ ```
277
+
278
+ **Don't:** Ask for a code without saying where to find it.
279
+
280
+ ```tsx
281
+ /**
282
+ * Don't example for "Say Where the Code Went" on the OtpField page.
283
+ * Don't ask for a code without saying where to find it.
284
+ * @module
285
+ */
286
+ import { Field, OtpField, style } from '@tempoxyz/ds'
287
+
288
+ /** Renders the static preview, which the guideline card shows inert. */
289
+ export function Demo() {
290
+ return (
291
+ <Field.Root {...styles.field()}>
292
+ <Field.Label>Verification code</Field.Label>
293
+ <OtpField.Root length={6} />
294
+ <Field.Description>Enter the code.</Field.Description>
295
+ </Field.Root>
296
+ )
297
+ }
298
+
299
+ namespace styles {
300
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
301
+ }
302
+ ```
303
+
304
+ ### Verify When the Code Is Complete
305
+
306
+ Start verification in `onValueComplete` when the last slot fills, or set `autoSubmit` to submit the owning form. A separate confirm step adds a click.
307
+
308
+ **Do:** Verify the code as soon as the last slot fills, and show progress below it.
309
+
310
+ ```tsx
311
+ /**
312
+ * Do example for "Verify When the Code Is Complete" on the OtpField page.
313
+ * Verify the code as soon as the last slot fills, and show progress below it.
314
+ * @module
315
+ */
316
+ import { Field, LoadingDots, OtpField, style } from '@tempoxyz/ds'
317
+
318
+ /** Renders the static preview, which the guideline card shows inert. */
319
+ export function Demo() {
320
+ return (
321
+ <Field.Root {...styles.field()}>
322
+ <Field.Label>Verification code</Field.Label>
323
+ <OtpField.Root defaultValue="482913" length={6} readOnly />
324
+ <Field.Description>
325
+ Verifying <LoadingDots aria-label="Verifying code" />
326
+ </Field.Description>
327
+ </Field.Root>
328
+ )
329
+ }
330
+
331
+ namespace styles {
332
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
333
+ }
334
+ ```
335
+
336
+ **Don't:** Add a Verify button after a complete code.
337
+
338
+ ```tsx
339
+ /**
340
+ * Don't example for "Verify When the Code Is Complete" on the OtpField page.
341
+ * Don't add a Verify button after a complete code.
342
+ * @module
343
+ */
344
+ import { Button, Field, OtpField, style } from '@tempoxyz/ds'
345
+
346
+ /** Renders the static preview, which the guideline card shows inert. */
347
+ export function Demo() {
348
+ return (
349
+ <div {...styles.column()}>
350
+ <Field.Root>
351
+ <Field.Label>Verification code</Field.Label>
352
+ <OtpField.Root defaultValue="482913" length={6} />
353
+ </Field.Root>
354
+ <Button>Verify code</Button>
355
+ </div>
356
+ )
357
+ }
358
+
359
+ namespace styles {
360
+ export const column = style({
361
+ alignItems: 'flex-start',
362
+ display: 'flex',
363
+ flexDirection: 'column',
364
+ gap: '16',
365
+ })
366
+ }
367
+ ```
368
+
369
+ Set `readOnly` while the request runs, with a [Spinner](/docs/components/spinner) or [LoadingDots](/docs/components/loading-dots) nearby. Then set `success` or `invalid`.
370
+
371
+ ### Explain a Rejected Code in Text
372
+
373
+ `invalid` only colors and nudges the slots. Add a `Field.Error` with the next step, and keep the entered code so people can fix a typo.
374
+
375
+ **Do:** Pair `invalid` with a message that says what to do.
376
+
377
+ ```tsx
378
+ /**
379
+ * Do example for "Explain a Rejected Code in Text" on the OtpField page.
380
+ * Pair `invalid` with a message that says what to do.
381
+ * @module
382
+ */
383
+ import { Field, OtpField, style } from '@tempoxyz/ds'
384
+
385
+ /** Renders the static preview, which the guideline card shows inert. */
386
+ export function Demo() {
387
+ return (
388
+ <Field.Root invalid {...styles.field()}>
389
+ <Field.Label>Verification code</Field.Label>
390
+ <OtpField.Root defaultValue="482913" invalid length={6} />
391
+ <Field.Error match>That code didn't match. Check your latest email.</Field.Error>
392
+ </Field.Root>
393
+ )
394
+ }
395
+
396
+ namespace styles {
397
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
398
+ }
399
+ ```
400
+
401
+ **Don't:** Rely on the red slots alone.
402
+
403
+ ```tsx
404
+ /**
405
+ * Don't example for "Explain a Rejected Code in Text" on the OtpField page.
406
+ * Don't rely on the red slots alone.
407
+ * @module
408
+ */
409
+ import { Field, OtpField, style } from '@tempoxyz/ds'
410
+
411
+ /** Renders the static preview, which the guideline card shows inert. */
412
+ export function Demo() {
413
+ return (
414
+ <Field.Root invalid {...styles.field()}>
415
+ <Field.Label>Verification code</Field.Label>
416
+ <OtpField.Root defaultValue="482913" invalid length={6} />
417
+ </Field.Root>
418
+ )
419
+ }
420
+
421
+ namespace styles {
422
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
423
+ }
424
+ ```
425
+
426
+ ### Offer a New Code
427
+
428
+ Place a `tertiary` [Button](/docs/components/button) such as Send a new code below the field. After a resend, disable it for a short cooldown and state when it returns.
429
+
430
+ **Do:** State when a disabled resend returns.
431
+
432
+ ```tsx
433
+ /** Do example for "Offer a New Code" on the OtpField page: state when a disabled resend returns. @module */
434
+ import { Button, Field, OtpField, style } from '@tempoxyz/ds'
435
+
436
+ /** Renders the static preview, which the guideline card shows inert. */
437
+ export function Demo() {
438
+ return (
439
+ <div {...styles.stack()}>
440
+ <Field.Root>
441
+ <Field.Label>Verification code</Field.Label>
442
+ <OtpField.Root length={6} />
443
+ <Field.Description>Sent to alice@example.com.</Field.Description>
444
+ </Field.Root>
445
+ <span {...styles.action()}>
446
+ <Button disabled size="small" variant="tertiary">
447
+ Send a new code in 30s
448
+ </Button>
449
+ </span>
450
+ </div>
451
+ )
452
+ }
453
+
454
+ namespace styles {
455
+ export const action = style({ display: 'flex' })
456
+
457
+ export const stack = style({
458
+ display: 'grid',
459
+ gap: '8',
460
+ maxWidth: '18rem !custom',
461
+ width: '100% !custom',
462
+ })
463
+ }
464
+ ```
465
+
466
+ **Don't:** Disable the resend button without saying why.
467
+
468
+ ```tsx
469
+ /**
470
+ * Don't example for "Offer a New Code" on the OtpField page.
471
+ * Don't disable the resend button without saying why.
472
+ * @module
473
+ */
474
+ import { Button, Field, OtpField, style } from '@tempoxyz/ds'
475
+
476
+ /** Renders the static preview, which the guideline card shows inert. */
477
+ export function Demo() {
478
+ return (
479
+ <div {...styles.stack()}>
480
+ <Field.Root>
481
+ <Field.Label>Verification code</Field.Label>
482
+ <OtpField.Root length={6} />
483
+ <Field.Description>Sent to alice@example.com.</Field.Description>
484
+ </Field.Root>
485
+ <span {...styles.action()}>
486
+ <Button disabled size="small" variant="tertiary">
487
+ Send a new code
488
+ </Button>
489
+ </span>
490
+ </div>
491
+ )
492
+ }
493
+
494
+ namespace styles {
495
+ export const action = style({ display: 'flex' })
496
+
497
+ export const stack = style({
498
+ display: 'grid',
499
+ gap: '8',
500
+ maxWidth: '18rem !custom',
501
+ width: '100% !custom',
502
+ })
503
+ }
504
+ ```
505
+
506
+ ### Leave Codes Visible
507
+
508
+ Short-lived codes are safe to show. Set `mask` only for codes that stay valid, such as a PIN, or on shared screens.
509
+
510
+ **Do:** Show a short-lived code, so people can check it against the message.
511
+
512
+ ```tsx
513
+ /**
514
+ * Do example for "Leave Codes Visible" on the OtpField page.
515
+ * Show a short-lived code, so people can check it against the message.
516
+ * @module
517
+ */
518
+ import { Field, OtpField, style } from '@tempoxyz/ds'
519
+
520
+ /** Renders the static preview, which the guideline card shows inert. */
521
+ export function Demo() {
522
+ return (
523
+ <Field.Root {...styles.field()}>
524
+ <Field.Label>Verification code</Field.Label>
525
+ <OtpField.Root defaultValue="482913" length={6} />
526
+ <Field.Description>It expires in 10 minutes.</Field.Description>
527
+ </Field.Root>
528
+ )
529
+ }
530
+
531
+ namespace styles {
532
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
533
+ }
534
+ ```
535
+
536
+ **Don't:** Mask a code that expires in minutes.
537
+
538
+ ```tsx
539
+ /**
540
+ * Don't example for "Leave Codes Visible" on the OtpField page.
541
+ * Don't mask a code that expires in minutes.
542
+ * @module
543
+ */
544
+ import { Field, OtpField, style } from '@tempoxyz/ds'
545
+
546
+ /** Renders the static preview, which the guideline card shows inert. */
547
+ export function Demo() {
548
+ return (
549
+ <Field.Root {...styles.field()}>
550
+ <Field.Label>Verification code</Field.Label>
551
+ <OtpField.Root defaultValue="482913" length={6} mask />
552
+ <Field.Description>It expires in 10 minutes.</Field.Description>
553
+ </Field.Root>
554
+ )
555
+ }
556
+
557
+ namespace styles {
558
+ export const field = style({ maxWidth: '18rem !custom', width: '100% !custom' })
559
+ }
560
+ ```
561
+
562
+ ## Content
563
+
564
+ * **Label:** Verification code, or the source, such as Authenticator code.
565
+ * **Term:** Write code, not OTP or one-time password.
566
+
567
+ ## Accessibility
568
+
569
+ OtpField renders a group of single-character text inputs.
570
+
571
+ ### Semantics
572
+
573
+ * **OtpField.Root (group):** Groups the slots into one control. Its `aria-label` names the group.
574
+ * **Slots (inputmode=numeric):** Each slot is a native text input with a numeric keyboard by default.
575
+ * **Autofill (one-time-code):** Set on the first slot, so browsers can offer a code from SMS or email.
576
+ * **Invalid and Success (aria-invalid):** `invalid` marks every slot invalid. `success` sets no ARIA state, so announce the result in text.
577
+ * **Disabled (disabled):** Disables every slot, so none of the slots takes focus or accepts input.
578
+
579
+ ### Keyboard
580
+
581
+ * **Enter the Field (Tab):** Moves focus to the active slot. Only one slot is in the tab order.
582
+ * **Type a Character:** Fills the slot and moves to the next one. Ignores characters that `validationType` rejects.
583
+ * **Delete Backward (Backspace):** Removes the previous character and moves back one slot.
584
+ * **Delete Forward (Delete):** Removes the character in the focused slot and shifts the rest left.
585
+ * **Move Between Slots (Left or Right Arrow):** Moves focus one slot at a time between filled slots and the first empty one.
586
+ * **Go to the First Slot (Home):** Moves focus to the first slot, whether or not the slots are filled.
587
+ * **Go to the End (End):** Moves to the first empty slot, or to the last slot when every slot is filled.
588
+ * **Paste a Code (Paste):** Fills the slots from the focused one and drops spaces and rejected characters.
589
+
590
+ ### Size and Motion
591
+
592
+ * **Slot Size (56px):** Slots are 56px tall and fill the field. The focused slot is 3px wider than the others, and a message in `Field.Root` sits 8px below them. Slots shrink to fit narrow screens instead of wrapping.
593
+ * **Reduced Motion (reduce):** If the reader prefers reduced motion, the nudge stops.
594
+
595
+ ### Your Responsibilities
596
+
597
+ * **Label the Slots (Field.Label):** Use a field label, which names the first slot. A root `aria-label` names only the group.
598
+ * **Announce the Result (output):** State the result in text beside the field, such as "Code accepted" or "Wrong code".
599
+
600
+ ## Related
601
+
602
+ [TextInput](/docs/components/text-input)
603
+
604
+ Use for variable-length codes.
605
+
606
+ [Field](/docs/components/field)
607
+
608
+ Use for labels, descriptions, and errors.
609
+
610
+ [SecretValue](/docs/components/secret-value)
611
+
612
+ Use for showing keys and secrets.
613
+
614
+ [Errors and Validation](/docs/patterns/errors-and-validation)
615
+
616
+ Covers explaining rejected values.