@reliability-design/web 1.4.5 → 1.4.7

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 (142) hide show
  1. package/CHANGELOG.md +211 -49
  2. package/dist/components/rel-accordion/README.md +132 -132
  3. package/dist/components/rel-alert/README.md +145 -141
  4. package/dist/components/rel-autocomplete/README.md +156 -61
  5. package/dist/components/rel-avatar/README.md +116 -130
  6. package/dist/components/rel-badge/README.md +98 -93
  7. package/dist/components/rel-breadcrumb/README.md +96 -102
  8. package/dist/components/rel-button/README.md +160 -130
  9. package/dist/components/rel-calendar/README.md +109 -109
  10. package/dist/components/rel-card/README.md +75 -94
  11. package/dist/components/rel-cardmedia/README.md +100 -127
  12. package/dist/components/rel-carousel/README.md +184 -166
  13. package/dist/components/rel-checkbox/README.md +93 -75
  14. package/dist/components/rel-chip/README.md +132 -121
  15. package/dist/components/rel-container/README.md +107 -99
  16. package/dist/components/rel-date-picker/README.md +200 -289
  17. package/dist/components/rel-divider/README.md +78 -100
  18. package/dist/components/rel-drawer/README.md +187 -200
  19. package/dist/components/rel-kbd/README.md +141 -151
  20. package/dist/components/rel-menu/README.md +178 -151
  21. package/dist/components/rel-menubar/README.md +218 -329
  22. package/dist/components/rel-otp/README.md +170 -440
  23. package/dist/components/rel-pagination/README.md +100 -424
  24. package/dist/components/rel-popover/README.md +203 -568
  25. package/dist/components/rel-progress/README.md +88 -110
  26. package/dist/components/rel-radio/README.md +131 -135
  27. package/dist/components/rel-range-picker/README.md +162 -0
  28. package/dist/components/rel-resizable/README.md +199 -140
  29. package/dist/components/rel-segmented-button/README.md +155 -141
  30. package/dist/components/rel-select/README.md +154 -93
  31. package/dist/components/rel-sheet/README.md +168 -224
  32. package/dist/components/rel-skeleton/README.md +103 -0
  33. package/dist/components/rel-slider/README.md +130 -135
  34. package/dist/components/rel-snackbar/README.md +162 -134
  35. package/dist/components/rel-stepper/README.md +158 -160
  36. package/dist/components/rel-switch/README.md +108 -102
  37. package/dist/components/rel-tab/README.md +171 -163
  38. package/dist/components/rel-textfield/README.md +159 -69
  39. package/dist/components/rel-time-picker/README.md +122 -157
  40. package/dist/components/rel-toast/README.md +157 -148
  41. package/dist/components/rel-tooltip/README.md +108 -103
  42. package/dist/components/rel-upload/README.md +220 -0
  43. package/dist/index.d.mts +1387 -350
  44. package/dist/index.d.ts +1387 -350
  45. package/dist/index.js +5863 -3600
  46. package/dist/index.js.map +1 -1
  47. package/dist/index.mjs +5864 -3592
  48. package/dist/index.mjs.map +1 -1
  49. package/dist/styles/vars.css +877 -866
  50. package/dist/{vars-XNMTSRVN.css → vars-JHTUGNRG.css} +877 -866
  51. package/package.json +4 -3
  52. package/dist/components/rel-accordion/rel-accordion.ts +0 -336
  53. package/dist/components/rel-alert/rel-alert.ts +0 -305
  54. package/dist/components/rel-autocomplete/rel-autocomplete.ts +0 -689
  55. package/dist/components/rel-avatar/rel-avatar.ts +0 -142
  56. package/dist/components/rel-badge/rel-badge.ts +0 -170
  57. package/dist/components/rel-breadcrumb/rel-breadcrumb.ts +0 -106
  58. package/dist/components/rel-button/rel-button.ts +0 -560
  59. package/dist/components/rel-calendar/index.ts +0 -1
  60. package/dist/components/rel-calendar/rel-calendar.ts +0 -519
  61. package/dist/components/rel-card/rel-card.ts +0 -88
  62. package/dist/components/rel-cardmedia/rel-cardmedia.ts +0 -88
  63. package/dist/components/rel-carousel/index.ts +0 -1
  64. package/dist/components/rel-carousel/rel-carousel.ts +0 -700
  65. package/dist/components/rel-chart/README.md +0 -286
  66. package/dist/components/rel-chart/index.ts +0 -1
  67. package/dist/components/rel-chart/rel-chart.ts +0 -754
  68. package/dist/components/rel-checkbox/rel-checkbox.ts +0 -382
  69. package/dist/components/rel-chip/rel-chip.ts +0 -472
  70. package/dist/components/rel-container/rel-container.ts +0 -95
  71. package/dist/components/rel-date-picker/date-format.test.ts +0 -43
  72. package/dist/components/rel-date-picker/date-format.ts +0 -47
  73. package/dist/components/rel-date-picker/index.ts +0 -1
  74. package/dist/components/rel-date-picker/rel-date-picker.ts +0 -1237
  75. package/dist/components/rel-divider/rel-divider.ts +0 -54
  76. package/dist/components/rel-drawer/index.ts +0 -1
  77. package/dist/components/rel-drawer/rel-drawer-footer.ts +0 -27
  78. package/dist/components/rel-drawer/rel-drawer.ts +0 -604
  79. package/dist/components/rel-kbd/rel-kbd.ts +0 -188
  80. package/dist/components/rel-layout/rel-layout.ts +0 -49
  81. package/dist/components/rel-menu/rel-menu.ts +0 -535
  82. package/dist/components/rel-menubar/index.ts +0 -2
  83. package/dist/components/rel-menubar/rel-menubar-menu.css +0 -3
  84. package/dist/components/rel-menubar/rel-menubar-menu.ts +0 -17
  85. package/dist/components/rel-menubar/rel-menubar.ts +0 -777
  86. package/dist/components/rel-nav-menu/README.md +0 -289
  87. package/dist/components/rel-nav-menu/index.ts +0 -3
  88. package/dist/components/rel-nav-menu/rel-nav-menu-item.ts +0 -243
  89. package/dist/components/rel-nav-menu/rel-nav-menu-panel.ts +0 -72
  90. package/dist/components/rel-nav-menu/rel-nav-menu.ts +0 -270
  91. package/dist/components/rel-navbar/README.md +0 -126
  92. package/dist/components/rel-navbar/rel-navbar.ts +0 -105
  93. package/dist/components/rel-otp/index.ts +0 -1
  94. package/dist/components/rel-otp/rel-otp.ts +0 -517
  95. package/dist/components/rel-pagination/index.ts +0 -1
  96. package/dist/components/rel-pagination/rel-pagination.ts +0 -509
  97. package/dist/components/rel-popover/index.ts +0 -2
  98. package/dist/components/rel-popover/rel-popover-content.ts +0 -81
  99. package/dist/components/rel-popover/rel-popover.ts +0 -444
  100. package/dist/components/rel-progress/rel-progress.ts +0 -225
  101. package/dist/components/rel-radio/rel-radio.ts +0 -305
  102. package/dist/components/rel-range-picker/rel-range-picker.ts +0 -1331
  103. package/dist/components/rel-resizable/index.ts +0 -3
  104. package/dist/components/rel-resizable/rel-resizable-group.ts +0 -405
  105. package/dist/components/rel-resizable/rel-resizable-handle.ts +0 -150
  106. package/dist/components/rel-resizable/rel-resizable-panel.ts +0 -58
  107. package/dist/components/rel-segmented-button/rel-segmented-button.ts +0 -213
  108. package/dist/components/rel-select/rel-select.ts +0 -1385
  109. package/dist/components/rel-sheet/rel-sheet.ts +0 -465
  110. package/dist/components/rel-sidebar/README.md +0 -209
  111. package/dist/components/rel-sidebar/rel-sidebar.ts +0 -1185
  112. package/dist/components/rel-skeleton/rel-skeleton.ts +0 -134
  113. package/dist/components/rel-slider/rel-slider.ts +0 -670
  114. package/dist/components/rel-snackbar/rel-snackbar.ts +0 -325
  115. package/dist/components/rel-stepper/rel-stepper.ts +0 -565
  116. package/dist/components/rel-switch/rel-switch.ts +0 -197
  117. package/dist/components/rel-tab/rel-tab.ts +0 -264
  118. package/dist/components/rel-table/README.md +0 -178
  119. package/dist/components/rel-table/rel-table-body.ts +0 -22
  120. package/dist/components/rel-table/rel-table-caption.ts +0 -30
  121. package/dist/components/rel-table/rel-table-cell.ts +0 -43
  122. package/dist/components/rel-table/rel-table-footer.ts +0 -24
  123. package/dist/components/rel-table/rel-table-head.ts +0 -47
  124. package/dist/components/rel-table/rel-table-header.ts +0 -22
  125. package/dist/components/rel-table/rel-table-row.ts +0 -37
  126. package/dist/components/rel-table/rel-table.ts +0 -62
  127. package/dist/components/rel-textfield/rel-textfield.ts +0 -866
  128. package/dist/components/rel-time-picker/rel-time-picker.ts +0 -1126
  129. package/dist/components/rel-toast/rel-toast.ts +0 -204
  130. package/dist/components/rel-tooltip/rel-tooltip.ts +0 -190
  131. package/dist/components/rel-topbar/rel-topbar-actions.ts +0 -27
  132. package/dist/components/rel-topbar/rel-topbar-brand.ts +0 -54
  133. package/dist/components/rel-topbar/rel-topbar-dropdown-item.ts +0 -69
  134. package/dist/components/rel-topbar/rel-topbar-dropdown.ts +0 -31
  135. package/dist/components/rel-topbar/rel-topbar-item.ts +0 -324
  136. package/dist/components/rel-topbar/rel-topbar-mobile-panel.ts +0 -27
  137. package/dist/components/rel-topbar/rel-topbar-profile.ts +0 -243
  138. package/dist/components/rel-topbar/rel-topbar-utilities.ts +0 -27
  139. package/dist/components/rel-topbar/rel-topbar.ts +0 -446
  140. package/dist/components/rel-upload/UPLOAD-DESIGN-CHANGES.md +0 -134
  141. package/dist/components/rel-upload/rel-upload-actions.ts +0 -163
  142. package/dist/components/rel-upload/rel-upload.ts +0 -943
@@ -1,440 +1,170 @@
1
- # RelOtp Component
2
-
3
- An accessible one-time password (OTP) input component for the Reliability Design System.
4
-
5
- ## Features
6
-
7
- - Auto-advance on input
8
- - Smart backspace behavior (clear current, then move back)
9
- - Arrow key navigation (Left/Right, Home/End)
10
- - Paste support (distributes characters across cells)
11
- - Mobile OTP autofill support (iOS/Android)
12
- - Numeric or alphanumeric validation
13
- - Optional masking (password-style)
14
- - Visual grouping with separators
15
- - Form integration via hidden input
16
- - Token-driven styling
17
-
18
- ## Basic Usage
19
-
20
- ```html
21
- <!-- Simple 6-digit numeric OTP -->
22
- <rel-otp length="6" group-size="3"></rel-otp>
23
-
24
- <!-- Alphanumeric with masking -->
25
- <rel-otp
26
- length="8"
27
- type="alphanumeric"
28
- mask="true"
29
- group-size="4">
30
- </rel-otp>
31
-
32
- <!-- With form integration -->
33
- <form>
34
- <rel-otp
35
- name="otp_code"
36
- length="6"
37
- required
38
- aria-label="Enter verification code">
39
- </rel-otp>
40
- <button type="submit">Verify</button>
41
- </form>
42
- ```
43
-
44
- ## Properties
45
-
46
- | Property | Type | Default | Description |
47
- |----------|------|---------|-------------|
48
- | `value` | `string` | `""` | The concatenated OTP value (reflects) |
49
- | `length` | `number` | `6` | Total number of characters/digits |
50
- | `group-size` | `number` | `3` | Visual grouping size (0 = no grouping) |
51
- | `placeholder` | `string` | `""` | Placeholder for each cell |
52
- | `type` | `"numeric" \| "alphanumeric"` | `"numeric"` | Input validation type |
53
- | `mask` | `boolean` | `false` | Password-style masking |
54
- | `disabled` | `boolean` | `false` | Disables all inputs |
55
- | `readonly` | `boolean` | `false` | Makes inputs read-only |
56
- | `required` | `boolean` | `false` | HTML5 required attribute |
57
- | `name` | `string \| null` | `null` | Creates hidden input for forms |
58
- | `autocomplete` | `string` | `"one-time-code"` | Autocomplete attribute (mobile OTP) |
59
- | `inputmode` | `"numeric" \| "text" \| null` | `null` | Input mode (auto-detected from type) |
60
- | `pattern` | `string \| null` | `null` | HTML5 pattern attribute |
61
- | `aria-label` | `string \| null` | `null` | Label for the entire control |
62
- | `invalid` | `boolean` | `false` | Shows error styling |
63
- | `show-separator` | `boolean` | `true` | Show separator between groups |
64
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size variant |
65
-
66
- ## Events
67
-
68
- | Event | Detail | Description |
69
- |-------|--------|-------------|
70
- | `rel-change` | `{ value: string, complete: boolean }` | Fires on any value change |
71
- | `rel-complete` | `{ value: string }` | Fires when valid code is complete |
72
-
73
- ## Keyboard Behavior
74
-
75
- | Key | Action |
76
- |-----|--------|
77
- | `0-9` / `A-Z` | Enter character (based on type), auto-advance |
78
- | `Backspace` | Clear current cell, or move back and clear |
79
- | `Delete` | Clear current cell, stay in place |
80
- | `ArrowLeft` | Move to previous cell |
81
- | `ArrowRight` | Move to next cell |
82
- | `Home` | Jump to first cell |
83
- | `End` | Jump to last cell |
84
-
85
- ## Paste Behavior
86
-
87
- When you paste a value:
88
- - Only valid characters are accepted (based on `type`)
89
- - Characters distribute across cells starting from current position
90
- - Overflow beyond `length` is truncated
91
- - Focus moves to next empty cell (or last cell if full)
92
- - Triggers `rel-change` and `rel-complete` events
93
-
94
- Example: Pasting "123456" into a 6-digit OTP fills all cells.
95
-
96
- ## Examples
97
-
98
- ### Basic Numeric OTP (6 digits)
99
-
100
- ```html
101
- <rel-otp
102
- id="otp-basic"
103
- length="6"
104
- group-size="3">
105
- </rel-otp>
106
-
107
- <script>
108
- const otp = document.getElementById('otp-basic');
109
-
110
- otp.addEventListener('rel-change', (e) => {
111
- console.log('Value:', e.detail.value);
112
- console.log('Complete:', e.detail.complete);
113
- });
114
-
115
- otp.addEventListener('rel-complete', (e) => {
116
- console.log('OTP Complete:', e.detail.value);
117
- // Submit to server, etc.
118
- });
119
- </script>
120
- ```
121
-
122
- ### Alphanumeric with Custom Grouping
123
-
124
- ```html
125
- <rel-otp
126
- length="8"
127
- type="alphanumeric"
128
- group-size="4"
129
- aria-label="Enter 8-character code">
130
- </rel-otp>
131
- ```
132
-
133
- ### Masked OTP (Password Style)
134
-
135
- ```html
136
- <rel-otp
137
- length="6"
138
- mask="true"
139
- group-size="3"
140
- aria-label="Enter PIN">
141
- </rel-otp>
142
- ```
143
-
144
- ### Form Integration
145
-
146
- ```html
147
- <form id="verify-form">
148
- <label>Enter verification code:</label>
149
- <rel-otp
150
- name="verification_code"
151
- length="6"
152
- required
153
- invalid="false"
154
- id="otp-form">
155
- </rel-otp>
156
- <button type="submit">Verify</button>
157
- </form>
158
-
159
- <script>
160
- const form = document.getElementById('verify-form');
161
- const otpInput = document.getElementById('otp-form');
162
-
163
- form.addEventListener('submit', (e) => {
164
- e.preventDefault();
165
- const formData = new FormData(form);
166
- console.log('OTP:', formData.get('verification_code'));
167
- });
168
-
169
- // Show error on invalid attempt
170
- function showError() {
171
- otpInput.invalid = true;
172
- }
173
- </script>
174
- ```
175
-
176
- ### Programmatic Control
177
-
178
- ```javascript
179
- const otp = document.querySelector('rel-otp');
180
-
181
- // Set value programmatically
182
- otp.value = "123456";
183
-
184
- // Clear value
185
- otp.value = "";
186
-
187
- // Mark as invalid
188
- otp.invalid = true;
189
-
190
- // Disable
191
- otp.disabled = true;
192
- ```
193
-
194
- ### Without Separators
195
-
196
- ```html
197
- <rel-otp
198
- length="6"
199
- show-separator="false">
200
- </rel-otp>
201
- ```
202
-
203
- ### Different Sizes
204
-
205
- ```html
206
- <!-- Small -->
207
- <rel-otp size="sm" length="4"></rel-otp>
208
-
209
- <!-- Medium (default) -->
210
- <rel-otp size="md" length="6"></rel-otp>
211
-
212
- <!-- Large -->
213
- <rel-otp size="lg" length="6"></rel-otp>
214
- ```
215
-
216
- ## Mobile OTP Autofill
217
-
218
- The component supports native mobile OTP autofill on iOS and Android:
219
-
220
- - Sets `autocomplete="one-time-code"` on the first input
221
- - When the browser/OS injects the full code into the first cell, the component automatically distributes it across all cells
222
- - Works with SMS-based verification codes
223
-
224
- ```html
225
- <rel-otp
226
- length="6"
227
- autocomplete="one-time-code">
228
- </rel-otp>
229
- ```
230
-
231
- ## Accessibility
232
-
233
- - **Role**: The container has `role="group"` with a descriptive `aria-label`
234
- - **Cell Labels**: Each input has `aria-label` like "Digit 1 of 6"
235
- - **Input Mode**: Sets appropriate `inputmode` for mobile keyboards
236
- - `numeric` type → `inputmode="numeric"`
237
- - `alphanumeric` type → `inputmode="text"`
238
- - **Focus Management**: Auto-advances focus, keyboard navigation supported
239
- - **States**: `disabled`, `readonly`, `required`, and `invalid` states properly reflected
240
-
241
- ```html
242
- <rel-otp
243
- aria-label="Enter your 6-digit verification code"
244
- length="6">
245
- </rel-otp>
246
- ```
247
-
248
- ## Styling
249
-
250
- The OTP component uses design system tokens for all styling. Customize via CSS parts:
251
-
252
- ```css
253
- rel-otp::part(cell) {
254
- border-color: var(--rel-bg-info);
255
- }
256
-
257
- rel-otp::part(cell):focus {
258
- box-shadow: 0 0 0 3px rgba(74, 144, 226, 0.2);
259
- }
260
-
261
- rel-otp::part(separator) {
262
- color: var(--rel-bg-info-light);
263
- }
264
-
265
- /* Custom invalid styling */
266
- rel-otp[invalid]::part(cell) {
267
- border-color: var(--rel-text-danger);
268
- animation: shake 0.3s;
269
- }
270
-
271
- @keyframes shake {
272
- 0%, 100% { transform: translateX(0); }
273
- 25% { transform: translateX(-4px); }
274
- 75% { transform: translateX(4px); }
275
- }
276
- ```
277
-
278
- ## CSS Parts
279
-
280
- | Part | Description |
281
- |------|-------------|
282
- | `root` | Root container element |
283
- | `cell` | Individual input cell |
284
- | `separator` | Separator between groups |
285
- | `hidden-input` | Hidden input for form submission |
286
-
287
- ## Framework Integration
288
-
289
- ### React
290
-
291
- ```jsx
292
- import { RelOtp } from '@reliability-design/web';
293
-
294
- function OtpForm() {
295
- const [otp, setOtp] = useState('');
296
- const [isInvalid, setIsInvalid] = useState(false);
297
-
298
- const handleChange = (e) => {
299
- setOtp(e.detail.value);
300
- setIsInvalid(false);
301
- };
302
-
303
- const handleComplete = async (e) => {
304
- const code = e.detail.value;
305
- const valid = await verifyOtp(code);
306
- setIsInvalid(!valid);
307
- };
308
-
309
- return (
310
- <RelOtp
311
- length={6}
312
- onRelChange={handleChange}
313
- onRelComplete={handleComplete}
314
- invalid={isInvalid}
315
- />
316
- );
317
- }
318
- ```
319
-
320
- ### Angular
321
-
322
- ```html
323
- <rel-otp
324
- [length]="6"
325
- [group-size]="3"
326
- (rel-complete)="onOtpComplete($event)"
327
- [invalid]="isInvalid">
328
- </rel-otp>
329
- ```
330
-
331
- ```typescript
332
- export class OtpComponent {
333
- isInvalid = false;
334
-
335
- async onOtpComplete(event: CustomEvent) {
336
- const code = event.detail.value;
337
- const valid = await this.verifyOtp(code);
338
- this.isInvalid = !valid;
339
- }
340
- }
341
- ```
342
-
343
- ### Vue
344
-
345
- ```vue
346
- <template>
347
- <rel-otp
348
- :length="6"
349
- :group-size="3"
350
- :invalid="isInvalid"
351
- @rel-complete="handleComplete"
352
- />
353
- </template>
354
-
355
- <script setup>
356
- import { ref } from 'vue';
357
-
358
- const isInvalid = ref(false);
359
-
360
- async function handleComplete(event) {
361
- const code = event.detail.value;
362
- const valid = await verifyOtp(code);
363
- isInvalid.value = !valid;
364
- }
365
- </script>
366
- ```
367
-
368
- ## Validation
369
-
370
- The component provides basic character validation (numeric or alphanumeric) but does not validate the actual OTP code. Implement your own verification logic:
371
-
372
- ```javascript
373
- const otp = document.querySelector('rel-otp');
374
-
375
- otp.addEventListener('rel-complete', async (e) => {
376
- const code = e.detail.value;
377
-
378
- try {
379
- const response = await fetch('/api/verify-otp', {
380
- method: 'POST',
381
- body: JSON.stringify({ code }),
382
- headers: { 'Content-Type': 'application/json' }
383
- });
384
-
385
- if (response.ok) {
386
- // Success
387
- console.log('OTP verified!');
388
- } else {
389
- // Invalid
390
- otp.invalid = true;
391
- otp.value = ''; // Clear for retry
392
- }
393
- } catch (error) {
394
- console.error('Verification failed:', error);
395
- }
396
- });
397
- ```
398
-
399
- ## Common Patterns
400
-
401
- ### Resend Timer
402
-
403
- ```html
404
- <rel-otp id="otp" length="6"></rel-otp>
405
- <button id="resend" disabled>Resend code in <span id="timer">60</span>s</button>
406
-
407
- <script>
408
- let timeLeft = 60;
409
- const timer = document.getElementById('timer');
410
- const resendBtn = document.getElementById('resend');
411
-
412
- const countdown = setInterval(() => {
413
- timeLeft--;
414
- timer.textContent = timeLeft;
415
-
416
- if (timeLeft <= 0) {
417
- clearInterval(countdown);
418
- resendBtn.disabled = false;
419
- resendBtn.textContent = 'Resend code';
420
- }
421
- }, 1000);
422
- </script>
423
- ```
424
-
425
- ### Auto-Submit on Complete
426
-
427
- ```javascript
428
- const otp = document.querySelector('rel-otp');
429
-
430
- otp.addEventListener('rel-complete', async (e) => {
431
- // Show loading state
432
- otp.disabled = true;
433
-
434
- // Auto-submit
435
- await submitOtp(e.detail.value);
436
-
437
- // Re-enable if failed
438
- otp.disabled = false;
439
- });
440
- ```
1
+ # rel-otp
2
+
3
+ A one-time-password input implementing the Figma "OTP Input" cell: one single-character `<input>` per position, with auto-advance, smart backspace, paste distribution, mobile OTP autofill and optional group separators. The cells accept digits only (`input-mode="numeric"`, the default) or digits plus ASCII letters (`input-mode="alphanumeric"`), and can be masked password-style. Use it for SMS / authenticator verification codes and PINs; for free-form text use `rel-textfield`.
4
+
5
+ ## Usage
6
+
7
+ ```html
8
+ <!-- 6-digit numeric code in two groups of three -->
9
+ <rel-otp length="6" group-size="3"></rel-otp>
10
+
11
+ <!-- Alphanumeric, masked, four per group, custom accessible name -->
12
+ <rel-otp
13
+ length="8"
14
+ input-mode="alphanumeric"
15
+ mask
16
+ group-size="4"
17
+ aria-label="Enter 8-character code">
18
+ </rel-otp>
19
+
20
+ <!-- Sizes: small 32px / medium 40px (default) / large 48px cells -->
21
+ <rel-otp size="small" length="4"></rel-otp>
22
+ <rel-otp size="medium" length="6"></rel-otp>
23
+ <rel-otp size="large" length="6"></rel-otp>
24
+
25
+ <!-- Form integration: `name` renders a hidden input carrying the value -->
26
+ <form id="verify-form">
27
+ <rel-otp id="otp-form" name="verification_code" length="6" required></rel-otp>
28
+ <button type="submit">Verify</button>
29
+ </form>
30
+ <script>
31
+ const form = document.getElementById('verify-form');
32
+ const otp = document.getElementById('otp-form');
33
+
34
+ form.addEventListener('submit', (e) => {
35
+ e.preventDefault();
36
+ console.log('OTP:', new FormData(form).get('verification_code'));
37
+ });
38
+
39
+ otp.addEventListener('rel-change', (e) => {
40
+ console.log('Value:', e.detail.value, 'Complete:', e.detail.complete);
41
+ otp.invalid = false;
42
+ });
43
+
44
+ otp.addEventListener('rel-complete', async (e) => {
45
+ otp.disabled = true;
46
+ const ok = await verifyOtp(e.detail.value);
47
+ otp.disabled = false;
48
+ if (!ok) {
49
+ otp.invalid = true; // danger border + aria-invalid
50
+ otp.value = ''; // clear for retry
51
+ }
52
+ });
53
+ </script>
54
+
55
+ <!-- No separators: show-separator is a boolean attribute (presence = true),
56
+ so hide the dashes by setting the property -->
57
+ <rel-otp id="otp-plain" length="6"></rel-otp>
58
+ <script>
59
+ document.getElementById('otp-plain').showSeparator = false;
60
+ </script>
61
+
62
+ <!-- States -->
63
+ <rel-otp value="123456" readonly></rel-otp>
64
+ <rel-otp value="12" disabled></rel-otp>
65
+ <rel-otp invalid length="6"></rel-otp>
66
+ ```
67
+
68
+ ### Deprecated aliases
69
+
70
+ ```html
71
+ <!-- type= is a deprecated alias of input-mode; input-mode wins when both are set.
72
+ Logs once per page:
73
+ [rel-otp] "type" is deprecated and will be removed in 3.0 — use "input-mode" instead. It describes the accepted characters, not a semantic input type. -->
74
+ <rel-otp type="alphanumeric" length="8"></rel-otp>
75
+
76
+ <!-- sm / md / lg are deprecated aliases of size="small" / "medium" / "large" (no console notice) -->
77
+ <rel-otp size="lg" length="6"></rel-otp>
78
+ ```
79
+
80
+ <!-- readme-gen:start -->
81
+
82
+ <!-- Generated from rel-otp.ts — do not edit; run readme-generate.mjs -->
83
+
84
+ ## API
85
+
86
+ ### Attributes / Properties
87
+
88
+ | Property | Attribute | Type | Default | Description |
89
+ | --- | --- | --- | --- | --- |
90
+ | `value` | `value` | `string` | `""` | The concatenated code (all cells joined). Set it to fill the cells programmatically; it never exceeds `length` characters. Reflected. |
91
+ | `length` | `length` | `number` | `6` | Total number of cells (characters in the code). |
92
+ | `groupSize` | `group-size` | `number` | `3` | Cells per visual group; a separator renders between groups. `0` (or `show-separator` off) renders one ungrouped row. |
93
+ | `placeholder` | `placeholder` | `string` | `""` | Placeholder character shown in every empty cell. |
94
+ | `inputMode` | `input-mode` | `"numeric" \| "alphanumeric" \| ""` | `""` | Accepted character set (Trello 5). Describes what the field ACCEPTS, not a semantic input type — hence the name. Explicitly set it wins over the deprecated `type`; unset (`""`, like the native `inputMode` accessor it shadows on the host), `type` (default `"numeric"`) applies. Note the lowercase `inputmode` prop is a different thing: the raw native keyboard hint forwarded to each cell. |
95
+ | `mask` | `mask` | `boolean` | `false` | Renders the cells as `type="password"` so entered characters are masked. |
96
+ | `disabled` | `disabled` | `boolean` | `false` | Disables every cell and dims the control to 0.6 opacity. Reflected. |
97
+ | `readonly` | `readonly` | `boolean` | `false` | Makes every cell read-only: the value is shown but typing, paste and keyboard editing are ignored. Reflected. |
98
+ | `required` | `required` | `boolean` | `false` | Marks the first cell `required` for native form validation. |
99
+ | `name` | `name` | `string \| null` | `null` | When set, renders a hidden `<input name="…">` (part `hidden-input`) carrying `value` for native form submission. |
100
+ | `autocomplete` | `autocomplete` | `string` | `"one-time-code"` | Reserved autocomplete hint. Currently every cell always renders `autocomplete="one-time-code"` regardless of this value. |
101
+ | `inputmode` | `inputmode` | `"numeric" \| "text" \| null` | `null` | Raw native `inputmode` keyboard hint forwarded to every cell. Unset, it follows the accepted character set: `numeric` for numeric, `text` for alphanumeric. |
102
+ | `pattern` | `pattern` | `string \| null` | `null` | Native `pattern` attribute forwarded to every cell (per-character regex for form validation). |
103
+ | `ariaLabel` | `aria-label` | `string \| null` | `null` | Accessible name of the whole group; defaults to "One-time password input". Each cell is additionally labelled "Digit/Character N of M". |
104
+ | `invalid` | `invalid` | `boolean` | `false` | Error styling (danger border on every cell) and `aria-invalid="true"`. Reflected. |
105
+ | `showSeparator` | `show-separator` | `boolean` | `true` | Renders the dash separator between groups. Boolean attribute: its presence means true — set the property to `false` to hide separators. |
106
+ | `size` | `size` | `"small" \| "medium" \| "large" \| "sm" \| "md" \| "lg"` | `"medium"` | Cell size on the shared field scale (square, border-box): `small` 32px, `medium` 40px, `large` 48px (the Figma OTP Input cell). `sm` / `md` / `lg` are deprecated aliases of the full words. Reflected. |
107
+
108
+ #### Deprecated (still supported — removal targeted for 3.0)
109
+
110
+ | Property | Attribute | Type | Use instead |
111
+ | --- | --- | --- | --- |
112
+ | `type` | `type` | `"numeric" \| "alphanumeric"` | Deprecated alias of `inputMode` (attribute `input-mode`); use `input-mode` instead. Kept working in 1.x and logs a one-time console notice. |
113
+
114
+ ## Events
115
+
116
+ | Event | Detail | Bubbles / composed | Description |
117
+ | --- | --- | --- | --- |
118
+ | `rel-change` | `{ value: newValue, complete }` | bubbles / composed | Fired on every value change (typing, backspace, delete, paste). Detail: `{ value: string, complete: boolean }` — `complete` is true when all cells hold a valid character. |
119
+ | `rel-complete` | `{ value: newValue }` | bubbles / composed | Fired after `rel-change` once every cell holds a valid character. Detail: `{ value: string }` (the full code). |
120
+
121
+ ## Slots
122
+
123
+ | Slot | Description |
124
+ | --- | --- |
125
+ | — | This element has no slots. |
126
+
127
+ ## CSS Parts
128
+
129
+ | Part | Description |
130
+ | --- | --- |
131
+ | `base` | The `role="group"` root container (same element as `root`) |
132
+ | `root` | Alias of `base`, kept for existing `::part(root)` selectors |
133
+ | `input` | Each single-character cell input (same element as `cell`) |
134
+ | `cell` | Alias of `input`, kept for existing `::part(cell)` selectors |
135
+ | `separator` | The dash rendered between cell groups |
136
+ | `hidden-input` | The hidden form input rendered when `name` is set |
137
+
138
+ <!-- readme-gen:end -->
139
+
140
+ ## Accessibility
141
+
142
+ - The root renders `role="group"` with `aria-label` from the `aria-label` attribute (default "One-time password input").
143
+ - Every cell is a native `<input maxlength="1">` labelled "Digit N of M" (numeric) or "Character N of M" (alphanumeric), so screen readers announce the position while focus auto-advances.
144
+ - `invalid` sets `aria-invalid="true"` on every cell; `disabled`, `readonly` and `required` (first cell only) are the native attributes.
145
+ - The native `inputmode` hint on each cell follows the accepted character set — `numeric` for `input-mode="numeric"`, `text` for `alphanumeric` — unless the raw `inputmode` prop overrides it. Every cell carries `autocomplete="one-time-code"` so iOS / Android can inject an SMS code; a multi-character injection into any cell is distributed like a paste.
146
+ - Focus management: focusing a cell selects its content (replace-on-type); a pointer-down on a cell beyond the first empty one is redirected to that empty cell so the code cannot be entered out of order; after paste focus lands on the next empty cell (or the last cell when full).
147
+ - Live-region announcements (via the shared `announce()` utility): "Code complete" when every cell holds a valid character, and "N digits/characters entered" after a paste that does not fill the code.
148
+
149
+ | Key | Action |
150
+ | --- | --- |
151
+ | `0-9` (numeric) / `0-9` `A-Z` `a-z` (alphanumeric) | Enter the character and advance to the next cell; other characters are rejected |
152
+ | `Backspace` | Clear the current cell; if it is already empty, move to the previous cell and clear it |
153
+ | `Delete` | Clear the current cell and stay |
154
+ | `ArrowLeft` / `ArrowRight` | Move to the previous / next cell |
155
+ | `Home` / `End` | Jump to the first / last cell |
156
+ | `ArrowUp` / `ArrowDown` | Ignored (prevented so the page does not scroll) |
157
+ | `Ctrl/Cmd+V` | Paste: valid characters are distributed from the current cell onward, overflow past `length` is dropped |
158
+ | `Tab` / `Shift+Tab` | Move focus between cells (each cell is a tab stop) |
159
+
160
+ ## Notes / Edge Cases
161
+
162
+ - `value` is truncated to `length` characters when synced into the cells; changing `length` re-initialises the cells from `value`.
163
+ - `rel-change` fires on every edit; `rel-complete` fires right after it when all cells hold a valid character for the current mode — validate the code server-side, the component only checks the character set.
164
+ - `input-mode` unset (`""`) falls back to the deprecated `type` prop (default `"numeric"`); set `input-mode` explicitly and it always wins.
165
+ - The lowercase `inputmode` prop is the raw native keyboard hint, not the accepted character set — normally leave it unset.
166
+ - `show-separator` is a boolean attribute: `show-separator="false"` still enables separators. Hide them with `el.showSeparator = false` or `group-size="0"`.
167
+ - `required` is applied to the first cell only, so native form validation blocks submission when the code is empty but not when it is partial — check `e.detail.complete` yourself.
168
+ - The `autocomplete` prop currently has no effect: every cell always renders `autocomplete="one-time-code"`.
169
+ - `disabled` and `readonly` also suppress paste and keyboard handling; the value stays visible.
170
+ - Style cells via `::part(cell)` (alias `::part(input)`) and the root via `::part(base)` (alias `::part(root)`); the two names in each pair target the same element.