# @neynar/ui - Complete Documentation
> React component library built on Base UI primitives + Tailwind CSS v4. This file contains complete documentation for all 53 components, theming, hooks, and utilities.
---
# Package Overview
A React component library built on Base UI primitives + Tailwind CSS v4. Production-tested in Neynar Studio.
## Quick Start
```tsx
import { Button } from "@neynar/ui/button";
import { Card, CardHeader, CardContent } from "@neynar/ui/card";
import "@neynar/ui/themes/purple-dawn";
export function App() {
return (
Welcome
);
}
```
## Import Pattern
Each component has its own entry point:
```tsx
import { Button } from "@neynar/ui/button";
import { Dialog, DialogTrigger, DialogContent } from "@neynar/ui/dialog";
import { Input } from "@neynar/ui/input";
import { cn } from "@neynar/ui/utils";
```
## Themes
Two themes available:
```tsx
// Purple Dawn - elegant translucent surfaces with purple tint (default)
import "@neynar/ui/themes/purple-dawn";
// First Light - hand-drawn wireframe aesthetic
import "@neynar/ui/themes/first-light";
```
See [theming.llm.md](./theming.llm.md) for customization.
## Color Mode
SSR-safe dark mode with automatic system detection:
```tsx
import { ColorModeInitializer, ColorModeToggle } from "@neynar/ui/color-mode";
// In layout.tsx
// Anywhere for user control
```
## Component Categories
### Core Inputs
Button, Checkbox, Input, RadioGroup, Select, Slider, Switch, Textarea, Toggle, ToggleGroup
### Form & Field
ButtonGroup, Calendar, Field, InputGroup, InputOTP, Label
### Layout & Structure
Accordion, AspectRatio, Card, Collapsible, Resizable, Separator, Table
### Navigation & Menus
Breadcrumb, ContextMenu, DropdownMenu, Menubar, NavigationMenu, Pagination, Tabs
### Overlays & Dialogs
AlertDialog, Dialog, Drawer, HoverCard, Popover, Sheet, Tooltip
### Feedback & Status
Alert, Badge, Empty, Progress, Skeleton, Sonner (toast), Spinner
### Advanced
Avatar, Carousel, Chart, Command, Kbd, ScrollArea, Sidebar
## Documentation
- [Component Docs](./components/) - 57 individual component docs
- [Theming Guide](./theming.llm.md) - Themes, color mode, CSS variables
- [Hooks](./hooks.llm.md) - useIsMobile
- [Utilities](./utilities.llm.md) - cn() class merging
---
# Theming
@neynar/ui theming system using CSS custom properties and Tailwind CSS.
## Quick Start
Import a theme in your app's global CSS or layout:
```tsx
// Option 1: Import in CSS
import "@neynar/ui/themes/purple-dawn"
// Option 2: Import in layout.tsx
import "@neynar/ui/themes/purple-dawn"
```
## Concepts
- **Theme** = Visual aesthetic (purple-dawn, first-light) - imported via CSS
- **Color Mode** = Light or dark variant - controlled at runtime via `.dark` class
## Available Themes
| Theme | Import | Description |
|-------|--------|-------------|
| Purple Dawn | `@neynar/ui/themes/purple-dawn` | Default. Elegant translucent surfaces with purple tint |
| First Light | `@neynar/ui/themes/first-light` | Hand-drawn sketch aesthetic with wobbly edges |
## Architecture
### base.css (Infrastructure)
Shared by all themes. Contains:
- Tailwind CSS imports (`tailwindcss`, `tw-animate-css`)
- `@theme` block mapping CSS variables to Tailwind utilities
- `@source` directive for component class scanning
- Base layer styles (borders, body background)
- Surface blur rules for overlay components via `[data-slot]` selectors
**Do NOT import base.css directly.** Always import a theme.
### Theme Files
Each theme defines CSS custom properties for:
- Colors (background, foreground, semantic colors)
- Typography (font-family)
- Spacing (radius, surface-blur)
- Both light (`:root`) and dark (`.dark`) modes
## CSS Variables
### Core Colors
| Variable | Usage |
|----------|-------|
| `--background` | Page background |
| `--foreground` | Default text |
| `--card` / `--card-foreground` | Card surfaces |
| `--popover` / `--popover-foreground` | Dropdown/dialog surfaces |
| `--primary` / `--primary-foreground` | Primary buttons, links |
| `--secondary` / `--secondary-foreground` | Secondary actions |
| `--muted` / `--muted-foreground` | Subtle backgrounds, helper text |
| `--subtle-foreground` | Even lighter text (40% opacity) |
| `--accent` / `--accent-foreground` | Hover states |
| `--border` | Border colors |
| `--input` | Input borders |
| `--ring` | Focus ring |
### Semantic Colors
| Variable | Usage |
|----------|-------|
| `--destructive` | Errors, delete actions |
| `--success` | Success states |
| `--warning` | Warning states |
| `--info` | Informational states |
### Chart Colors
`--chart-1` through `--chart-5` for data visualization.
### Sidebar Colors
`--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring`
### Theme-Specific Variables
| Variable | Theme | Usage |
|----------|-------|-------|
| `--surface-blur` | All | Backdrop blur amount (12px default, 4px for First Light) |
| `--font-family` | All | Primary font family |
| `--radius` | All | Border radius base (0.625rem default, 0 for First Light) |
| `--first-light-shadow` | First Light | Offset shadow effect (3px 3px) |
| `--first-light-shadow-hover` | First Light | Hover shadow (2px 2px) |
| `--first-light-shadow-active` | First Light | Active/pressed shadow (0px 0px) |
## Radius Utilities
The `--radius` variable is used to calculate multiple radius sizes via `@theme inline`:
```css
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
```
## Dark Mode
Add `.dark` class to `` or a parent element:
```tsx
```
Themes define both light (`:root`) and dark (`.dark`) variants.
### Setup with ColorModeInitializer
To prevent flash of incorrect color mode:
```tsx
import { ColorModeInitializer } from "@neynar/ui/color-mode";
{children}
```
### Color Mode API
```tsx
// Get current mode
const isDark = document.documentElement.classList.contains('dark');
// Set mode programmatically
document.documentElement.classList.remove('light', 'dark');
document.documentElement.classList.add('dark');
// Persist preference
document.cookie = 'color-mode={"preference":"dark","mode":"dark"}; path=/; max-age=31536000';
```
## Runtime Theme Switching
For Storybook or dynamic switching, add theme class to ``:
```tsx
// Switch to First Light theme
document.documentElement.classList.add("theme-first-light")
document.documentElement.classList.remove("theme-purple-dawn")
// Switch to Purple Dawn theme
document.documentElement.classList.add("theme-purple-dawn")
document.documentElement.classList.remove("theme-first-light")
```
## Purple Dawn Theme
The default theme with elegant translucent surfaces.
### Characteristics
- **Font**: Figtree Variable (sans-serif)
- **Radius**: 0.625rem (10px)
- **Surface Blur**: 12px
- **Color Tint**: Purple (hue 290)
- **Surface Opacity**: 75% for cards and popovers
### Light Mode Colors
```css
--background: oklch(0.96 0.06 290); /* Light purple-tinted white */
--foreground: oklch(0.18 0.08 290); /* Dark purple-tinted black */
--card: oklch(0.93 0.08 290 / 75%); /* Translucent purple */
--primary: oklch(0.3 0.09 290); /* Dark purple */
--border: oklch(0.18 0.08 290 / 20%); /* 20% opacity border */
```
### Dark Mode Colors
```css
--background: oklch(0.145 0.02 290); /* Very dark purple */
--foreground: oklch(0.985 0.01 290); /* Near-white */
--card: oklch(0.205 0.03 290 / 75%); /* Translucent dark purple */
--primary: oklch(0.87 0.02 290); /* Light purple */
--border: oklch(0.985 0.01 290 / 15%); /* 15% opacity border */
```
## First Light Theme
Hand-drawn wireframe aesthetic with wobbly SVG filters.
### Setup
First Light requires an SVG filter component for the wobbly edge effect:
```tsx
import "@neynar/ui/themes/first-light"
import { FirstLightFilters } from "@neynar/ui/first-light"
// Add once in your root layout
export function Layout({ children }) {
return (
<>
{children}
>
)
}
```
### Characteristics
- **Font**: Architects Daughter (handwriting)
- **Radius**: 0 (sharp corners)
- **Surface Blur**: 4px (minimal)
- **Color Style**: High contrast black/white with strong borders
- **Special Effect**: SVG turbulence filter for wobbly edges
### Light Mode (Paper)
```css
--background: #fafaf8; /* Warm white paper */
--foreground: #1a1a1a; /* Pencil black */
--border: rgba(0, 0, 0, 0.7); /* Strong black border */
--accent: #fff3b0; /* Highlighter yellow */
```
### Dark Mode (Chalkboard)
```css
--background: #1e2a1e; /* Green-tinted chalkboard */
--foreground: #e8e8e8; /* Chalk white */
--border: rgba(255, 255, 255, 0.6); /* White chalk border */
--accent: #e8d44d; /* Yellow chalk */
```
### First Light Utility Classes
| Class | Effect |
|-------|--------|
| `.first-light-paper` | Grid paper background (20px grid) |
| `.first-light-lined` | Lined paper background (28px lines) |
| `.first-light-highlight` | Yellow highlighter effect |
| `.first-light-underline` | Hand-drawn underline (slightly rotated) |
| `.first-light-scribble` | Dashed underline effect |
| `.first-light-light` | Lighter wobble filter intensity |
| `.first-light-heavy` | Heavier wobble filter intensity |
### SVG Filter Details
The `FirstLightFilters` component provides three filter intensities:
| Filter ID | Base Frequency | Octaves | Scale | Use Case |
|-----------|----------------|---------|-------|----------|
| `#first-light-filter` | 0.015 | 2 | 1.5 | Default wobble |
| `#first-light-filter-light` | 0.006 | 1 | 0.4 | Subtle wobble |
| `#first-light-filter-heavy` | 0.012 | 3 | 1.5 | Pronounced wobble |
## Frosted Glass Effect
Cards, popovers, dialogs, sheets, and menus use translucent backgrounds with backdrop blur:
- **Surface Opacity**: 75% on `--card` and `--popover`
- **Backdrop Blur**: Via `--surface-blur` variable (applied to `[data-slot]` elements)
- **Transparent Borders**: 10-20% opacity
Components that receive the blur effect (via `base.css`):
- `[data-slot="card"]`
- `[data-slot="popover-content"]`
- `[data-slot="hover-card-content"]`
- `[data-slot="dialog-content"]`
- `[data-slot="alert-dialog-content"]`
- `[data-slot="sheet-content"]`
- `[data-slot="drawer-content"]`
- `[data-slot="dropdown-menu-content"]`
- `[data-slot="context-menu-content"]`
- `[data-slot="menubar-content"]`
- `[data-slot="navigation-menu-popup"]`
- `[data-slot="combobox-content"]`
- `[data-slot="select-content"]`
- `[data-slot="command"]`
## Creating Custom Themes
1. Create a new CSS file importing base.css
2. Define CSS variables in `:root` and `.dark`
3. Optionally add `.theme-{name}` selector for runtime switching
```css
@import "../base.css";
:root,
html.theme-custom {
--font-family: "Inter", sans-serif;
--radius: 0.5rem;
--surface-blur: 8px;
--background: oklch(0.98 0 0);
--foreground: oklch(0.1 0 0);
/* ... rest of variables */
}
.dark,
html.theme-custom.dark {
--background: oklch(0.1 0 0);
--foreground: oklch(0.98 0 0);
/* ... dark mode overrides */
}
```
## Color Format
Themes use **oklch()** for perceptually uniform colors:
```css
--primary: oklch(0.5 0.18 290);
/* L C H
| | +-- Hue (0-360)
| +------- Chroma (0-0.37)
+------------ Lightness (0-1)
*/
```
Benefits:
- Consistent perceived brightness across hues
- Easy to create harmonious palettes
- Supports relative color syntax for derived colors
### Relative Color Syntax
First Light theme uses relative color syntax for toast colors:
```css
--success-bg: oklch(from var(--success) 0.92 0.05 h / 90%);
--success-text: oklch(from var(--success) 0.25 c h);
```
This derives new colors from the base `--success` color, preserving hue while adjusting lightness and chroma.
## Using Theme Tokens in Tailwind
All CSS variables are mapped to Tailwind utilities via `@theme inline` in base.css:
```tsx
```
Available color utilities: `bg-{token}`, `text-{token}`, `border-{token}`, etc.
## Customization
Override any token in your CSS:
```css
:root {
--primary: oklch(0.6 0.25 260); /* Custom purple */
--radius: 0.5rem; /* Smaller corners */
}
.dark {
--primary: oklch(0.7 0.2 260);
}
```
---
# Hooks
React hooks provided by @neynar/ui.
## useIsMobile
Detects mobile viewport using `matchMedia`. Returns reactive boolean.
### Import
```tsx
import { useIsMobile } from "@neynar/ui/use-mobile"
```
### Returns
| Type | Description |
|------|-------------|
| `boolean` | `true` when viewport < 768px, `false` otherwise |
### Behavior
- Returns `false` during SSR (hydration-safe)
- Updates reactively on viewport resize
- Uses `matchMedia` for performance (no resize listener spam)
- Breakpoint: 768px (CSS `md:` equivalent)
### Example: Responsive Navigation
```tsx
import { useIsMobile } from "@neynar/ui/use-mobile"
function ResponsiveNav() {
const isMobile = useIsMobile()
return isMobile ? :
}
```
### Example: Responsive Modal
Use Sheet for mobile, Dialog for desktop:
```tsx
import { useIsMobile } from "@neynar/ui/use-mobile"
import { Sheet, SheetContent } from "@neynar/ui/sheet"
import { Dialog, DialogContent } from "@neynar/ui/dialog"
function ResponsiveModal({ open, onOpenChange, children }) {
const isMobile = useIsMobile()
if (isMobile) {
return (
{children}
)
}
return (
)
}
```
### Example: Conditional Rendering
```tsx
import { useIsMobile } from "@neynar/ui/use-mobile"
function Dashboard() {
const isMobile = useIsMobile()
return (
{!isMobile && }
{isMobile && }
)
}
```
### Related
- [Sheet](./components/sheet.llm.md) - Often used with mobile detection for bottom sheets
- [Drawer](./components/drawer.llm.md) - Mobile-first bottom sheet component
- [Sidebar](./components/sidebar.llm.md) - Responsive sidebar that can use mobile detection
---
# Utilities
Helper functions provided by @neynar/ui.
## cn()
Merges class names with Tailwind CSS conflict resolution.
### Import
```tsx
import { cn } from "@neynar/ui/utils"
```
### Signature
```tsx
function cn(...inputs: ClassValue[]): string
```
### How It Works
1. **clsx** - Handles conditionals, arrays, and objects
2. **tailwind-merge** - Resolves Tailwind conflicts (last class wins)
### Examples
```tsx
// Conditional classes
cn("px-4", isActive && "bg-primary")
// Merge with className prop
cn("rounded-lg border", className)
// Tailwind conflict resolution
cn("text-red-500", "text-blue-500") // → "text-blue-500"
cn("px-4 py-2", "px-8") // → "px-8 py-2"
// Objects and arrays
cn({ "opacity-50": disabled }, ["flex", "items-center"])
```
### Common Patterns
```tsx
// Component with className prop
function Card({ className, ...props }) {
return (
)
}
// Conditional styling
// Variant-based styling
{children}
```
---
## Internal Utilities
The following utilities are used internally by components but are **not exported**:
### CVA Variants (lib/variants.ts)
Internal variant definitions for consistent styling across components:
- **menuItemVariants** - Styling for DropdownMenuItem, ContextMenuItem, MenubarItem
- **typographyColorVariants** - Color options for Title, Text, Code, Blockquote
- **titleVariants** - Size and weight options for Title
- **textVariants** - Size, weight, alignment for Text
These are exposed via component props rather than direct imports:
```tsx
// Use component props (correct)
Helper textDelete
// Don't try to import variants directly (not exported)
// import { menuItemVariants } from "@neynar/ui/lib/variants" // ❌
```
## Related
- [Theming](./theming.llm.md) - CSS variables and themes
- [Hooks](./hooks.llm.md) - React hooks
---
# Contributing Guide
Technical reference for AI assistants modifying this package.
## Critical Rules
1. **Never export defaults** - Named exports only
2. **Always add `data-slot`** - Root element of every component
3. **Use `type` not `interface`** - For prop definitions
4. **`"use client"` only when needed** - Only for components using React hooks
5. **Run type-check** - `yarn type-check` must pass before committing
## File Locations
```
src/
├── components/
│ ├── ui/ # Base UI components
│ │ ├── button.tsx
│ │ └── stories/ # Storybook stories
│ │ └── button.stories.tsx
│ └── neynar/ # Neynar-specific components
│ ├── typography/
│ ├── color-mode/
│ └── first-light/
├── hooks/ # Custom hooks
├── lib/ # Utilities (cn, variants)
└── styles/ # CSS and themes
└── themes/
llm/ # LLM documentation
├── components/ # Per-component docs
├── index.llm.md # Package overview
├── hooks.llm.md
├── utilities.llm.md
└── theming.llm.md
package.json # Exports map (auto-generated)
```
## Component Template
```tsx
// Add "use client" ONLY if component uses hooks
import { Primitive } from "@base-ui/react/primitive"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const componentVariants = cva("base-classes", {
variants: {
variant: {
default: "...",
secondary: "...",
},
},
defaultVariants: {
variant: "default",
},
})
type ComponentProps = Primitive.Props & VariantProps
/**
* Brief description.
*
* @example
* ```tsx
* Content
* ```
*/
function Component({ className, variant, ...props }: ComponentProps) {
return (
)
}
export { Component, componentVariants, type ComponentProps }
```
## Story Template
```tsx
import type { Meta, StoryObj } from "@storybook/react"
import { Component } from "../component"
const meta: Meta = {
title: "UI/Component",
component: Component,
tags: ["autodocs"],
}
export default meta
type Story = StoryObj
export const Default: Story = {
args: { children: "Example" },
}
export const Variants: Story = {
render: () => (
DefaultSecondary
),
}
```
## LLM Documentation Template
```markdown
# ComponentName
One sentence description.
## Import
\`\`\`tsx
import { Component } from "@neynar/ui/component"
\`\`\`
## Usage
\`\`\`tsx
Content
\`\`\`
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | "default" \| "secondary" | "default" | Visual style |
## Examples
### With variant
\`\`\`tsx
Secondary
\`\`\`
```
## data-slot Convention
Every component root element MUST have `data-slot`:
```tsx
// Single component
// Compound component
```
## "use client" Rules
**ADD** when component:
- Uses `useState`, `useEffect`, `useRef`, `useContext`
- Uses custom hooks (`useColorMode`, `useMobile`)
- Has event handlers that need client hydration
**OMIT** when component:
- Is a pure wrapper around Base UI primitive
- Only passes props through
- Has no React hooks
Current components with `"use client"`:
- avatar, calendar, carousel, chart, collapsible
- combobox, command, dialog, drawer, field
- hover-card, input-otp, label, progress, resizable
- select, sidebar, slider, switch, tabs
- toggle-group, tooltip
- color-mode/*, first-light/*
## Semantic Color Tokens
```css
/* Backgrounds */
bg-background, bg-card, bg-popover, bg-muted, bg-accent
/* Foregrounds */
text-foreground, text-muted-foreground, text-card-foreground
/* Status */
bg-primary, text-primary-foreground
bg-secondary, text-secondary-foreground
bg-destructive, text-destructive
bg-success, text-success
bg-warning, text-warning
bg-info, text-info
/* Borders */
border-border, border-input, border-ring
```
## Package.json Exports
Exports are auto-generated by `scripts/generate-exports.ts` during build.
Each component gets an entry:
```json
{
"./button": {
"types": "./dist/components/ui/button.d.ts",
"import": "./dist/components/ui/button.js"
}
}
```
## Validation Commands
```bash
# Must pass before commit
yarn type-check # TypeScript
yarn lint # ESLint
yarn build # Build check
# Development
yarn storybook # Visual testing
```
## Common Patterns
### Polymorphism with `render` prop
```tsx
}>Link Button
```
### Compound Components
```tsx
```
### Forwarding refs (handled by Base UI)
```tsx
// Base UI handles ref forwarding automatically
// No need for forwardRef wrapper
function Component(props: ComponentProps) {
return
}
```
### Icon sizing
```tsx
// Icons auto-size via [&_svg:not([class*='size-'])]:size-4
```
## Debugging Tips
- Check `data-slot` attributes in DevTools for component boundaries
- Use `?path=/story/ui-button--default` in Storybook URL
- Run `yarn type-check 2>&1 | head -20` for focused error output
---
# Components
# Accordion
Collapsible content sections with expand/collapse controls for FAQs, documentation, and hierarchical content.
## Import
```tsx
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from "@neynar/ui/accordion"
```
## Anatomy
```tsx
Question or titleAnswer or content
```
## Components
| Component | Description |
|-----------|-------------|
| Accordion | Root container, manages open/closed state and keyboard navigation |
| AccordionItem | Groups a trigger with its content panel, requires unique `value` |
| AccordionTrigger | Button that toggles the item, includes automatic chevron icons |
| AccordionContent | Collapsible panel with automatic slide animation |
## Props
### Accordion
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| defaultValue | any[] | - | Uncontrolled initial open items (single value or array) |
| value | any[] | - | Controlled open items (for managing state externally) |
| onValueChange | (value: any[]) => void | - | Called when items open/close |
| multiple | boolean | false | Allow multiple items open simultaneously |
| disabled | boolean | false | Disable all items |
| orientation | 'vertical' \| 'horizontal' | 'vertical' | Visual orientation, affects keyboard navigation |
| loopFocus | boolean | true | Loop focus when reaching end with arrow keys |
### AccordionItem
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | any | (auto) | Unique identifier for this item (required for controlled state) |
| disabled | boolean | false | Disable this specific item |
| onOpenChange | (open: boolean) => void | - | Called when this item opens/closes |
### AccordionTrigger
Accepts all Base UI Accordion.Trigger props. Automatically includes chevron icons that flip based on open state.
### AccordionContent
Accepts all Base UI Accordion.Panel props. Automatically animates height with slide down/up transitions.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| keepMounted | boolean | false | Keep content in DOM when closed (for SEO/hydration) |
| hiddenUntilFound | boolean | false | Use `hidden="until-found"` for browser's find-in-page |
## Data Attributes
All components support data attributes for styling:
| Attribute | When Present |
|-----------|--------------|
| data-open | Item/panel is expanded |
| data-closed | Item/panel is collapsed |
| data-disabled | Item/accordion is disabled |
| data-orientation | Current orientation ('vertical' or 'horizontal') |
| data-index | Index of the item in the accordion |
| data-starting-style | Panel is animating in |
| data-ending-style | Panel is animating out |
## Examples
### Single Item Open (Default)
```tsx
How do I get started?
Sign up for a free account and generate your API key from the dashboard.
What are the rate limits?
Free tier: 1,000 requests/day. Pro: 100,000 requests/day.
```
### Multiple Items Open
```tsx
API Features
Real-time feeds, webhooks, and comprehensive user data.
Authentication
Sign-in with Farcaster (SIWF) and API key authentication.
```
### Controlled State
```tsx
function ControlledAccordion() {
const [openItems, setOpenItems] = useState(["item-1"])
return (
Controlled Item
Open state is managed externally via React state.
)
}
```
### With Icons and Rich Content
```tsx
Real-time Updates
Access real-time feeds with sub-second latency.
Automatic pagination
Advanced filtering
```
### Disabled Items
```tsx
Active ItemThis item can be toggled.Disabled ItemThis content cannot be accessed.
```
## Keyboard
| Key | Action |
|-----|--------|
| Space / Enter | Toggle focused item |
| Tab | Move to next focusable element |
| Shift + Tab | Move to previous focusable element |
| ArrowDown | Move focus to next item trigger (vertical) |
| ArrowUp | Move focus to previous item trigger (vertical) |
| Home | Focus first item trigger |
| End | Focus last item trigger |
## Accessibility
- Full keyboard navigation with arrow keys and roving tabindex
- ARIA attributes automatically applied (`aria-expanded`, `aria-controls`, `aria-labelledby`)
- Screen readers announce expanded/collapsed state changes
- Focus management maintains expected tab order
- Supports `hiddenUntilFound` for browser find-in-page functionality
## Related
- [Collapsible](./collapsible.llm.md) - Single collapsible section without accordion semantics
- [Tabs](./tabs.llm.md) - Alternative for showing one section at a time
- [Dialog](./dialog.llm.md) - For overlay content instead of inline expansion
---
# AlertDialog
Modal dialog for critical confirmations requiring user attention, typically for destructive or irreversible actions.
## Import
```tsx
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
AlertDialogTrigger,
} from "@neynar/ui/alert-dialog"
```
## Anatomy
```tsx
```
## Components
| Component | Description |
|-----------|-------------|
| AlertDialog | Root container managing open state and accessibility |
| AlertDialogTrigger | Button that opens the dialog (supports `render` prop) |
| AlertDialogContent | Main dialog content with portal and backdrop |
| AlertDialogHeader | Container for title, description, and optional media |
| AlertDialogTitle | Dialog title (automatically announced to screen readers) |
| AlertDialogDescription | Description text with automatic link styling |
| AlertDialogMedia | Optional icon container for visual indicators |
| AlertDialogFooter | Container for action buttons |
| AlertDialogAction | Primary action button (inherits Button variants) |
| AlertDialogCancel | Cancel button that closes the dialog |
| AlertDialogPortal | Portal container (automatically used by AlertDialogContent) |
| AlertDialogOverlay | Backdrop overlay (automatically used by AlertDialogContent) |
## Props
### AlertDialog
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| open | boolean | - | Controlled open state |
| defaultOpen | boolean | false | Uncontrolled default open state |
| onOpenChange | (open: boolean) => void | - | Called when open state changes |
### AlertDialogContent
Automatically renders portal and backdrop overlay. Centered with fade + zoom animations.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| size | "default" \| "sm" | "default" | Dialog size variant |
**Size Behavior:**
- `sm` - Compact dialog, footer buttons in 2-column grid on mobile
- `default` - Standard size, left-aligned on desktop when using media icon
### AlertDialogTrigger
Supports `render` prop to customize the trigger element:
```tsx
}>
Delete Item
```
### AlertDialogCancel
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | Button variant | "outline" | Button variant (inherits from Button) |
| size | Button size | "default" | Button size (inherits from Button) |
Automatically renders as a Button via `render` prop. Closes dialog when clicked.
### AlertDialogAction
Inherits all Button props and variants. Does not automatically close the dialog - handle close in `onClick`:
```tsx
{
deleteItem()
// Dialog closes via state change
}}
>
Delete
```
## Data Attributes
| Attribute | When Present |
|-----------|--------------|
| data-open | Dialog is open |
| data-closed | Dialog is closed |
| data-size | Size variant ("default" or "sm") |
| data-slot | Component identifier for styling |
## Examples
### Basic Destructive Action
```tsx
}>
Delete Item
Delete this item?
This action cannot be undone. The item will be permanently removed.
CancelDelete
```
### With Media Icon
```tsx
}>
Revoke Key
Revoke API Key?
This will immediately invalidate the API key. Any applications using
this key will stop working. This action cannot be undone.
CancelRevoke Key
```
### Small Dialog for Quick Confirmations
```tsx
}>
Delete Webhook
Delete webhook?
You will no longer receive events at this endpoint.
CancelDelete
```
### Controlled State
```tsx
function ControlledExample() {
const [open, setOpen] = useState(false)
return (
<>
Confirm action
This dialog is controlled externally.
setOpen(false)}>
Cancel
{
performAction()
setOpen(false)
}}
>
Confirm
>
)
}
```
### Complex Content with Lists
```tsx
}>
Delete Multiple Items
Delete 3 items?
The following items will be permanently deleted:
API Key: prod_****_8f3d
Webhook: https://api.example.com/hook
Team Member: jane@example.com
This action cannot be undone.
CancelDelete All
```
## Keyboard
| Key | Action |
|-----|--------|
| Escape | Close dialog |
| Tab | Navigate between action and cancel buttons |
| Space/Enter | Activate focused button |
## Accessibility
- Title is announced to screen readers via `aria-labelledby`
- Description is associated via `aria-describedby`
- Focus is trapped within dialog when open
- Focus returns to trigger element when closed
- Escape key closes dialog
- Backdrop click closes dialog
## Related
- [Dialog](./dialog.llm.md) - For non-critical confirmations and general content
- [Button](./button.llm.md) - Used for action and cancel buttons
- [Popover](./popover.llm.md) - For non-modal contextual content
---
# Alert
Displays contextual messages with optional icons and actions for notifications, warnings, errors, and informational content.
## Import
```tsx
import { Alert, AlertTitle, AlertDescription, AlertAction } from "@neynar/ui/alert"
```
## Anatomy
```tsx
TitleDescription with optional links.
```
## Components
| Component | Description |
|-----------|-------------|
| Alert | Root container with variant styling and grid layout |
| AlertTitle | Title heading, auto-positions next to icon |
| AlertDescription | Description content with muted foreground color |
| AlertAction | Action area positioned in top-right corner |
## Props
### Alert
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | "default" \| "destructive" \| "success" \| "warning" \| "info" | "default" | Visual style variant for different message types |
| className | string | - | Additional CSS classes |
| children | ReactNode | - | Alert content (icon, title, description, action) |
All standard HTML div props are supported.
### AlertTitle
Standard HTML div props. Automatically positions next to icon when present using CSS grid. Supports inline links with underline styling on hover.
### AlertDescription
Standard HTML div props. Uses muted foreground color that adapts to the alert variant. Supports paragraph spacing and inline links.
### AlertAction
Standard HTML div props. Absolutely positioned in top-right corner (top-2.5, right-3). Use for dismiss buttons or action CTAs.
## Data Attributes
| Attribute | Element | When Present |
|-----------|---------|--------------|
| data-slot="alert" | Alert | Always on root alert element |
| data-slot="alert-title" | AlertTitle | Always on title element |
| data-slot="alert-description" | AlertDescription | Always on description element |
| data-slot="alert-action" | AlertAction | Always on action element |
The Alert uses `has-data-[slot=alert-action]:pr-18` to add right padding when an action is present, preventing content overlap.
## Variants
| Variant | Usage |
|---------|-------|
| default | General information, updates, neutral notifications |
| destructive | Errors, critical information requiring immediate attention |
| success | Successful actions, positive outcomes, confirmations |
| warning | Important warnings requiring attention but not critical |
| info | Informational messages, tips, helpful context |
## Icon Handling
When an SVG icon is included as a direct child:
- Alert switches to 2-column grid layout (`has-[>svg]:grid-cols-[auto_1fr]`)
- Icon is auto-sized to 16px (`*:[svg:not([class*='size-'])]:size-4`)
- Icon color matches variant text color
- Icon spans both rows and translates slightly down for alignment
- Title and description automatically position in second column
## Examples
### Basic Alert
```tsx
Heads up!
You can add components to your app using the CLI.
```
### Destructive/Error Alert
```tsx
Authentication Error
Your API key has expired or is invalid. Please generate a new key or check
your credentials in the API settings page.
```
### Success Alert
```tsx
Plan Upgrade Successful
Your account has been upgraded to the Pro plan. Your new rate limits and
features are now active.
```
### With Dismiss Action
```tsx
Rate Limit Warning
You've used 95% of your API rate limit for this billing period. Your requests
may be throttled. Consider upgrading your plan to
increase your limit.
```
### With Primary Action Button
```tsx
API v1 Deprecation Notice
The v1 API endpoints will be deprecated on March 31, 2025. Please migrate to
v2 endpoints to avoid service disruption.
```
### Description Only (No Title)
```tsx
Your changes have been saved automatically.
```
### Without Icon
```tsx
Simple Alert
This alert doesn't have an icon, useful for less critical information.
```
### Warning Alert
```tsx
Webhook Configuration Required
Set up webhooks to receive real-time notifications for cast events, reactions,
and follows. Configure webhooks in your dashboard settings.
```
### Info Alert
```tsx
Update Available
A new version of the API is available with improved performance.
```
## Styling
### Custom Variants
Use the `alertVariants` function to extend or create custom variants:
```tsx
import { alertVariants } from "@neynar/ui/alert"
{/* ... */}
```
### Custom Styling
Override styles using className:
```tsx
Custom Styled
With larger text and border.
```
## Accessibility
- Uses `role="alert"` for screen reader announcements
- Alert content is announced immediately when rendered
- Interactive elements (links, buttons) are keyboard accessible
- Link underlines and hover states provide clear affordance
- Variant colors provide additional visual context beyond text
## Layout Behavior
The Alert uses CSS Grid for flexible layout:
- **Without icon**: Single column layout
- **With icon**: Two-column grid with icon in first column
- **With action**: Adds right padding to prevent overlap
- **Responsive text**: Uses `text-balance` on mobile and `text-pretty` on desktop for optimal readability
## Common Patterns
### API Dashboard Notifications
```tsx
Rate Limit Warning
You've used 95% of your API rate limit for this billing period.
API v1 Deprecation Notice
The v1 API endpoints will be deprecated on March 31, 2025.
```
### Inline Links
Alert descriptions automatically style links with underlines and hover states:
```tsx
Documentation Updated
We've updated our API documentation with new examples and best practices.
View the updated docs or read the changelog.
```
## Related
- **Toast** - For temporary notifications that auto-dismiss
- **Banner** - For persistent site-wide announcements
- **Dialog** - For alerts requiring user confirmation
---
# AspectRatio
Container that maintains a consistent aspect ratio, automatically adjusting height based on width.
## Import
```tsx
import { AspectRatio } from "@neynar/ui/aspect-ratio"
```
## Anatomy
```tsx
```
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| ratio | number | - | Aspect ratio as width/height (e.g., 16/9, 1, 4/3) |
| className | string | - | Additional CSS classes |
| children | ReactNode | - | Content to display within aspect ratio container |
All standard `div` props are supported.
## Common Aspect Ratios
| Ratio | Value | Use Case |
|-------|-------|----------|
| 16:9 | 16/9 | Video content, YouTube thumbnails, widescreen |
| 4:3 | 4/3 | Traditional displays, some photography |
| 1:1 | 1 | Square images, Instagram posts, NFTs, profile pictures |
| 21:9 | 21/9 | Ultrawide/cinematic content, hero banners |
| 9:16 | 9/16 | Vertical video (stories, reels) |
| 2:1 | 2/1 | Panoramic images, Twitter banners |
## Examples
### Video Thumbnail
```tsx
```
### NFT Gallery
```tsx
{nfts.map((nft) => (
))}
```
### Profile Banner
```tsx
Channel Name
```
### Placeholder Content
```tsx
Upload Image
```
## Usage Notes
- Always use `overflow-hidden` with images to prevent content overflow
- Combine with `object-cover` for images that fill the container
- Use `object-contain` to fit entire image with possible letterboxing
- Container width is flexible; height is calculated automatically
- Works with any content type: images, video, gradients, custom elements
## Related
- [Card](./card.llm.md) - Often used together for media cards
---
# Avatar
Display user profile images with automatic fallback support, status badges, and grouping.
## Import
```tsx
import {
Avatar,
AvatarImage,
AvatarFallback,
AvatarBadge,
AvatarGroup,
AvatarGroupCount,
} from "@neynar/ui/avatar"
```
## Anatomy
```tsx
UN......+5
```
## Components
| Component | Description |
|-----------|-------------|
| Avatar | Root container with size variants |
| AvatarImage | Image element with automatic fallback |
| AvatarFallback | Fallback content (initials or icon) shown when image unavailable |
| AvatarBadge | Status indicator positioned at bottom-right |
| AvatarGroup | Container for stacked avatars with ring borders |
| AvatarGroupCount | Counter badge showing overflow count ("+N") |
## Props
### Avatar
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| size | "sm" \| "default" \| "lg" | "default" | Avatar size variant |
Extends all Base UI Avatar.Root props.
### AvatarImage
Standard `img` element props via Base UI Avatar.Image.
| Prop | Type | Description |
|------|------|-------------|
| src | string | Image source URL |
| alt | string | Alternative text for accessibility |
### AvatarFallback
Extends Base UI Avatar.Fallback props. Accepts text content (typically initials) or icon elements.
### AvatarBadge
Standard `span` element props. Can contain icons or be used as colored dot.
### AvatarGroup
Standard `div` element props. Automatically applies ring borders and negative spacing to child avatars.
### AvatarGroupCount
Standard `div` element props. Display text like "+12" or icons.
## Sizes
| Size | Dimensions | Use Case |
|------|------------|----------|
| sm | 24px | Compact lists, inline mentions |
| default | 32px | Standard UI, activity feeds |
| lg | 40px | Profile headers, featured users |
## Data Attributes
| Attribute | Value | Applied To |
|-----------|-------|------------|
| data-slot | "avatar" | Avatar root |
| data-slot | "avatar-image" | AvatarImage |
| data-slot | "avatar-fallback" | AvatarFallback |
| data-slot | "avatar-badge" | AvatarBadge |
| data-slot | "avatar-group" | AvatarGroup |
| data-slot | "avatar-group-count" | AvatarGroupCount |
| data-size | "sm" \| "default" \| "lg" | Avatar root (for size styling) |
## Examples
### Basic Usage
```tsx
DW
```
### With Sizes
```tsx
ABABAB
```
### Fallback States
```tsx
{/* Initials fallback */}
JD
{/* Icon fallback */}
```
### With Status Badge
```tsx
{/* Verification badge */}
VU
{/* Online status dot */}
OU
{/* Custom status */}
PU
```
### Avatar Group
```tsx
{/* Basic group */}
U1U2U3
{/* With overflow count */}
U1U2U3+8
{/* Icon with count */}
24
```
### User Profile
```tsx
DW
Dan Romero
@dwr.eth
```
### Activity Feed
```tsx
UN
@username cast a new post
2 minutes ago
```
## Accessibility
- AvatarImage includes `alt` attribute for screen readers
- AvatarFallback provides text alternative when images fail to load
- Proper semantic HTML with appropriate ARIA attributes via Base UI
- Badge icons should be decorative; status communicated through other means
## Technical Notes
- Image loading handled automatically by Base UI Avatar primitive
- Fallback displays only when image fails to load or src is missing
- Badge automatically scales with avatar size (sm shows no icon, default/lg show icons)
- AvatarGroup applies negative spacing (`-space-x-2`) and ring borders automatically
- Uses `data-size` attribute for responsive badge sizing
- Group uses `/avatar` and `/avatar-group` context for nested styling
## Related
- [Button](./button.llm.md) - For clickable avatar actions
- [Card](./card.llm.md) - For profile cards with avatars
- [Dialog](./dialog.llm.md) - For profile modals
---
# Badge
Small label for displaying metadata, status indicators, counts, and categorical information.
## Import
```tsx
import { Badge } from "@neynar/ui/badge"
```
## Anatomy
```tsx
Label
```
## Props
### Badge
Extends all HTML span attributes plus:
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | "default" \| "secondary" \| "destructive" \| "success" \| "warning" \| "info" \| "outline" \| "ghost" \| "link" | "default" | Visual style variant |
| render | React.ReactElement \| ((state) => React.ReactElement) | - | Custom element to render as (e.g., ``) |
| className | string | - | Additional CSS classes |
All standard HTML span attributes are supported (onClick, aria-*, data-*, etc.).
### Special Props
**render prop** - Render Badge as a different element:
```tsx
}>Documentation
```
## Variants
### Visual Variants
| Variant | Use Case | Appearance |
|---------|----------|------------|
| default | Primary emphasis, featured items | Solid primary color background |
| secondary | Neutral status, less emphasis | Subtle secondary background |
| outline | Low emphasis, borders | Border with transparent background |
| ghost | Minimal, hover reveals | Transparent, hover shows background |
| link | Clickable, link-style | Underlined text style |
### Semantic Variants
| Variant | Use Case | Appearance |
|---------|----------|------------|
| destructive | Errors, critical warnings | Red/destructive color with subtle background |
| success | Success states, confirmations | Green/success color with subtle background |
| warning | Warnings, alerts | Yellow/warning color with subtle background |
| info | Informational messages | Blue/info color with subtle background |
## Examples
### Basic Variants
```tsx
DefaultSecondaryOutlineError
```
### With Icons
Add `data-icon="inline-start"` or `data-icon="inline-end"` to icon elements for proper spacing:
```tsx
import { StarIcon, FlameIcon } from "lucide-react"
Featured
Hot
```
### Counter Badges
```tsx
31299+5
```
### Status Indicators
Use colored dots for operational status:
```tsx
Online
Degraded
Offline
```
### As Link
```tsx
} variant="outline">
Upgrade to Pro
```
### Plan Tiers
```tsx
import { CrownIcon, SparklesIcon } from "lucide-react"
Free
Popular
Enterprise
```
## Data Attributes
The Badge component sets these data attributes for styling:
| Attribute | Value |
|-----------|-------|
| data-slot | "badge" |
| data-icon | "inline-start" or "inline-end" (on icon elements) |
## Styling
### Icon Sizing
Icons inside Badge automatically get `size-3` (12px). Use `data-icon="inline-start"` or `data-icon="inline-end"` for proper padding:
```tsx
// Automatic icon sizing and spacing
Text
```
### Custom Styling
```tsx
Large Badge
```
## Accessibility
- Badge uses semantic HTML `` by default
- Can be rendered as `` via render prop for clickable badges
- Supports all ARIA attributes for enhanced semantics
- No built-in focus management (use render prop with `