@neynar/ui 1.0.1 → 1.0.3

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 (68) hide show
  1. package/context7.json +17 -0
  2. package/llm/components/accordion.llm.md +205 -0
  3. package/llm/components/alert-dialog.llm.md +289 -0
  4. package/llm/components/alert.llm.md +310 -0
  5. package/llm/components/aspect-ratio.llm.md +110 -0
  6. package/llm/components/avatar.llm.md +282 -0
  7. package/llm/components/badge.llm.md +185 -0
  8. package/llm/components/blockquote.llm.md +86 -0
  9. package/llm/components/breadcrumb.llm.md +245 -0
  10. package/llm/components/button-group.llm.md +248 -0
  11. package/llm/components/button.llm.md +247 -0
  12. package/llm/components/calendar.llm.md +252 -0
  13. package/llm/components/card.llm.md +356 -0
  14. package/llm/components/carousel.llm.md +281 -0
  15. package/llm/components/chart.llm.md +278 -0
  16. package/llm/components/checkbox.llm.md +234 -0
  17. package/llm/components/code.llm.md +75 -0
  18. package/llm/components/collapsible.llm.md +271 -0
  19. package/llm/components/color-mode.llm.md +196 -0
  20. package/llm/components/combobox.llm.md +346 -0
  21. package/llm/components/command.llm.md +353 -0
  22. package/llm/components/context-menu.llm.md +368 -0
  23. package/llm/components/dialog.llm.md +283 -0
  24. package/llm/components/drawer.llm.md +326 -0
  25. package/llm/components/dropdown-menu.llm.md +404 -0
  26. package/llm/components/empty.llm.md +282 -0
  27. package/llm/components/field.llm.md +303 -0
  28. package/llm/components/first-light.llm.md +129 -0
  29. package/llm/components/hover-card.llm.md +278 -0
  30. package/llm/components/input-group.llm.md +334 -0
  31. package/llm/components/input-otp.llm.md +270 -0
  32. package/llm/components/input.llm.md +197 -0
  33. package/llm/components/item.llm.md +347 -0
  34. package/llm/components/kbd.llm.md +221 -0
  35. package/llm/components/label.llm.md +219 -0
  36. package/llm/components/menubar.llm.md +378 -0
  37. package/llm/components/navigation-menu.llm.md +320 -0
  38. package/llm/components/pagination.llm.md +337 -0
  39. package/llm/components/popover.llm.md +278 -0
  40. package/llm/components/progress.llm.md +259 -0
  41. package/llm/components/radio-group.llm.md +269 -0
  42. package/llm/components/resizable.llm.md +222 -0
  43. package/llm/components/scroll-area.llm.md +290 -0
  44. package/llm/components/select.llm.md +338 -0
  45. package/llm/components/separator.llm.md +129 -0
  46. package/llm/components/sheet.llm.md +275 -0
  47. package/llm/components/sidebar.llm.md +528 -0
  48. package/llm/components/skeleton.llm.md +140 -0
  49. package/llm/components/slider.llm.md +213 -0
  50. package/llm/components/sonner.llm.md +299 -0
  51. package/llm/components/spinner.llm.md +187 -0
  52. package/llm/components/switch.llm.md +258 -0
  53. package/llm/components/table.llm.md +334 -0
  54. package/llm/components/tabs.llm.md +245 -0
  55. package/llm/components/text.llm.md +108 -0
  56. package/llm/components/textarea.llm.md +236 -0
  57. package/llm/components/title.llm.md +88 -0
  58. package/llm/components/toggle-group.llm.md +228 -0
  59. package/llm/components/toggle.llm.md +235 -0
  60. package/llm/components/tooltip.llm.md +191 -0
  61. package/llm/contributing.llm.md +273 -0
  62. package/llm/hooks.llm.md +91 -0
  63. package/llm/index.llm.md +178 -0
  64. package/llm/theming.llm.md +381 -0
  65. package/llm/utilities.llm.md +97 -0
  66. package/llms-full.txt +15995 -0
  67. package/llms.txt +182 -0
  68. package/package.json +5 -1
@@ -0,0 +1,213 @@
1
+ # Slider
2
+
3
+ Slider for selecting single values or ranges with one or more draggable thumbs.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { Slider } from "@neynar/ui/slider"
9
+ ```
10
+
11
+ ## Anatomy
12
+
13
+ ```tsx
14
+ <Slider defaultValue={[50]} min={0} max={100} />
15
+ ```
16
+
17
+ ## Props
18
+
19
+ All props from Base UI Slider.Root are supported.
20
+
21
+ | Prop | Type | Default | Description |
22
+ |------|------|---------|-------------|
23
+ | defaultValue | number \| number[] | - | Uncontrolled initial value. Use array for ranges. |
24
+ | value | number \| number[] | - | Controlled value. Use array for ranges. |
25
+ | onValueChange | (value: number \| number[], eventDetails) => void | - | Called when value changes during interaction. |
26
+ | onValueCommitted | (value: number \| number[], eventDetails) => void | - | Called when interaction completes (pointerup). |
27
+ | min | number | 0 | Minimum value. |
28
+ | max | number | 100 | Maximum value. |
29
+ | step | number | 1 | Step increment for value changes. |
30
+ | largeStep | number | 10 | Step for Page Up/Down or Shift + Arrow keys. |
31
+ | minStepsBetweenValues | number | - | Minimum steps between thumbs in range slider. |
32
+ | disabled | boolean | false | Disables all interactions. |
33
+ | orientation | "horizontal" \| "vertical" | "horizontal" | Slider direction. |
34
+ | thumbAlignment | "center" \| "edge" \| "edge-client-only" | "edge" | How thumbs align at min/max positions. |
35
+ | thumbCollisionBehavior | "none" \| "push" \| "swap" | "push" | How thumbs interact when they collide. |
36
+ | name | string | - | Form field name for submission. |
37
+ | className | string | - | Additional classes for the control element. |
38
+
39
+ ### Value Format
40
+
41
+ - **Single value**: `defaultValue={[50]}` or `value={[50]}`
42
+ - **Range (two thumbs)**: `defaultValue={[25, 75]}`
43
+ - **Multiple thumbs**: `defaultValue={[20, 40, 60, 80]}`
44
+
45
+ Values are always arrays. Component automatically creates the correct number of thumbs based on array length.
46
+
47
+ ### Thumb Alignment
48
+
49
+ Set to `"edge"` by default (thumbs align to edges at min/max):
50
+ - `"center"` - Thumb centers align at min/max
51
+ - `"edge"` - Thumb edges align at min/max (default)
52
+ - `"edge-client-only"` - Like edge but only on client
53
+
54
+ ## Data Attributes
55
+
56
+ Applied to control element for styling:
57
+
58
+ | Attribute | When Present |
59
+ |-----------|--------------|
60
+ | data-disabled | Slider is disabled |
61
+ | data-horizontal | Orientation is horizontal |
62
+ | data-vertical | Orientation is vertical |
63
+
64
+ Applied to individual elements via `data-slot`:
65
+ - `data-slot="slider"` - Root element
66
+ - `data-slot="slider-track"` - Track element
67
+ - `data-slot="slider-range"` - Filled indicator
68
+ - `data-slot="slider-thumb"` - Draggable thumb(s)
69
+
70
+ ## Examples
71
+
72
+ ### Basic Single Value
73
+
74
+ ```tsx
75
+ function VolumeControl() {
76
+ return (
77
+ <div className="space-y-2">
78
+ <label>Volume</label>
79
+ <Slider defaultValue={[50]} min={0} max={100} />
80
+ </div>
81
+ )
82
+ }
83
+ ```
84
+
85
+ ### Controlled with Display
86
+
87
+ ```tsx
88
+ function RateLimitControl() {
89
+ const [value, setValue] = useState([60])
90
+
91
+ return (
92
+ <div className="space-y-2">
93
+ <div className="flex justify-between">
94
+ <label>Requests per minute</label>
95
+ <span className="font-mono">{value[0]}</span>
96
+ </div>
97
+ <Slider
98
+ value={value}
99
+ onValueChange={(v) => setValue(Array.isArray(v) ? [...v] : [v])}
100
+ min={10}
101
+ max={200}
102
+ step={10}
103
+ />
104
+ </div>
105
+ )
106
+ }
107
+ ```
108
+
109
+ ### Range Selection
110
+
111
+ ```tsx
112
+ function PriceRangeFilter() {
113
+ const [range, setRange] = useState([25, 75])
114
+
115
+ return (
116
+ <div className="space-y-2">
117
+ <label>Price Range: ${range[0]} - ${range[1]}</label>
118
+ <Slider
119
+ value={range}
120
+ onValueChange={(v) => setRange(Array.isArray(v) ? [...v] : [v])}
121
+ min={0}
122
+ max={100}
123
+ step={5}
124
+ />
125
+ </div>
126
+ )
127
+ }
128
+ ```
129
+
130
+ ### With Step Increments
131
+
132
+ ```tsx
133
+ <Slider
134
+ defaultValue={[50]}
135
+ min={0}
136
+ max={100}
137
+ step={25}
138
+ />
139
+ ```
140
+
141
+ ### Vertical Orientation
142
+
143
+ ```tsx
144
+ <div className="h-48">
145
+ <Slider
146
+ defaultValue={[50]}
147
+ min={0}
148
+ max={100}
149
+ orientation="vertical"
150
+ />
151
+ </div>
152
+ ```
153
+
154
+ ### Multiple Thresholds
155
+
156
+ ```tsx
157
+ function AlertThresholds() {
158
+ const [thresholds, setThresholds] = useState([25, 50, 75])
159
+
160
+ return (
161
+ <div className="space-y-2">
162
+ <label>Alert Levels: {thresholds.join(', ')}</label>
163
+ <Slider
164
+ value={thresholds}
165
+ onValueChange={(v) => setThresholds(Array.isArray(v) ? [...v] : [v])}
166
+ min={0}
167
+ max={100}
168
+ step={5}
169
+ />
170
+ </div>
171
+ )
172
+ }
173
+ ```
174
+
175
+ ### Disabled State
176
+
177
+ ```tsx
178
+ <Slider
179
+ defaultValue={[50]}
180
+ min={0}
181
+ max={100}
182
+ disabled
183
+ />
184
+ ```
185
+
186
+ ## Keyboard Navigation
187
+
188
+ | Key | Action |
189
+ |-----|--------|
190
+ | Arrow Left/Down | Decrease value by `step` |
191
+ | Arrow Right/Up | Increase value by `step` |
192
+ | Page Down | Decrease by `largeStep` (default 10) |
193
+ | Page Up | Increase by `largeStep` (default 10) |
194
+ | Shift + Arrow | Increase/decrease by `largeStep` |
195
+ | Home | Set to minimum value |
196
+ | End | Set to maximum value |
197
+ | Tab | Focus next thumb |
198
+ | Shift + Tab | Focus previous thumb |
199
+
200
+ ## Accessibility
201
+
202
+ - Uses ARIA `slider` role with proper attributes
203
+ - Announces current value, min, max, and orientation to screen readers
204
+ - Full keyboard navigation support
205
+ - Focus indicators on thumbs with hover/focus ring
206
+ - Disabled state prevents interaction and reduces opacity
207
+ - Touch-friendly with adequate thumb size (16px)
208
+
209
+ ## Related
210
+
211
+ - [Input](./input.llm.md) - For precise numeric entry
212
+ - [Select](./select.llm.md) - For discrete value selection
213
+ - [RadioGroup](./radio-group.llm.md) - For choosing one option
@@ -0,0 +1,299 @@
1
+ # Sonner (Toast Notifications)
2
+
3
+ Toast notification system built on Sonner with themed icons and automatic color system integration.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { Toaster, toast } from "@neynar/ui/sonner"
9
+ ```
10
+
11
+ ## Anatomy
12
+
13
+ ```tsx
14
+ // Add once to your app root
15
+ <Toaster position="bottom-right" />
16
+
17
+ // Trigger toasts anywhere in your app
18
+ toast.success("Saved successfully")
19
+ toast.error("Failed to save")
20
+ ```
21
+
22
+ ## Components
23
+
24
+ | Component | Description |
25
+ |-----------|-------------|
26
+ | Toaster | Container component rendered once at app root. Manages all toast notifications. |
27
+ | toast | Function API for triggering notifications. Includes type variants and promise handling. |
28
+
29
+ ## Props
30
+
31
+ ### Toaster
32
+
33
+ Pre-configured toast container with custom icons and theme integration. Renders at the specified position and manages all active toasts.
34
+
35
+ | Prop | Type | Default | Description |
36
+ |------|------|---------|-------------|
37
+ | position | "top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right" | "bottom-right" | Screen position for toasts |
38
+ | theme | "light" \| "dark" \| "system" | "system" | Color theme for toasts |
39
+ | richColors | boolean | true | Enhanced colors for toast variants |
40
+ | expand | boolean | false | Expand toasts on hover |
41
+ | visibleToasts | number | 3 | Maximum visible toasts at once |
42
+ | closeButton | boolean | false | Show close button on all toasts |
43
+ | duration | number | 4000 | Default duration in milliseconds |
44
+ | toastOptions | object | - | Global toast styling options |
45
+
46
+ ### toast Function API
47
+
48
+ Function for triggering toast notifications. Available variants:
49
+
50
+ ```tsx
51
+ // Basic toast
52
+ toast("Message")
53
+
54
+ // Type variants
55
+ toast.success("Success message")
56
+ toast.error("Error message")
57
+ toast.warning("Warning message")
58
+ toast.info("Info message")
59
+ toast.loading("Loading...")
60
+
61
+ // Promise handling
62
+ toast.promise(promise, {
63
+ loading: "Loading...",
64
+ success: "Success!",
65
+ error: "Failed"
66
+ })
67
+ ```
68
+
69
+ ### Toast Options
70
+
71
+ All toast functions accept an optional second parameter with these options:
72
+
73
+ | Option | Type | Description |
74
+ |--------|------|-------------|
75
+ | description | string | Secondary text below the title |
76
+ | duration | number | Override default duration (milliseconds) |
77
+ | icon | ReactNode | Custom icon (replaces default) |
78
+ | action | { label: string, onClick: () => void } | Primary action button |
79
+ | cancel | { label: string, onClick: () => void } | Secondary cancel button |
80
+ | id | string | Custom toast ID for updates |
81
+ | position | string | Override global position |
82
+ | dismissible | boolean | Allow manual dismissal (default: true) |
83
+ | onDismiss | () => void | Callback when dismissed |
84
+ | onAutoClose | () => void | Callback when auto-closed |
85
+
86
+ ## Customizations
87
+
88
+ ### Custom Icons
89
+
90
+ Default icons are automatically provided:
91
+ - Success: `CircleCheckIcon`
92
+ - Error: `OctagonXIcon`
93
+ - Warning: `TriangleAlertIcon`
94
+ - Info: `InfoIcon`
95
+ - Loading: `Loader2Icon` (animated)
96
+
97
+ Override with the `icon` option:
98
+
99
+ ```tsx
100
+ toast.success("Copied!", {
101
+ icon: <CopyIcon className="size-4" />
102
+ })
103
+ ```
104
+
105
+ ### Theme Integration
106
+
107
+ Toasts automatically integrate with your theme's CSS variables:
108
+ - Uses `--popover` colors for normal toasts
109
+ - Uses `--success`, `--destructive`, `--warning`, `--info` for variants
110
+ - Applies `--radius` for border radius
111
+ - Includes backdrop blur effect
112
+
113
+ ### Styling
114
+
115
+ Customize appearance via `toastOptions`:
116
+
117
+ ```tsx
118
+ <Toaster
119
+ toastOptions={{
120
+ classNames: {
121
+ toast: "custom-toast-class",
122
+ title: "custom-title-class",
123
+ description: "custom-description-class"
124
+ },
125
+ style: {
126
+ background: "var(--custom-bg)",
127
+ border: "1px solid var(--custom-border)"
128
+ }
129
+ }}
130
+ />
131
+ ```
132
+
133
+ ## Examples
134
+
135
+ ### Basic Usage
136
+
137
+ ```tsx
138
+ function SaveButton() {
139
+ const handleSave = async () => {
140
+ try {
141
+ await saveData()
142
+ toast.success("Data saved successfully")
143
+ } catch (error) {
144
+ toast.error("Failed to save data")
145
+ }
146
+ }
147
+
148
+ return <Button onClick={handleSave}>Save</Button>
149
+ }
150
+ ```
151
+
152
+ ### With Description
153
+
154
+ ```tsx
155
+ toast.error("Failed to save", {
156
+ description: "Please check your internet connection and try again"
157
+ })
158
+ ```
159
+
160
+ ### With Action Button
161
+
162
+ ```tsx
163
+ toast.success("File uploaded", {
164
+ description: "Your file has been uploaded successfully",
165
+ action: {
166
+ label: "View",
167
+ onClick: () => router.push("/files")
168
+ }
169
+ })
170
+ ```
171
+
172
+ ### With Both Actions
173
+
174
+ ```tsx
175
+ toast.success("Changes saved", {
176
+ description: "Do you want to publish these changes now?",
177
+ action: {
178
+ label: "Publish",
179
+ onClick: () => publishChanges()
180
+ },
181
+ cancel: {
182
+ label: "Later",
183
+ onClick: () => console.log("Cancelled")
184
+ }
185
+ })
186
+ ```
187
+
188
+ ### Promise Handling
189
+
190
+ ```tsx
191
+ function CreateButton() {
192
+ const handleCreate = () => {
193
+ const promise = createAPIKey()
194
+
195
+ toast.promise(promise, {
196
+ loading: "Creating API key...",
197
+ success: "API key created successfully",
198
+ error: "Failed to create API key"
199
+ })
200
+ }
201
+
202
+ return <Button onClick={handleCreate}>Create Key</Button>
203
+ }
204
+ ```
205
+
206
+ ### Promise with Dynamic Messages
207
+
208
+ ```tsx
209
+ toast.promise(fetchUser(), {
210
+ loading: "Loading user...",
211
+ success: (data) => ({
212
+ message: `Welcome ${data.name}!`,
213
+ description: data.email,
214
+ duration: 5000
215
+ }),
216
+ error: (error) => ({
217
+ message: "Failed to load user",
218
+ description: error.message
219
+ })
220
+ })
221
+ ```
222
+
223
+ ### Loading State Management
224
+
225
+ ```tsx
226
+ function RegenerateButton() {
227
+ const handleRegenerate = async () => {
228
+ const toastId = toast.loading("Regenerating API key...", {
229
+ description: "This may take a few seconds"
230
+ })
231
+
232
+ try {
233
+ const newKey = await regenerateKey()
234
+ toast.success("API key regenerated", {
235
+ id: toastId, // Updates existing toast
236
+ description: "Your old key has been invalidated"
237
+ })
238
+ } catch (error) {
239
+ toast.error("Failed to regenerate", {
240
+ id: toastId
241
+ })
242
+ }
243
+ }
244
+
245
+ return <Button onClick={handleRegenerate}>Regenerate</Button>
246
+ }
247
+ ```
248
+
249
+ ### Custom Duration
250
+
251
+ ```tsx
252
+ // Short notification (2 seconds)
253
+ toast.success("Copied!", { duration: 2000 })
254
+
255
+ // Long notification (10 seconds)
256
+ toast.warning("Important update", { duration: 10000 })
257
+
258
+ // Persistent until dismissed
259
+ toast.info("Review required", { duration: Infinity })
260
+ ```
261
+
262
+ ### Multiple Positions
263
+
264
+ ```tsx
265
+ // Override global position per toast
266
+ toast.success("Top notification", { position: "top-center" })
267
+ toast.error("Bottom notification", { position: "bottom-left" })
268
+ ```
269
+
270
+ ### Stack Multiple Toasts
271
+
272
+ ```tsx
273
+ function showBatch() {
274
+ toast.success("First notification")
275
+ setTimeout(() => toast.info("Second notification"), 200)
276
+ setTimeout(() => toast.warning("Third notification"), 400)
277
+ }
278
+ ```
279
+
280
+ ## Keyboard
281
+
282
+ | Key | Action |
283
+ |-----|--------|
284
+ | Escape | Dismiss all toasts |
285
+ | Swipe | Dismiss individual toast (touch devices) |
286
+
287
+ ## Accessibility
288
+
289
+ - ARIA live regions announce toast messages to screen readers
290
+ - Toasts are automatically announced with appropriate politeness level based on type
291
+ - Keyboard accessible dismissal with Escape key
292
+ - Focus management preserves user context
293
+ - Action buttons are keyboard navigable
294
+
295
+ ## Related
296
+
297
+ - [Alert](./alert.llm.md) - Static inline notifications
298
+ - [Alert Dialog](./alert-dialog.llm.md) - Modal confirmations
299
+ - [Dialog](./dialog.llm.md) - Modal dialogs
@@ -0,0 +1,187 @@
1
+ # Spinner
2
+
3
+ Animated loading spinner for indicating processing states and asynchronous operations.
4
+
5
+ ## Import
6
+
7
+ ```tsx
8
+ import { Spinner } from "@neynar/ui/spinner"
9
+ ```
10
+
11
+ ## Anatomy
12
+
13
+ ```tsx
14
+ <Spinner />
15
+ ```
16
+
17
+ ## Props
18
+
19
+ Extends all standard SVG element props (`React.ComponentProps<"svg">`), including `className`, `aria-*`, and SVG attributes.
20
+
21
+ ### Common Props
22
+
23
+ | Prop | Type | Default | Description |
24
+ |------|------|---------|-------------|
25
+ | className | string | - | Custom CSS classes. Default size is `size-4` (16px) |
26
+ | color | string | - | Icon color (inherits from text color by default) |
27
+ | size | number \| string | - | Icon size in pixels or as string |
28
+ | strokeWidth | number \| string | - | Stroke width for the icon |
29
+
30
+ ### Built-in Attributes
31
+
32
+ - `role="status"` - Announces loading state to screen readers
33
+ - `aria-label="Loading"` - Provides accessible label
34
+ - `animate-spin` - Continuous rotation animation
35
+
36
+ ## Sizing
37
+
38
+ Use Tailwind size utilities via `className`:
39
+
40
+ ```tsx
41
+ // Extra small (12px)
42
+ <Spinner className="size-3" />
43
+
44
+ // Small/Default (16px)
45
+ <Spinner className="size-4" />
46
+
47
+ // Medium (24px)
48
+ <Spinner className="size-6" />
49
+
50
+ // Large (32px)
51
+ <Spinner className="size-8" />
52
+
53
+ // Extra large (48px)
54
+ <Spinner className="size-12" />
55
+ ```
56
+
57
+ ## Examples
58
+
59
+ ### Basic Loading Indicator
60
+
61
+ ```tsx
62
+ <div className="flex items-center justify-center">
63
+ <Spinner className="size-8" />
64
+ </div>
65
+ ```
66
+
67
+ ### With Loading Text
68
+
69
+ ```tsx
70
+ <div className="flex flex-col items-center gap-3">
71
+ <Spinner className="size-8" />
72
+ <p className="text-sm text-muted-foreground">Loading content...</p>
73
+ </div>
74
+ ```
75
+
76
+ ### In Button (Loading State)
77
+
78
+ ```tsx
79
+ <Button disabled>
80
+ <Spinner data-icon="inline-start" />
81
+ Loading...
82
+ </Button>
83
+ ```
84
+
85
+ The `data-icon="inline-start"` attribute uses Button's internal spacing for icons at the start position.
86
+
87
+ ### Small Button
88
+
89
+ ```tsx
90
+ <Button size="sm" disabled>
91
+ <Spinner data-icon="inline-start" className="size-3.5" />
92
+ Saving...
93
+ </Button>
94
+ ```
95
+
96
+ ### Inline with Text
97
+
98
+ ```tsx
99
+ <div className="flex items-center gap-2">
100
+ <Spinner className="size-4" />
101
+ <span className="text-sm">Syncing with Farcaster network...</span>
102
+ </div>
103
+ ```
104
+
105
+ ### Custom Colors
106
+
107
+ ```tsx
108
+ // Primary color
109
+ <Spinner className="size-6 text-primary" />
110
+
111
+ // Custom color
112
+ <Spinner className="size-6 text-blue-600" />
113
+
114
+ // Destructive
115
+ <Spinner className="size-6 text-destructive" />
116
+
117
+ // Muted
118
+ <Spinner className="size-6 text-muted-foreground" />
119
+ ```
120
+
121
+ ### Full-Page Loading
122
+
123
+ ```tsx
124
+ <div className="flex min-h-[400px] items-center justify-center">
125
+ <Spinner className="size-12" />
126
+ </div>
127
+ ```
128
+
129
+ ### Card Loading State
130
+
131
+ ```tsx
132
+ <Card>
133
+ <CardContent className="flex min-h-[200px] flex-col items-center justify-center gap-3">
134
+ <Spinner className="size-8" />
135
+ <p className="text-sm text-muted-foreground">Loading activity feed...</p>
136
+ </CardContent>
137
+ </Card>
138
+ ```
139
+
140
+ ### Conditional Loading with Success State
141
+
142
+ ```tsx
143
+ function DataLoader() {
144
+ const [status, setStatus] = useState<"loading" | "success">("loading")
145
+
146
+ return (
147
+ <Button onClick={handleLoad} disabled={status === "loading"}>
148
+ {status === "loading" ? (
149
+ <Spinner data-icon="inline-start" />
150
+ ) : (
151
+ <CheckCircle2Icon data-icon="inline-start" className="text-green-600" />
152
+ )}
153
+ {status === "loading" ? "Loading..." : "Fetch Data"}
154
+ </Button>
155
+ )
156
+ }
157
+ ```
158
+
159
+ ## Size Reference
160
+
161
+ | Class | Pixels | Use Case |
162
+ |-------|--------|----------|
163
+ | `size-3` | 12px | Extra small buttons, tight spaces |
164
+ | `size-4` | 16px | Default, inline text, small buttons |
165
+ | `size-6` | 24px | Medium buttons, prominent indicators |
166
+ | `size-8` | 32px | Large buttons, card loading states |
167
+ | `size-12` | 48px | Full page loading, empty states |
168
+
169
+ ## Accessibility
170
+
171
+ - Built-in `role="status"` for ARIA live region announcement
172
+ - `aria-label="Loading"` provides screen reader context
173
+ - Animation respects `prefers-reduced-motion` when configured
174
+ - Color contrast should meet WCAG AA standards (use appropriate text colors)
175
+
176
+ ## Implementation Notes
177
+
178
+ - Uses Lucide's `Loader2Icon` component
179
+ - Animated via Tailwind's `animate-spin` utility
180
+ - Inherits text color by default for easy theming
181
+ - SVG renders inline for optimal performance
182
+ - No additional JavaScript runtime overhead
183
+
184
+ ## Related
185
+
186
+ - [Button](./button.llm.md) - Often used with Spinner for loading states
187
+ - [Skeleton](./skeleton.llm.md) - Alternative loading indicator for content placeholders