@neynar/ui 1.0.0 → 1.0.2
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.
- package/README.md +1 -1
- package/context7.json +17 -0
- package/llm/components/accordion.llm.md +205 -0
- package/llm/components/alert-dialog.llm.md +289 -0
- package/llm/components/alert.llm.md +310 -0
- package/llm/components/aspect-ratio.llm.md +110 -0
- package/llm/components/avatar.llm.md +282 -0
- package/llm/components/badge.llm.md +185 -0
- package/llm/components/blockquote.llm.md +86 -0
- package/llm/components/breadcrumb.llm.md +245 -0
- package/llm/components/button-group.llm.md +248 -0
- package/llm/components/button.llm.md +247 -0
- package/llm/components/calendar.llm.md +252 -0
- package/llm/components/card.llm.md +356 -0
- package/llm/components/carousel.llm.md +281 -0
- package/llm/components/chart.llm.md +278 -0
- package/llm/components/checkbox.llm.md +234 -0
- package/llm/components/code.llm.md +75 -0
- package/llm/components/collapsible.llm.md +271 -0
- package/llm/components/color-mode.llm.md +196 -0
- package/llm/components/combobox.llm.md +346 -0
- package/llm/components/command.llm.md +353 -0
- package/llm/components/context-menu.llm.md +368 -0
- package/llm/components/dialog.llm.md +283 -0
- package/llm/components/drawer.llm.md +326 -0
- package/llm/components/dropdown-menu.llm.md +404 -0
- package/llm/components/empty.llm.md +282 -0
- package/llm/components/field.llm.md +303 -0
- package/llm/components/first-light.llm.md +129 -0
- package/llm/components/hover-card.llm.md +278 -0
- package/llm/components/input-group.llm.md +334 -0
- package/llm/components/input-otp.llm.md +270 -0
- package/llm/components/input.llm.md +197 -0
- package/llm/components/item.llm.md +347 -0
- package/llm/components/kbd.llm.md +221 -0
- package/llm/components/label.llm.md +219 -0
- package/llm/components/menubar.llm.md +378 -0
- package/llm/components/navigation-menu.llm.md +320 -0
- package/llm/components/pagination.llm.md +337 -0
- package/llm/components/popover.llm.md +278 -0
- package/llm/components/progress.llm.md +259 -0
- package/llm/components/radio-group.llm.md +269 -0
- package/llm/components/resizable.llm.md +222 -0
- package/llm/components/scroll-area.llm.md +290 -0
- package/llm/components/select.llm.md +338 -0
- package/llm/components/separator.llm.md +129 -0
- package/llm/components/sheet.llm.md +275 -0
- package/llm/components/sidebar.llm.md +528 -0
- package/llm/components/skeleton.llm.md +140 -0
- package/llm/components/slider.llm.md +213 -0
- package/llm/components/sonner.llm.md +299 -0
- package/llm/components/spinner.llm.md +187 -0
- package/llm/components/switch.llm.md +258 -0
- package/llm/components/table.llm.md +334 -0
- package/llm/components/tabs.llm.md +245 -0
- package/llm/components/text.llm.md +108 -0
- package/llm/components/textarea.llm.md +236 -0
- package/llm/components/title.llm.md +88 -0
- package/llm/components/toggle-group.llm.md +228 -0
- package/llm/components/toggle.llm.md +235 -0
- package/llm/components/tooltip.llm.md +191 -0
- package/llm/contributing.llm.md +273 -0
- package/llm/hooks.llm.md +91 -0
- package/llm/index.llm.md +178 -0
- package/llm/theming.llm.md +381 -0
- package/llm/utilities.llm.md +97 -0
- package/llms-full.txt +15995 -0
- package/llms.txt +182 -0
- package/package.json +6 -2
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# InputGroup
|
|
2
|
+
|
|
3
|
+
Compound component for adding icons, text, buttons, or keyboard shortcuts to inputs and textareas.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
InputGroup,
|
|
10
|
+
InputGroupAddon,
|
|
11
|
+
InputGroupButton,
|
|
12
|
+
InputGroupInput,
|
|
13
|
+
InputGroupText,
|
|
14
|
+
InputGroupTextarea,
|
|
15
|
+
} from "@neynar/ui/input-group"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Anatomy
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<InputGroup>
|
|
22
|
+
<InputGroupAddon align="inline-start">
|
|
23
|
+
<SearchIcon />
|
|
24
|
+
</InputGroupAddon>
|
|
25
|
+
<InputGroupInput placeholder="Search..." />
|
|
26
|
+
<InputGroupAddon align="inline-end">
|
|
27
|
+
<InputGroupButton size="icon-xs">
|
|
28
|
+
<XIcon />
|
|
29
|
+
</InputGroupButton>
|
|
30
|
+
</InputGroupAddon>
|
|
31
|
+
</InputGroup>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Components
|
|
35
|
+
|
|
36
|
+
| Component | Description |
|
|
37
|
+
|-----------|-------------|
|
|
38
|
+
| InputGroup | Root container that manages unified border, focus ring, and validation states |
|
|
39
|
+
| InputGroupInput | Input element without its own border/ring (integrated with group) |
|
|
40
|
+
| InputGroupTextarea | Textarea element without its own border/ring (integrated with group) |
|
|
41
|
+
| InputGroupAddon | Container for addon content (icons, text, buttons) at start/end |
|
|
42
|
+
| InputGroupButton | Button sized for use within addons |
|
|
43
|
+
| InputGroupText | Text label/prefix/suffix for addons (e.g., "https://", "USD") |
|
|
44
|
+
|
|
45
|
+
## Props
|
|
46
|
+
|
|
47
|
+
### InputGroup
|
|
48
|
+
|
|
49
|
+
Root container. Extends standard `<div>` props.
|
|
50
|
+
|
|
51
|
+
| Prop | Type | Default | Description |
|
|
52
|
+
|------|------|---------|-------------|
|
|
53
|
+
| className | string | - | Additional CSS classes |
|
|
54
|
+
| data-disabled | boolean | - | Disables addon opacity when set to "true" |
|
|
55
|
+
|
|
56
|
+
### InputGroupAddon
|
|
57
|
+
|
|
58
|
+
| Prop | Type | Default | Description |
|
|
59
|
+
|------|------|---------|-------------|
|
|
60
|
+
| align | "inline-start" \| "inline-end" \| "block-start" \| "block-end" | "inline-start" | Position of addon relative to input |
|
|
61
|
+
| className | string | - | Additional CSS classes |
|
|
62
|
+
|
|
63
|
+
Clicking the addon automatically focuses the input (unless clicking a button inside).
|
|
64
|
+
|
|
65
|
+
### InputGroupButton
|
|
66
|
+
|
|
67
|
+
Extends Button component props with custom sizes.
|
|
68
|
+
|
|
69
|
+
| Prop | Type | Default | Description |
|
|
70
|
+
|------|------|---------|-------------|
|
|
71
|
+
| size | "xs" \| "sm" \| "icon-xs" \| "icon-sm" | "xs" | Button size optimized for input groups |
|
|
72
|
+
| variant | ButtonVariant | "ghost" | Button visual style |
|
|
73
|
+
| type | "button" \| "submit" \| "reset" | "button" | HTML button type |
|
|
74
|
+
| className | string | - | Additional CSS classes |
|
|
75
|
+
|
|
76
|
+
### InputGroupText
|
|
77
|
+
|
|
78
|
+
Extends standard `<span>` props. Use for text prefixes/suffixes like "https://", "@", "/", "USD", "MB".
|
|
79
|
+
|
|
80
|
+
### InputGroupInput
|
|
81
|
+
|
|
82
|
+
Extends standard `<input>` props. Inherits all Input component functionality but without its own border/focus ring.
|
|
83
|
+
|
|
84
|
+
### InputGroupTextarea
|
|
85
|
+
|
|
86
|
+
Extends standard `<textarea>` props. Inherits all Textarea component functionality but without its own border/focus ring.
|
|
87
|
+
|
|
88
|
+
## Alignment Options
|
|
89
|
+
|
|
90
|
+
| Align | Position | Use Case |
|
|
91
|
+
|-------|----------|----------|
|
|
92
|
+
| inline-start | Left side (horizontal) | Icons, text prefixes, leading buttons |
|
|
93
|
+
| inline-end | Right side (horizontal) | Icons, text suffixes, action buttons |
|
|
94
|
+
| block-start | Top (vertical) | Labels above input |
|
|
95
|
+
| block-end | Bottom (vertical) | Help text or actions below input |
|
|
96
|
+
|
|
97
|
+
## Button Sizes
|
|
98
|
+
|
|
99
|
+
| Size | Dimensions | Use Case |
|
|
100
|
+
|------|------------|----------|
|
|
101
|
+
| xs | h-6 (24px) | Text buttons with labels |
|
|
102
|
+
| sm | Standard height | Larger text buttons |
|
|
103
|
+
| icon-xs | 24x24px | Small icon-only buttons |
|
|
104
|
+
| icon-sm | 32x32px | Medium icon-only buttons |
|
|
105
|
+
|
|
106
|
+
## Data Attributes
|
|
107
|
+
|
|
108
|
+
| Attribute | When Present |
|
|
109
|
+
|-----------|--------------|
|
|
110
|
+
| data-slot="input-group" | On root container |
|
|
111
|
+
| data-slot="input-group-addon" | On addon containers |
|
|
112
|
+
| data-slot="input-group-control" | On input/textarea |
|
|
113
|
+
| data-align | Shows alignment value on addons |
|
|
114
|
+
| data-disabled="true" | When group is disabled (reduces addon opacity) |
|
|
115
|
+
| aria-invalid="true" | When input is invalid (shows destructive ring) |
|
|
116
|
+
|
|
117
|
+
## Examples
|
|
118
|
+
|
|
119
|
+
### Icon Addons
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
// Icon at start
|
|
123
|
+
<InputGroup>
|
|
124
|
+
<InputGroupAddon align="inline-start">
|
|
125
|
+
<SearchIcon />
|
|
126
|
+
</InputGroupAddon>
|
|
127
|
+
<InputGroupInput placeholder="Search..." />
|
|
128
|
+
</InputGroup>
|
|
129
|
+
|
|
130
|
+
// Icon at end
|
|
131
|
+
<InputGroup>
|
|
132
|
+
<InputGroupInput placeholder="Select date..." />
|
|
133
|
+
<InputGroupAddon align="inline-end">
|
|
134
|
+
<CalendarIcon />
|
|
135
|
+
</InputGroupAddon>
|
|
136
|
+
</InputGroup>
|
|
137
|
+
|
|
138
|
+
// Both sides
|
|
139
|
+
<InputGroup>
|
|
140
|
+
<InputGroupAddon align="inline-start">
|
|
141
|
+
<SearchIcon />
|
|
142
|
+
</InputGroupAddon>
|
|
143
|
+
<InputGroupInput placeholder="Search..." />
|
|
144
|
+
<InputGroupAddon align="inline-end">
|
|
145
|
+
<XIcon />
|
|
146
|
+
</InputGroupAddon>
|
|
147
|
+
</InputGroup>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Text Addons
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// URL prefix
|
|
154
|
+
<InputGroup>
|
|
155
|
+
<InputGroupAddon align="inline-start">
|
|
156
|
+
<InputGroupText>https://</InputGroupText>
|
|
157
|
+
</InputGroupAddon>
|
|
158
|
+
<InputGroupInput placeholder="example.com" />
|
|
159
|
+
</InputGroup>
|
|
160
|
+
|
|
161
|
+
// Currency suffix
|
|
162
|
+
<InputGroup>
|
|
163
|
+
<InputGroupInput type="number" placeholder="0.00" />
|
|
164
|
+
<InputGroupAddon align="inline-end">
|
|
165
|
+
<InputGroupText>USD</InputGroupText>
|
|
166
|
+
</InputGroupAddon>
|
|
167
|
+
</InputGroup>
|
|
168
|
+
|
|
169
|
+
// Username format
|
|
170
|
+
<InputGroup>
|
|
171
|
+
<InputGroupAddon align="inline-start">
|
|
172
|
+
<InputGroupText>@</InputGroupText>
|
|
173
|
+
</InputGroupAddon>
|
|
174
|
+
<InputGroupInput placeholder="username" />
|
|
175
|
+
<InputGroupAddon align="inline-end">
|
|
176
|
+
<InputGroupText>.eth</InputGroupText>
|
|
177
|
+
</InputGroupAddon>
|
|
178
|
+
</InputGroup>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Button Addons
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// Clear button
|
|
185
|
+
<InputGroup>
|
|
186
|
+
<InputGroupAddon align="inline-start">
|
|
187
|
+
<SearchIcon />
|
|
188
|
+
</InputGroupAddon>
|
|
189
|
+
<InputGroupInput placeholder="Search..." value={query} />
|
|
190
|
+
<InputGroupAddon align="inline-end">
|
|
191
|
+
<InputGroupButton size="icon-xs" aria-label="Clear" onClick={handleClear}>
|
|
192
|
+
<XIcon />
|
|
193
|
+
</InputGroupButton>
|
|
194
|
+
</InputGroupAddon>
|
|
195
|
+
</InputGroup>
|
|
196
|
+
|
|
197
|
+
// Copy button
|
|
198
|
+
<InputGroup>
|
|
199
|
+
<InputGroupInput value="neynar_sk_prod_abc123" readOnly className="font-mono text-sm" />
|
|
200
|
+
<InputGroupAddon align="inline-end">
|
|
201
|
+
<InputGroupButton size="icon-xs" aria-label="Copy" onClick={handleCopy}>
|
|
202
|
+
{copied ? <CheckIcon /> : <CopyIcon />}
|
|
203
|
+
</InputGroupButton>
|
|
204
|
+
</InputGroupAddon>
|
|
205
|
+
</InputGroup>
|
|
206
|
+
|
|
207
|
+
// Action button with text
|
|
208
|
+
<InputGroup>
|
|
209
|
+
<InputGroupInput placeholder="Enter email..." />
|
|
210
|
+
<InputGroupAddon align="inline-end">
|
|
211
|
+
<InputGroupButton size="xs">Send</InputGroupButton>
|
|
212
|
+
</InputGroupAddon>
|
|
213
|
+
</InputGroup>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Multiple Buttons
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// Password with visibility toggle and copy
|
|
220
|
+
<InputGroup>
|
|
221
|
+
<InputGroupAddon align="inline-start">
|
|
222
|
+
<LockIcon />
|
|
223
|
+
</InputGroupAddon>
|
|
224
|
+
<InputGroupInput type={showPassword ? "text" : "password"} placeholder="••••••••" />
|
|
225
|
+
<InputGroupAddon align="inline-end">
|
|
226
|
+
<InputGroupButton size="icon-xs" aria-label="Toggle visibility" onClick={toggleVisibility}>
|
|
227
|
+
{showPassword ? <EyeOffIcon /> : <EyeIcon />}
|
|
228
|
+
</InputGroupButton>
|
|
229
|
+
<InputGroupButton size="icon-xs" aria-label="Copy" onClick={handleCopy}>
|
|
230
|
+
<CopyIcon />
|
|
231
|
+
</InputGroupButton>
|
|
232
|
+
</InputGroupAddon>
|
|
233
|
+
</InputGroup>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Complex Combinations
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
// API endpoint builder with multiple addons
|
|
240
|
+
<InputGroup>
|
|
241
|
+
<InputGroupAddon align="inline-start">
|
|
242
|
+
<InputGroupText>https://api.neynar.com/</InputGroupText>
|
|
243
|
+
</InputGroupAddon>
|
|
244
|
+
<InputGroupInput placeholder="v2/cast/conversation" />
|
|
245
|
+
<InputGroupAddon align="inline-end">
|
|
246
|
+
<InputGroupButton size="xs" variant="outline">
|
|
247
|
+
Test
|
|
248
|
+
</InputGroupButton>
|
|
249
|
+
<InputGroupButton size="icon-xs" aria-label="Copy">
|
|
250
|
+
<CopyIcon />
|
|
251
|
+
</InputGroupButton>
|
|
252
|
+
</InputGroupAddon>
|
|
253
|
+
</InputGroup>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### States
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
// Disabled
|
|
260
|
+
<InputGroup data-disabled="true">
|
|
261
|
+
<InputGroupAddon align="inline-start">
|
|
262
|
+
<SearchIcon />
|
|
263
|
+
</InputGroupAddon>
|
|
264
|
+
<InputGroupInput placeholder="Disabled..." disabled />
|
|
265
|
+
</InputGroup>
|
|
266
|
+
|
|
267
|
+
// Invalid/Error
|
|
268
|
+
<InputGroup>
|
|
269
|
+
<InputGroupAddon align="inline-start">
|
|
270
|
+
<SearchIcon />
|
|
271
|
+
</InputGroupAddon>
|
|
272
|
+
<InputGroupInput placeholder="Invalid input" aria-invalid="true" />
|
|
273
|
+
</InputGroup>
|
|
274
|
+
|
|
275
|
+
// Loading
|
|
276
|
+
<InputGroup>
|
|
277
|
+
<InputGroupAddon align="inline-start">
|
|
278
|
+
<Loader2Icon className="animate-spin" />
|
|
279
|
+
</InputGroupAddon>
|
|
280
|
+
<InputGroupInput placeholder="Loading..." />
|
|
281
|
+
</InputGroup>
|
|
282
|
+
|
|
283
|
+
// Read-only
|
|
284
|
+
<InputGroup>
|
|
285
|
+
<InputGroupAddon align="inline-start">
|
|
286
|
+
<LockIcon />
|
|
287
|
+
</InputGroupAddon>
|
|
288
|
+
<InputGroupInput value="neynar_sk_readonly" readOnly className="font-mono text-sm" />
|
|
289
|
+
<InputGroupAddon align="inline-end">
|
|
290
|
+
<InputGroupButton size="icon-xs" aria-label="Copy">
|
|
291
|
+
<CopyIcon />
|
|
292
|
+
</InputGroupButton>
|
|
293
|
+
</InputGroupAddon>
|
|
294
|
+
</InputGroup>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Keyboard
|
|
298
|
+
|
|
299
|
+
| Key | Action |
|
|
300
|
+
|-----|--------|
|
|
301
|
+
| Tab | Move focus to/from input |
|
|
302
|
+
| Click addon | Focus input (unless clicking button) |
|
|
303
|
+
|
|
304
|
+
Input and button keyboard interactions follow standard HTML behavior.
|
|
305
|
+
|
|
306
|
+
## Accessibility
|
|
307
|
+
|
|
308
|
+
- InputGroup uses `role="group"` for semantic grouping
|
|
309
|
+
- Clicking addon automatically focuses input for better UX
|
|
310
|
+
- Icon-only buttons require `aria-label` for screen readers
|
|
311
|
+
- Invalid state shows destructive border/ring when `aria-invalid="true"` is set
|
|
312
|
+
- Disabled state reduces addon opacity and disables interactive elements
|
|
313
|
+
- Focus ring appears on the entire group container, not individual inputs
|
|
314
|
+
|
|
315
|
+
## Styling Notes
|
|
316
|
+
|
|
317
|
+
### Icon Auto-Sizing
|
|
318
|
+
|
|
319
|
+
Icons within addons are automatically sized to 16px (`size-4`) unless a custom size class is applied.
|
|
320
|
+
|
|
321
|
+
### Focus Management
|
|
322
|
+
|
|
323
|
+
The InputGroup container manages the focus ring. Individual inputs/textareas have their borders and rings removed to integrate seamlessly.
|
|
324
|
+
|
|
325
|
+
### Validation States
|
|
326
|
+
|
|
327
|
+
Set `aria-invalid="true"` on InputGroupInput to show destructive border and ring on the entire group.
|
|
328
|
+
|
|
329
|
+
## Related
|
|
330
|
+
|
|
331
|
+
- **Input** - Standalone input without addons
|
|
332
|
+
- **Textarea** - Standalone textarea
|
|
333
|
+
- **Field** - Wrap InputGroup with label and description
|
|
334
|
+
- **Button** - Used within InputGroupButton
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# InputOTP
|
|
2
|
+
|
|
3
|
+
One-time password input with individual character slots for verification codes, 2FA, and authentication flows.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import {
|
|
9
|
+
InputOTP,
|
|
10
|
+
InputOTPGroup,
|
|
11
|
+
InputOTPSlot,
|
|
12
|
+
InputOTPSeparator,
|
|
13
|
+
} from "@neynar/ui/input-otp"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Anatomy
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<InputOTP maxLength={6} value={code} onChange={setCode}>
|
|
20
|
+
<InputOTPGroup>
|
|
21
|
+
<InputOTPSlot index={0} />
|
|
22
|
+
<InputOTPSlot index={1} />
|
|
23
|
+
<InputOTPSlot index={2} />
|
|
24
|
+
</InputOTPGroup>
|
|
25
|
+
<InputOTPSeparator />
|
|
26
|
+
<InputOTPGroup>
|
|
27
|
+
<InputOTPSlot index={3} />
|
|
28
|
+
<InputOTPSlot index={4} />
|
|
29
|
+
<InputOTPSlot index={5} />
|
|
30
|
+
</InputOTPGroup>
|
|
31
|
+
</InputOTP>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Components
|
|
35
|
+
|
|
36
|
+
| Component | Description |
|
|
37
|
+
|-----------|-------------|
|
|
38
|
+
| InputOTP | Root container managing OTP input state and behavior |
|
|
39
|
+
| InputOTPGroup | Groups multiple slots together, supports error styling |
|
|
40
|
+
| InputOTPSlot | Individual character slot displaying current value |
|
|
41
|
+
| InputOTPSeparator | Visual separator between groups (minus icon) |
|
|
42
|
+
|
|
43
|
+
## Props
|
|
44
|
+
|
|
45
|
+
### InputOTP
|
|
46
|
+
|
|
47
|
+
Built on the `input-otp` library. Accepts all standard input props plus:
|
|
48
|
+
|
|
49
|
+
| Prop | Type | Default | Description |
|
|
50
|
+
|------|------|---------|-------------|
|
|
51
|
+
| maxLength | number | - | Number of character slots (required) |
|
|
52
|
+
| value | string | - | Controlled value state |
|
|
53
|
+
| onChange | (value: string) => void | - | Called when value changes |
|
|
54
|
+
| pattern | string | - | Regex pattern to validate input (e.g., `^[0-9]+$`) |
|
|
55
|
+
| disabled | boolean | false | Disabled state |
|
|
56
|
+
| autoFocus | boolean | false | Auto-focus on mount |
|
|
57
|
+
| containerClassName | string | - | Additional classes for container |
|
|
58
|
+
| onComplete | () => void | - | Called when all slots are filled |
|
|
59
|
+
| inputMode | string | 'numeric' | Virtual keyboard type on mobile |
|
|
60
|
+
| pasteTransformer | (text: string) => string | - | Transform pasted text before applying |
|
|
61
|
+
|
|
62
|
+
### InputOTPGroup
|
|
63
|
+
|
|
64
|
+
Container for grouping slots. Set `aria-invalid={true}` to show error state.
|
|
65
|
+
|
|
66
|
+
| Prop | Type | Default | Description |
|
|
67
|
+
|------|------|---------|-------------|
|
|
68
|
+
| aria-invalid | boolean | - | Shows error styling when true |
|
|
69
|
+
| className | string | - | Additional classes |
|
|
70
|
+
|
|
71
|
+
### InputOTPSlot
|
|
72
|
+
|
|
73
|
+
| Prop | Type | Default | Description |
|
|
74
|
+
|------|------|---------|-------------|
|
|
75
|
+
| index | number | - | Zero-based slot position (required) |
|
|
76
|
+
| className | string | - | Additional classes |
|
|
77
|
+
|
|
78
|
+
Automatically displays character from context, active state, and animated caret.
|
|
79
|
+
|
|
80
|
+
### InputOTPSeparator
|
|
81
|
+
|
|
82
|
+
Visual separator with minus icon. Standard div props accepted.
|
|
83
|
+
|
|
84
|
+
## Data Attributes
|
|
85
|
+
|
|
86
|
+
### InputOTP
|
|
87
|
+
|
|
88
|
+
| Attribute | When Present |
|
|
89
|
+
|-----------|--------------|
|
|
90
|
+
| data-slot | Always "input-otp" |
|
|
91
|
+
|
|
92
|
+
### InputOTPSlot
|
|
93
|
+
|
|
94
|
+
| Attribute | When Present |
|
|
95
|
+
|-----------|--------------|
|
|
96
|
+
| data-slot | Always "input-otp-slot" |
|
|
97
|
+
| data-active | Slot is currently focused |
|
|
98
|
+
|
|
99
|
+
## Examples
|
|
100
|
+
|
|
101
|
+
### Basic 6-Digit Code
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { useState } from "react"
|
|
105
|
+
import {
|
|
106
|
+
InputOTP,
|
|
107
|
+
InputOTPGroup,
|
|
108
|
+
InputOTPSlot,
|
|
109
|
+
InputOTPSeparator,
|
|
110
|
+
} from "@neynar/ui/input-otp"
|
|
111
|
+
|
|
112
|
+
function TwoFactorAuth() {
|
|
113
|
+
const [code, setCode] = useState("")
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<InputOTP maxLength={6} value={code} onChange={setCode}>
|
|
117
|
+
<InputOTPGroup>
|
|
118
|
+
<InputOTPSlot index={0} />
|
|
119
|
+
<InputOTPSlot index={1} />
|
|
120
|
+
<InputOTPSlot index={2} />
|
|
121
|
+
</InputOTPGroup>
|
|
122
|
+
<InputOTPSeparator />
|
|
123
|
+
<InputOTPGroup>
|
|
124
|
+
<InputOTPSlot index={3} />
|
|
125
|
+
<InputOTPSlot index={4} />
|
|
126
|
+
<InputOTPSlot index={5} />
|
|
127
|
+
</InputOTPGroup>
|
|
128
|
+
</InputOTP>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Email Verification with Error State
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { useState } from "react"
|
|
137
|
+
import {
|
|
138
|
+
InputOTP,
|
|
139
|
+
InputOTPGroup,
|
|
140
|
+
InputOTPSlot,
|
|
141
|
+
InputOTPSeparator,
|
|
142
|
+
} from "@neynar/ui/input-otp"
|
|
143
|
+
import { Label } from "@neynar/ui/label"
|
|
144
|
+
import { Button } from "@neynar/ui/button"
|
|
145
|
+
|
|
146
|
+
function EmailVerification() {
|
|
147
|
+
const [code, setCode] = useState("")
|
|
148
|
+
const [error, setError] = useState(false)
|
|
149
|
+
|
|
150
|
+
const handleVerify = () => {
|
|
151
|
+
if (code !== "123456") {
|
|
152
|
+
setError(true)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<div className="space-y-3">
|
|
158
|
+
<Label htmlFor="email-code">
|
|
159
|
+
Enter the code sent to your email
|
|
160
|
+
</Label>
|
|
161
|
+
<InputOTP
|
|
162
|
+
id="email-code"
|
|
163
|
+
maxLength={6}
|
|
164
|
+
value={code}
|
|
165
|
+
onChange={setCode}
|
|
166
|
+
pattern="^[0-9]+$"
|
|
167
|
+
>
|
|
168
|
+
<InputOTPGroup aria-invalid={error}>
|
|
169
|
+
<InputOTPSlot index={0} />
|
|
170
|
+
<InputOTPSlot index={1} />
|
|
171
|
+
<InputOTPSlot index={2} />
|
|
172
|
+
</InputOTPGroup>
|
|
173
|
+
<InputOTPSeparator />
|
|
174
|
+
<InputOTPGroup aria-invalid={error}>
|
|
175
|
+
<InputOTPSlot index={3} />
|
|
176
|
+
<InputOTPSlot index={4} />
|
|
177
|
+
<InputOTPSlot index={5} />
|
|
178
|
+
</InputOTPGroup>
|
|
179
|
+
</InputOTP>
|
|
180
|
+
{error && (
|
|
181
|
+
<p className="text-destructive text-sm">
|
|
182
|
+
Invalid code. Please try again.
|
|
183
|
+
</p>
|
|
184
|
+
)}
|
|
185
|
+
<Button onClick={handleVerify} disabled={code.length !== 6}>
|
|
186
|
+
Verify
|
|
187
|
+
</Button>
|
|
188
|
+
</div>
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 4-Digit PIN
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
<InputOTP maxLength={4}>
|
|
197
|
+
<InputOTPGroup>
|
|
198
|
+
<InputOTPSlot index={0} />
|
|
199
|
+
<InputOTPSlot index={1} />
|
|
200
|
+
<InputOTPSlot index={2} />
|
|
201
|
+
<InputOTPSlot index={3} />
|
|
202
|
+
</InputOTPGroup>
|
|
203
|
+
</InputOTP>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 8-Digit Backup Code
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
<InputOTP maxLength={8}>
|
|
210
|
+
<InputOTPGroup>
|
|
211
|
+
<InputOTPSlot index={0} />
|
|
212
|
+
<InputOTPSlot index={1} />
|
|
213
|
+
<InputOTPSlot index={2} />
|
|
214
|
+
<InputOTPSlot index={3} />
|
|
215
|
+
</InputOTPGroup>
|
|
216
|
+
<InputOTPSeparator />
|
|
217
|
+
<InputOTPGroup>
|
|
218
|
+
<InputOTPSlot index={4} />
|
|
219
|
+
<InputOTPSlot index={5} />
|
|
220
|
+
<InputOTPSlot index={6} />
|
|
221
|
+
<InputOTPSlot index={7} />
|
|
222
|
+
</InputOTPGroup>
|
|
223
|
+
</InputOTP>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### With Paste Transformer
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
<InputOTP
|
|
230
|
+
maxLength={6}
|
|
231
|
+
pattern="^[0-9]+$"
|
|
232
|
+
pasteTransformer={(text) => text.replaceAll("-", "")}
|
|
233
|
+
>
|
|
234
|
+
<InputOTPGroup>
|
|
235
|
+
<InputOTPSlot index={0} />
|
|
236
|
+
<InputOTPSlot index={1} />
|
|
237
|
+
<InputOTPSlot index={2} />
|
|
238
|
+
<InputOTPSlot index={3} />
|
|
239
|
+
<InputOTPSlot index={4} />
|
|
240
|
+
<InputOTPSlot index={5} />
|
|
241
|
+
</InputOTPGroup>
|
|
242
|
+
</InputOTP>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Now users can paste "123-456" and it becomes "123456".
|
|
246
|
+
|
|
247
|
+
## Keyboard
|
|
248
|
+
|
|
249
|
+
| Key | Action |
|
|
250
|
+
|-----|--------|
|
|
251
|
+
| 0-9, A-Z | Enter character (if allowed by pattern) |
|
|
252
|
+
| Backspace | Delete previous character |
|
|
253
|
+
| Delete | Clear current slot |
|
|
254
|
+
| ArrowLeft | Move to previous slot |
|
|
255
|
+
| ArrowRight | Move to next slot |
|
|
256
|
+
| Cmd/Ctrl+V | Paste full code (transformed if pasteTransformer provided) |
|
|
257
|
+
|
|
258
|
+
## Accessibility
|
|
259
|
+
|
|
260
|
+
- Uses native input element for proper screen reader support
|
|
261
|
+
- Announces current slot and total slots to assistive technology
|
|
262
|
+
- Focus management handles keyboard navigation between slots
|
|
263
|
+
- Supports autocomplete="one-time-code" for iOS/Android SMS autofill
|
|
264
|
+
- Error state communicated via aria-invalid on groups
|
|
265
|
+
|
|
266
|
+
## Related
|
|
267
|
+
|
|
268
|
+
- [Label](./label.llm.md) - Form labels
|
|
269
|
+
- [Button](./button.llm.md) - Verification actions
|
|
270
|
+
- [Form](./form.llm.md) - Form integration
|