# @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 ( {children} ) } ``` ### 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 text Delete // 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: () => (
Default Secondary
), } ``` ## 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 Components ```tsx Open Title Description ``` ### 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 title Answer 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 Item This item can be toggled. Disabled Item This 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. Cancel Delete ``` ### 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. Cancel Revoke Key ``` ### Small Dialog for Quick Confirmations ```tsx }> Delete Webhook Delete webhook? You will no longer receive events at this endpoint. Cancel Delete ``` ### 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.

Cancel Delete 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 Title Description 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 Video thumbnail
``` ### NFT Gallery ```tsx
{nfts.map((nft) => ( {nft.name} ))}
``` ### Profile Banner ```tsx Profile banner

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 AB AB AB ``` ### 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 */} U1 U2 U3 {/* With overflow count */} U1 U2 U3 +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 Default Secondary Outline Error ``` ### 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 3 12 99+ 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 ` Status ``` ## Components | Component | Description | |-----------|-------------| | ButtonGroup | Root container with fused border styling | | ButtonGroupSeparator | Visual divider between button sections | | ButtonGroupText | Text/status display element within group | ## Props ### ButtonGroup Extends all native `div` props plus: | Prop | Type | Default | Description | |------|------|---------|-------------| | orientation | "horizontal" \| "vertical" | "horizontal" | Layout direction of buttons | **Auto-behaviors:** - Fuses borders between adjacent buttons - Rounds only outer corners - Handles focus visibility (z-index management) - Supports Input and Select components ### ButtonGroupSeparator Extends `Separator` component props: | Prop | Type | Default | Description | |------|------|---------|-------------| | orientation | "horizontal" \| "vertical" | "vertical" | Separator direction | **Note:** Typically inherits orientation from parent ButtonGroup context, but can be overridden. ### ButtonGroupText Extends all native `div` props plus: | Prop | Type | Default | Description | |------|------|---------|-------------| | render | React.Element \| function | - | Custom element or render function | **Render prop example:** ```tsx }> Custom text ``` ## Data Attributes | Attribute | Element | Description | |-----------|---------|-------------| | data-slot="button-group" | ButtonGroup | Identifies container | | data-slot="button-group-text" | ButtonGroupText | Identifies text element | | data-slot="button-group-separator" | ButtonGroupSeparator | Identifies separator | | data-orientation | ButtonGroup | Current orientation value | ## Examples ### Basic Horizontal Group ```tsx ``` ### Vertical Group with Icons ```tsx ``` ### Split Button Pattern ```tsx ``` ### With Separators ```tsx ``` ### Pagination with Text ```tsx 1 of 10 ``` ### Bulk Actions with Status ```tsx 3 selected ``` ### Search Bar with Input ```tsx ``` ### Filter Toggle Group ```tsx function FilterExample() { const [filter, setFilter] = useState("all") return ( ) } ``` ## Keyboard ButtonGroup inherits keyboard navigation from child components: | Key | Action | |-----|--------| | Tab | Navigate to next focusable element | | Shift+Tab | Navigate to previous focusable element | | Space/Enter | Activate focused button | ## Accessibility - Uses `role="group"` to semantically group related buttons - Maintains proper focus order through natural DOM structure - Focus visibility handled with z-index elevation on `:focus-visible` - Child buttons maintain individual ARIA attributes and keyboard support ## Related - [Button](./button.llm.md) - Individual button component - [Separator](./separator.llm.md) - Standalone separator - [Input](./input.llm.md) - Works with Input in search patterns - [Select](./select.llm.md) - Works with Select in filter patterns --- # Button Versatile button component with multiple visual variants, semantic colors, and flexible sizing. ## Import ```tsx import { Button } from "@neynar/ui/button" ``` ## Anatomy ```tsx ``` ## Props ### Button Extends all standard HTML button attributes plus Base UI Button props. | Prop | Type | Default | Description | |------|------|---------|-------------| | variant | "default" \| "outline" \| "secondary" \| "ghost" \| "destructive" \| "success" \| "warning" \| "info" \| "link" | "default" | Visual style variant | | size | "default" \| "xs" \| "sm" \| "lg" \| "icon" \| "icon-xs" \| "icon-sm" \| "icon-lg" | "default" | Button size | | disabled | boolean | false | Disables the button | | render | ReactElement \| Function | - | Custom element to render as (maintains button semantics) | | className | string | - | Additional CSS classes | | focusableWhenDisabled | boolean | true | Whether button remains focusable when disabled | | nativeButton | boolean | true | Whether component renders native ` // Icon at end ``` The component automatically sizes icons to `size-4` (16px) except for `xs` sizes which use `size-3` (12px). Icons receive proper spacing via gap utilities. ### Render Prop Use the `render` prop to compose the button with other elements while maintaining button semantics: ```tsx // Render as link // Compose with Next.js Link ``` ## Variants ### Visual Variants | Variant | Use Case | |---------|----------| | default | Primary actions, most important button in a context | | outline | Secondary actions, neutral emphasis with border | | secondary | Alternative actions, less emphasis than default | | ghost | Tertiary actions, minimal visual weight | | link | Text-only actions that look like links | ### Semantic Variants | Variant | Use Case | |---------|----------| | destructive | Delete, revoke, or other destructive actions | | success | Confirm, approve, or positive actions | | warning | Caution, proceed with care actions | | info | Informational or help actions | ### Size Variants | Size | Height | Use Case | |------|--------|----------| | xs | 24px (h-6) | Compact UIs, inline actions, tags | | sm | 32px (h-8) | Dense tables, toolbars, secondary actions | | default | 36px (h-9) | Standard buttons in forms and dialogs | | lg | 40px (h-10) | Marketing pages, prominent CTAs | ### Icon-Only Sizes | Size | Dimensions | Use Case | |------|------------|----------| | icon-xs | 24x24px | Compact icon buttons | | icon-sm | 32x32px | Small icon buttons in toolbars | | icon | 36x36px | Standard icon buttons | | icon-lg | 40x40px | Large icon buttons for emphasis | ## Data Attributes | Attribute | When Present | |-----------|--------------| | data-slot="button" | Always present (for styling context) | | data-disabled | Button is disabled | | data-focusable | Button remains focusable when disabled | | aria-expanded | Button controls expandable content (auto-styled) | | aria-invalid | Button is in invalid state (auto-styled with destructive ring) | ## Examples ### Basic Usage ```tsx ``` ### With Icons ```tsx // Icon at start // Icon at end // Icon only - requires aria-label ``` ### Loading State ```tsx function SubmitButton() { const [isLoading, setIsLoading] = useState(false) return ( ) } ``` ### API Key Management Context ```tsx function APIKeyActions() { const [copied, setCopied] = useState(false) return (
) } ``` ### Form Actions ```tsx
``` ## Keyboard | Key | Action | |-----|--------| | Space | Activate button | | Enter | Activate button | | Tab | Move focus to/from button | ## Accessibility - Rendered as native `

Your account settings and preferences.

``` ### With Footer Actions ```tsx Confirm Action This action cannot be undone

Are you sure you want to delete this item?

``` ### Small Size Card ```tsx Compact Card Reduced padding

Small size for dense layouts.

``` ### Content Only ```tsx

Simple card with just content, no header or footer.

``` ### With Status Indicator ```tsx
Success
Operation completed successfully

Your changes have been saved.

``` ### Dashboard Stats Card ```tsx Total Requests Last 30 days
1,284,563
12.5% from last month
``` ### With Progress and Footer ```tsx API Rate Limit Requests this month: 847,231 / 1,000,000
84.7% used 152,769 remaining

Resets on January 1, 2024

``` ### Complex Layout with Avatar ```tsx
JD
Jane Doe Admin
jane.doe@example.com
API Access
Last active: 2 hours ago
``` ## Theming The Card component uses the following CSS custom properties: - `--card`: Background color - `--card-foreground`: Text color - `--border`: Ring/border color - `--muted-foreground`: Description text color For frosted glass effects, the component respects: - `--surface-blur`: Backdrop blur amount - 75% opacity on `--card` background ## Accessibility - Use semantic HTML elements within cards for proper structure - CardTitle should contain heading elements when appropriate (`

`, `

`, etc.) - Ensure sufficient color contrast between text and card background - Footer buttons should have clear, actionable labels ## Related - [Button](./button.llm.md) - For actions in CardAction and CardFooter - [Badge](./badge.llm.md) - For status indicators in headers - [Progress](./progress.llm.md) - For usage stats within cards - [Avatar](./avatar.llm.md) - For user cards with profile images --- # Carousel Embla-powered carousel component with horizontal/vertical scrolling, keyboard navigation, and programmatic control. ## Import ```tsx import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious, type CarouselApi, } from "@neynar/ui/carousel" ``` ## Anatomy ```tsx ... ... ``` ## Components | Component | Description | |-----------|-------------| | Carousel | Root container, manages state and keyboard navigation | | CarouselContent | Scrollable container with overflow handling | | CarouselItem | Individual slide wrapper | | CarouselPrevious | Previous button (auto-disabled at start) | | CarouselNext | Next button (auto-disabled at end) | ## Props ### Carousel | Prop | Type | Default | Description | |------|------|---------|-------------| | orientation | "horizontal" \| "vertical" | "horizontal" | Scroll direction | | opts | EmblaOptionsType | - | Embla carousel options (loop, align, etc.) | | plugins | EmblaPluginType[] | - | Embla carousel plugins | | setApi | (api: CarouselApi) => void | - | Callback to receive carousel API for programmatic control | ### CarouselContent Standard div props. Automatically applies flex layout and orientation-based spacing. ### CarouselItem Standard div props. Use Tailwind `basis-*` classes to control items per view: - `basis-full` - 1 item per view (default) - `md:basis-1/2` - 2 items on medium screens - `lg:basis-1/3` - 3 items on large screens ### CarouselPrevious / CarouselNext Extends Button props with defaults: `variant="outline"`, `size="icon-sm"`. ## Embla Options (opts prop) Common options passed to `opts`: | Option | Type | Description | |--------|------|-------------| | loop | boolean | Enable infinite looping | | align | "start" \| "center" \| "end" | Slide alignment | | slidesToScroll | number | Number of slides to scroll at once | | skipSnaps | boolean | Skip snapping to slides that are out of view | Full options: https://www.embla-carousel.com/api/options/ ## Data Attributes | Attribute | Element | Description | |-----------|---------|-------------| | data-slot="carousel" | Carousel root | For styling hooks | | data-slot="carousel-content" | Content wrapper | For styling hooks | | data-slot="carousel-item" | Individual item | For styling hooks | | data-slot="carousel-previous" | Previous button | For styling hooks | | data-slot="carousel-next" | Next button | For styling hooks | ## Examples ### Basic Usage ```tsx {items.map((item, index) => ( {item.content} ))} ``` ### Multiple Items Per View ```tsx {items.map((item, index) => ( {item.content} ))} ``` ### Loop Mode ```tsx {items.map((item, index) => ( {item.content} ))} ``` ### Vertical Orientation ```tsx {items.map((item, index) => ( {item.content} ))} ``` ### Programmatic Control with Custom Indicators ```tsx function ControlledCarousel() { const [api, setApi] = useState() const [current, setCurrent] = useState(0) const [count, setCount] = useState(0) useEffect(() => { if (!api) return setCount(api.scrollSnapList().length) setCurrent(api.selectedScrollSnap()) api.on("select", () => { setCurrent(api.selectedScrollSnap()) }) }, [api]) return (
{items.map((item, index) => ( {item.content} ))} {/* Custom indicators */}
{Array.from({ length: count }).map((_, index) => (

Slide {current + 1} of {count}

) } ``` ## Keyboard | Key | Action | |-----|--------| | ArrowLeft | Previous slide (horizontal) | | ArrowRight | Next slide (horizontal) | ## Accessibility - Root has `role="region"` and `aria-roledescription="carousel"` - Items have `role="group"` and `aria-roledescription="slide"` - Navigation buttons include screen reader text ("Previous slide", "Next slide") - Buttons automatically disabled when navigation not available - Full keyboard support with arrow key navigation ## useCarousel Hook Access carousel context in child components: ```tsx function CustomCarouselControl() { const { api, scrollPrev, scrollNext, canScrollPrev, canScrollNext } = useCarousel() return (
) } ``` ## CarouselApi Methods Key methods available via `setApi` callback: | Method | Description | |--------|-------------| | `scrollTo(index)` | Scroll to specific slide | | `scrollPrev()` | Scroll to previous slide | | `scrollNext()` | Scroll to next slide | | `canScrollPrev()` | Check if can scroll backward | | `canScrollNext()` | Check if can scroll forward | | `selectedScrollSnap()` | Get current slide index | | `scrollSnapList()` | Get array of all snap points | | `on(event, callback)` | Subscribe to events (select, init, reInit) | Full API: https://www.embla-carousel.com/api/ ## Related - Card - Common carousel item container - Button - Used for navigation controls --- # Chart Flexible chart container built on Recharts with theming, configuration, and accessibility support for all chart types. ## Import ```tsx import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent, type ChartConfig, } from "@neynar/ui/chart" ``` ## Anatomy ```tsx } /> } /> ``` ## Components | Component | Description | |-----------|-------------| | ChartContainer | Root container providing responsive layout, theme context, and CSS variables | | ChartTooltip | Re-export of Recharts Tooltip for positioning (use with ChartTooltipContent) | | ChartTooltipContent | Custom tooltip content with series indicators and formatting | | ChartLegend | Re-export of Recharts Legend for positioning (use with ChartLegendContent) | | ChartLegendContent | Custom legend content with icons and colors | ## Props ### ChartContainer | Prop | Type | Default | Description | |------|------|---------|-------------| | config | ChartConfig | - | Chart configuration defining series labels, colors, and icons | | children | ReactNode | - | Recharts chart component (LineChart, BarChart, AreaChart, etc.) | | className | string | - | Additional classes for sizing (e.g., "h-[400px]") | | id | string | - | Optional unique identifier for the chart | ### ChartConfig The config object maps series keys to their display configuration: ```tsx const config = { revenue: { label: "Revenue", color: "hsl(142 76% 36%)", // Single color }, expenses: { label: "Expenses", theme: { // Theme-aware colors light: "hsl(0 84% 60%)", dark: "hsl(0 72% 50%)", }, icon: TrendingDown, // Optional icon component }, } satisfies ChartConfig ``` Each series can have: - `label`: Display name for tooltips and legends - `color`: Single color for both themes, OR - `theme`: Object with `light` and `dark` colors - `icon`: Optional React component for legend ### ChartTooltipContent | Prop | Type | Default | Description | |------|------|---------|-------------| | indicator | "dot" \| "line" \| "dashed" | "dot" | Visual indicator style for series | | hideLabel | boolean | false | Hide the tooltip label (x-axis value) | | hideIndicator | boolean | false | Hide series color indicators | | nameKey | string | - | Override key for series names | | labelKey | string | - | Override key for tooltip label | | formatter | function | - | Custom value formatter | | labelFormatter | function | - | Custom label formatter | ### ChartLegendContent | Prop | Type | Default | Description | |------|------|---------|-------------| | hideIcon | boolean | false | Hide series icons, showing only color squares | | nameKey | string | - | Override key for series names | | verticalAlign | "top" \| "bottom" | "bottom" | Legend position | ## Data Attributes | Attribute | Applied To | Description | |-----------|------------|-------------| | data-slot | ChartContainer | Always "chart" | | data-chart | ChartContainer | Unique chart ID for CSS variable scoping | ## Chart Types The container works with all Recharts chart types: - **LineChart**: Trends over time - **BarChart**: Comparing values (vertical or horizontal) - **AreaChart**: Filled regions showing volume - **PieChart**: Proportional data - **RadialBarChart**: Circular progress or gauges ## Examples ### Basic Line Chart ```tsx import { Line, LineChart, XAxis, YAxis } from "recharts" const data = [ { month: "Jan", value: 2400 }, { month: "Feb", value: 1398 }, { month: "Mar", value: 9800 }, ] const config = { value: { label: "Revenue", color: "var(--chart-1)", }, } satisfies ChartConfig } /> ``` ### Multi-Series with Legend ```tsx import { Area, AreaChart, XAxis, YAxis } from "recharts" const data = [ { month: "Jan", revenue: 12400, expenses: 8200 }, { month: "Feb", revenue: 18200, expenses: 9100 }, ] const config = { revenue: { label: "Revenue", color: "hsl(142 76% 36%)" }, expenses: { label: "Expenses", color: "hsl(0 84% 60%)" }, } satisfies ChartConfig } /> } /> ``` ### Bar Chart with Custom Tooltip ```tsx import { Bar, BarChart, XAxis, YAxis } from "recharts" const config = { requests: { label: "API Requests", color: "var(--chart-1)" }, } satisfies ChartConfig [value.toLocaleString(), "Requests"]} /> } /> ``` ### Theme-Aware Colors ```tsx const config = { active: { label: "Active Users", theme: { light: "hsl(220 70% 50%)", dark: "hsl(220 70% 60%)", }, }, } satisfies ChartConfig {/* Color automatically switches with theme */} ``` ### Tooltip Indicators ```tsx // Dot indicator (default) } /> // Line indicator } /> // Dashed indicator } /> ``` ## Color Variables ChartContainer generates CSS variables for each config key: ```tsx const config = { revenue: { color: "hsl(142 76% 36%)" }, } // Generates: --color-revenue: hsl(142 76% 36%) // Use in charts: var(--color-revenue) ``` Recharts built-in chart colors are also available: - `var(--chart-1)` through `var(--chart-5)`: Pre-defined theme colors ## Accessibility - Uses Recharts' `accessibilityLayer` prop for keyboard navigation - Tooltips provide screen reader-friendly descriptions of data points - Color indicators supplement with text labels in tooltips/legends - CSS variables respect color mode preferences ## Best Practices - Set explicit height via className (e.g., "h-[400px]") - default is aspect-video - Use `satisfies ChartConfig` for type-safe configuration - Reference colors with `var(--color-{key})` to match config - Include `accessibilityLayer` prop on Recharts components - Format large numbers in tooltips with `formatter` prop - Use theme-aware colors for charts that will be viewed in both modes ## Related - [Recharts Documentation](https://recharts.org/) - Full chart component reference - Card - Container for chart sections with headers - Tooltip - Related tooltip pattern for non-chart UI --- # Checkbox Binary selection component for forms, settings, and multi-select interfaces. ## Import ```tsx import { Checkbox } from "@neynar/ui/checkbox" ``` ## Anatomy ```tsx ``` The component includes a built-in indicator with check icon - no need for separate child components. ## Props All Base UI Checkbox.Root props are supported. Common props: | Prop | Type | Default | Description | |------|------|---------|-------------| | checked | boolean | - | Controlled checked state | | defaultChecked | boolean | false | Initial checked state (uncontrolled) | | onCheckedChange | (checked: boolean) => void | - | Called when state changes | | disabled | boolean | false | Prevents user interaction | | required | boolean | false | Must be checked before form submission | | name | string | - | Form field name | | value | string | - | Form value when checked | | indeterminate | boolean | false | Show indeterminate/mixed state | | readOnly | boolean | false | Prevents state changes but shows value | ### Styling Props | Prop | Type | Description | |------|------|-------------| | className | string | Additional CSS classes | ## Data Attributes Use these for custom styling: | Attribute | When Present | |-----------|--------------| | data-checked | Checkbox is checked | | data-unchecked | Checkbox is unchecked | | data-disabled | Component is disabled | | data-readonly | Component is readonly | | data-required | Field is required | | data-invalid | Validation failed (with Field) | | data-focused | Checkbox has focus | ## Examples ### Basic Usage ```tsx ``` ### Controlled State ```tsx function NewsletterSubscription() { const [subscribed, setSubscribed] = useState(false) return ( ) } ``` ### With Label Component ```tsx import { Label } from "@neynar/ui/label" ``` ### With Description ```tsx ``` ### Form Integration ```tsx
Preferences
``` ### Disabled State ```tsx ``` ### Permission Settings Example ```tsx function APIPermissions() { const [permissions, setPermissions] = useState({ readAccess: true, writeAccess: false, adminAccess: false, }) const togglePermission = (key: keyof typeof permissions) => { setPermissions(prev => ({ ...prev, [key]: !prev[key] })) } return (
) } ``` ## Keyboard | Key | Action | |-----|--------| | Space | Toggle checked state | | Enter | Toggle checked state (in forms) | | Tab | Move focus to next element | | Shift + Tab | Move focus to previous element | ## Accessibility - Renders semantic HTML with hidden native checkbox input - Full keyboard navigation support - Proper ARIA attributes automatically applied - Works with form labels via native label element - Supports required field validation - Focus visible states for keyboard users ## Styling Notes - Size: 16px × 16px (size-4) - Border radius: 4px - Checked state shows primary color background - Includes focus ring on keyboard focus - Disabled state reduces opacity to 50% - Peer class allows styling adjacent elements based on state ## Related - [Label](./label.llm.md) - Accessible labels for form fields - [Switch](./switch.llm.md) - Alternative for on/off toggles - [Radio Group](./radio-group.llm.md) - Single selection from multiple options --- # Code Inline code component with monospace font and subtle background. ## Import ```tsx import { Code } from "@neynar/ui/typography" ``` ## Usage ```tsx Use the useState hook for local state. ``` ## Props | Prop | Type | Default | Description | |------|------|---------|-------------| | color | "default" \| "muted" \| "subtle" \| "destructive" \| "success" \| "warning" \| "info" | "default" | Text color | | className | string | - | Additional CSS classes | ## Examples ### Basic ```tsx npm install @neynar/ui ``` ### Inline with Text ```tsx The Button component accepts a variant prop. ``` ### With Colors ```tsx error.message response.data ``` ### In Lists ```tsx
  • variant - Visual style
  • size - Component size
  • disabled - Disable interaction
``` ## Styling Default styling: - Monospace font (`font-mono`) - Small text size (`text-sm`) - Muted background (`bg-muted`) - Rounded corners - Horizontal padding ## Note For multi-line code blocks with syntax highlighting, use a dedicated solution like `prism-react-renderer` or `shiki`. ## Related - [Text](./text.llm.md) - Paragraph text - [Kbd](./kbd.llm.md) - Keyboard shortcuts --- # Collapsible Expandable sections that show and hide content with smooth animations. ## Import ```tsx import { Collapsible, CollapsibleTrigger, CollapsibleContent } from "@neynar/ui/collapsible" ``` ## Anatomy ```tsx ``` ## Components | Component | Description | |-----------|-------------| | Collapsible | Root container, manages open/closed state | | CollapsibleTrigger | Button that toggles the content visibility | | CollapsibleContent | Panel that shows/hides with animation | ## Props ### Collapsible | Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | - | Controlled open state | | defaultOpen | boolean | - | Initial open state (uncontrolled) | | onOpenChange | (open: boolean, details: object) => void | - | Called when open state changes | | disabled | boolean | - | Disables all interactions | | className | string \| function | - | CSS class or function returning class based on state | | style | CSSProperties \| function | - | Style object or function returning styles based on state | | render | ReactElement \| function | - | Custom render element or function | ### CollapsibleTrigger | Prop | Type | Default | Description | |------|------|---------|-------------| | nativeButton | boolean | true | Whether to render a native ` Settings {open ? : }
Advanced settings panel

) } ``` ### Settings Panel with Icons ```tsx

API Settings

Configure rate limits

``` ### Multiple Independent Sections ```tsx function AdvancedSettings() { return (
API Settings
API configuration
Security
Security settings
) } ``` ### Nested Collapsibles ```tsx Integrations
Email Integration
Configure email settings
Webhooks
Configure webhooks
``` ### Animated Chevron with Data Attributes ```tsx Expand Section
Content
``` ### Disabled State ```tsx Disabled Section
Cannot be opened
``` ## Keyboard | Key | Action | |-----|--------| | Space | Toggle when trigger is focused | | Enter | Toggle when trigger is focused | ## Accessibility - Trigger automatically receives `role="button"` and proper ARIA attributes - Content visibility controlled with proper ARIA states - Focus management handled automatically - Keyboard navigation fully supported with Space/Enter keys - Use `hiddenUntilFound` prop to enable browser's Ctrl+F/Cmd+F page search to find and expand collapsed content ## Related - [Accordion](/components/accordion.llm.md) - For mutually exclusive collapsible sections - [Dialog](/components/dialog.llm.md) - For modal content overlays - [Tabs](/components/tabs.llm.md) - For switching between different views --- # Combobox Searchable select component supporting single and multi-select with optional chips UI. ## Import ```tsx import { Combobox, ComboboxInput, ComboboxContent, ComboboxList, ComboboxItem, ComboboxGroup, ComboboxLabel, ComboboxEmpty, ComboboxSeparator, ComboboxChips, ComboboxChip, ComboboxChipsInput, useComboboxAnchor, } from "@neynar/ui/combobox" ``` ## Anatomy ### Single Select ```tsx ``` ### Multi-Select with Chips ```tsx const anchorRef = useComboboxAnchor() ``` ## Components | Component | Description | |-----------|-------------| | Combobox | Root component managing state (doesn't render element) | | ComboboxInput | Text input with optional trigger/clear buttons for single-select | | ComboboxContent | Popup container with automatic portal and positioning | | ComboboxList | Scrollable container for items with overflow handling | | ComboboxItem | Selectable item with check indicator when selected | | ComboboxChips | Container for selected chips in multi-select mode | | ComboboxChip | Individual chip with optional remove button | | ComboboxChipsInput | Text input for multi-select (use inside ComboboxChips) | | ComboboxGroup | Groups related items together | | ComboboxLabel | Label for item groups | | ComboboxEmpty | Message shown when no results match search | | ComboboxSeparator | Visual divider between groups | | useComboboxAnchor | Hook returning ref for anchoring popup to chips | ## Props ### Combobox | Prop | Type | Default | Description | |------|------|---------|-------------| | value | string \| string[] \| null | - | Controlled selected value(s) | | onValueChange | (value) => void | - | Called when selection changes | | defaultValue | string \| string[] \| null | - | Uncontrolled initial value | | inputValue | string | - | Controlled input text value | | onInputValueChange | (value) => void | - | Called when input text changes | | defaultInputValue | string | - | Uncontrolled initial input text | | multiple | boolean | false | Enable multi-select mode | | disabled | boolean | false | Disable all interactions | | open | boolean | - | Controlled open state | | onOpenChange | (open) => void | - | Called when popup opens/closes | | openOnInputClick | boolean | true | Whether clicking input opens popup | ### ComboboxInput Single-select input wrapped in InputGroup with optional addons. | Prop | Type | Default | Description | |------|------|---------|-------------| | showTrigger | boolean | true | Show dropdown trigger button | | showClear | boolean | false | Show clear button when value selected | | placeholder | string | - | Input placeholder text | | disabled | boolean | false | Disable input | ### ComboboxContent | Prop | Type | Default | Description | |------|------|---------|-------------| | side | 'top' \| 'bottom' \| 'left' \| 'right' | 'bottom' | Popup placement side | | align | 'start' \| 'center' \| 'end' | 'start' | Popup alignment | | sideOffset | number | 6 | Distance from anchor | | alignOffset | number | 0 | Alignment adjustment | | anchor | RefObject | - | Ref to anchor element (for chips mode) | **Anchoring for Multi-Select:** ```tsx const anchorRef = useComboboxAnchor() ... ... ``` ### ComboboxItem | Prop | Type | Default | Description | |------|------|---------|-------------| | value | any | - | **Required**. Unique value identifying this item | | disabled | boolean | false | Disable item interaction | | onClick | MouseEventHandler | - | Click handler for item selection | ### ComboboxChips Container for chips in multi-select mode. Must be used with `useComboboxAnchor()` hook. | Prop | Type | Default | Description | |------|------|---------|-------------| | ref | RefObject | - | Pass ref from useComboboxAnchor() | | children | ReactNode | - | ComboboxChip and ComboboxChipsInput elements | ### ComboboxChip | Prop | Type | Default | Description | |------|------|---------|-------------| | showRemove | boolean | true | Show X button to remove chip | | children | ReactNode | - | Chip content (usually text/icon) | ### ComboboxChipsInput | Prop | Type | Default | Description | |------|------|---------|-------------| | placeholder | string | - | Input placeholder text | ## Data Attributes ### ComboboxInput - `data-popup-open` - Popup is open - `data-disabled` - Input is disabled - `data-focused` - Input has focus ### ComboboxContent - `data-open` - Popup is open (triggers animations) - `data-closed` - Popup is closed (triggers animations) - `data-side` - Current placement side (top/bottom/left/right) - `data-chips` - Anchored to chips container (multi-select) ### ComboboxItem - `data-selected` - Item is selected - `data-highlighted` - Item is keyboard highlighted - `data-disabled` - Item is disabled ### ComboboxList - `data-empty` - List has no items (shows ComboboxEmpty) ## Examples ### Basic Single Select ```tsx function FrameworkPicker() { const [value, setValue] = useState(null) const frameworks = [ { value: "next", label: "Next.js" }, { value: "react", label: "React" }, { value: "vue", label: "Vue" }, ] return ( setValue(v as string)}> No frameworks found {frameworks.map((fw) => ( {fw.label} ))} ) } ``` ### Multi-Select with Chips ```tsx function TagPicker() { const [tags, setTags] = useState([]) const anchorRef = useComboboxAnchor() const availableTags = ["react", "typescript", "nextjs", "tailwind"] return ( setTags(v as string[])}> {tags.map((tag) => ( {tag} ))} No tags found {availableTags.map((tag) => ( {tag} ))} ) } ``` ### Grouped Items with Labels ```tsx function FoodPicker() { const [value, setValue] = useState(null) return ( setValue(v as string)}> No food found Fruits Apple Banana Vegetables Carrot Broccoli ) } ``` ### Items with Icons and Metadata ```tsx function ChannelPicker() { const [channel, setChannel] = useState(null) const channels = [ { id: "1", name: "farcaster", subscribers: 89450 }, { id: "2", name: "base", subscribers: 45230 }, ] return ( setChannel(v as string)}> No channels found {channels.map((ch) => (
/{ch.name}
{ch.subscribers.toLocaleString()} subscribers
))}
) } ``` ### Without Trigger Button ```tsx Item 1 ``` ## Keyboard Interactions | Key | Action | |-----|--------| | ArrowDown | Highlight next item (opens popup if closed) | | ArrowUp | Highlight previous item (opens popup if closed) | | Enter | Select highlighted item and close popup | | Escape | Close popup and clear highlight | | Home | Highlight first item | | End | Highlight last item | | Tab | Close popup and move focus | | Backspace | Remove last chip (multi-select, when input empty) | ## Accessibility - ARIA combobox pattern with `role="combobox"` on input - `aria-expanded` indicates popup state - `aria-controls` links input to popup listbox - `aria-activedescendant` tracks highlighted item - Items use `role="option"` with `aria-selected` state - Automatic focus management when opening/closing - Screen readers announce selection changes and item counts - Keyboard navigation follows ARIA authoring practices ## Related - [Select](/components/select.llm.md) - Simple dropdown without search - [Input](/components/input.llm.md) - Basic text input - [Command](/components/command.llm.md) - Command palette pattern --- # Command Fast, accessible command palette with keyboard navigation and fuzzy search built on cmdk. ## Import ```tsx import { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut, } from "@neynar/ui/command" ``` ## Anatomy ```tsx No results found. Dashboard ⌘D Create ``` ## Components | Component | Description | |-----------|-------------| | Command | Root container with fuzzy search and keyboard navigation | | CommandDialog | Command wrapped in modal dialog, typical for ⌘K palettes | | CommandInput | Search input with integrated search icon | | CommandList | Scrollable container for groups/items (max-h-72) | | CommandEmpty | Displayed when search returns no results | | CommandGroup | Groups related items with optional heading | | CommandItem | Selectable item, automatically shows check icon when selected | | CommandSeparator | Visual divider between groups | | CommandShortcut | Keyboard shortcut label, auto-aligned right | ## Props ### Command Extends cmdk's Command component. | Prop | Type | Default | Description | |------|------|---------|-------------| | label | string | - | Accessible label for screen readers | | value | string | - | Controlled selected value | | onValueChange | (value: string) => void | - | Called when selection changes | | filter | (value: string, search: string) => number | - | Custom filter function (0-1 score) | | className | string | - | Additional CSS classes | ### CommandDialog Wraps Command in a modal dialog with portal and overlay. | Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | - | Controlled open state | | onOpenChange | (open: boolean) => void | - | Called when open state changes | | title | string | "Command Palette" | Dialog title (screen readers only) | | description | string | "Search for a command..." | Dialog description (screen readers only) | | showCloseButton | boolean | false | Show X button in top-right corner | | className | string | - | Additional CSS classes for content | Title and description are visually hidden but read by screen readers for accessibility. ### CommandInput | Prop | Type | Default | Description | |------|------|---------|-------------| | placeholder | string | - | Input placeholder text | | value | string | - | Controlled input value | | onValueChange | (search: string) => void | - | Called when input changes | | className | string | - | Additional CSS classes | Search icon is automatically included on the right side of the input. ### CommandList | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | Automatically handles scrolling with max height of 18rem (72). Hidden scrollbar for clean appearance. ### CommandEmpty | Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | - | Content shown when no results | | className | string | - | Additional CSS classes | Automatically displayed when search returns no matching items. ### CommandGroup | Prop | Type | Default | Description | |------|------|---------|-------------| | heading | string | - | Optional group label | | className | string | - | Additional CSS classes | ### CommandItem | Prop | Type | Default | Description | |------|------|---------|-------------| | value | string | - | Unique value (auto-inferred from textContent if omitted) | | keywords | string[] | - | Additional search keywords | | onSelect | (value: string) => void | - | Called when item is selected | | disabled | boolean | - | Disable selection | | className | string | - | Additional CSS classes | Check icon automatically appears when item is selected (hidden if CommandShortcut is present). ### CommandShortcut | Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | - | Shortcut text (e.g., "⌘K") | | className | string | - | Additional CSS classes | Automatically positioned at the end of the command item and hides the check icon. ### CommandSeparator | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | ## Data Attributes ### CommandItem | Attribute | When Present | |-----------|--------------| | data-selected | Item is currently highlighted/active | | data-disabled | Item is disabled (disabled={true}) | | data-checked | Item is marked as checked/selected | Use these for custom styling based on item state. ## Examples ### Basic Command Palette ```tsx function QuickActions() { const [open, setOpen] = useState(false) useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === "k" && (e.metaKey || e.ctrlKey)) { e.preventDefault() setOpen((open) => !open) } } document.addEventListener("keydown", down) return () => document.removeEventListener("keydown", down) }, []) return ( No results found. console.log("Dashboard")}> Dashboard ⌘D console.log("Settings")}> Settings ⌘, ) } ``` ### Multiple Groups with Separators ```tsx No results found. Dashboard Analytics Profile Preferences ``` ### Controlled with Loading States ```tsx function CommandWithLoading() { const [open, setOpen] = useState(false) const [loading, setLoading] = useState(false) const [selectedCommand, setSelectedCommand] = useState(null) const handleSelect = (value: string) => { setSelectedCommand(value) setLoading(true) // Simulate async action setTimeout(() => { setLoading(false) setOpen(false) setSelectedCommand(null) }, 1000) } return ( No results found. handleSelect("create")} disabled={loading && selectedCommand === "create"} > {loading && selectedCommand === "create" ? ( ) : ( )} Create API Key ⌘N ) } ``` ### Custom Empty State ```tsx

No results found.

Try a different search term or check the docs.

Getting Started API Reference
``` ### Without Dialog (Inline) ```tsx No results found. New File New User ``` ## Keyboard | Key | Action | |-----|--------| | ⌘K / Ctrl+K | Open command palette (app implementation) | | ↓ / ↑ | Navigate items | | Enter | Select highlighted item | | Escape | Close dialog | | Home / End | Jump to first/last item | | Type | Filter items by fuzzy search | ## Accessibility - Full keyboard navigation with arrow keys and Enter - ARIA attributes automatically applied to items and groups - Screen reader support for search input and results count - Focus management when opening/closing dialog - DialogTitle and DialogDescription provided for screen readers (visually hidden) - Disabled items marked with `aria-disabled` and cannot be selected ## Related - [Dialog](/Users/bobringer/work/neynar/worktrees/frontend-monorepo/neyn-8162-nas-baseui-upgrade./dialog.llm.md) - Command dialog uses Dialog internally - [Input Group](/Users/bobringer/work/neynar/worktrees/frontend-monorepo/neyn-8162-nas-baseui-upgrade./input-group.llm.md) - CommandInput uses InputGroup for icon layout --- # ContextMenu Right-click context menu with support for nested submenus, checkboxes, radio groups, keyboard shortcuts, and destructive actions. ## Import ```tsx import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuRadioGroup, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuGroup, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, } from "@neynar/ui/context-menu" ``` ## Anatomy ```tsx Right-click target Section Action Toggle Option More Nested action ``` ## Components | Component | Description | |-----------|-------------| | ContextMenu | Root container, manages open/closed state | | ContextMenuTrigger | Element that opens menu on right-click | | ContextMenuContent | Menu popup with automatic portal and positioning | | ContextMenuItem | Interactive menu action | | ContextMenuCheckboxItem | Menu item with checkbox state | | ContextMenuRadioItem | Menu item for mutually exclusive selection | | ContextMenuRadioGroup | Container for radio items | | ContextMenuLabel | Non-interactive section label | | ContextMenuSeparator | Visual divider between items | | ContextMenuShortcut | Keyboard shortcut hint (right-aligned) | | ContextMenuGroup | Groups related items | | ContextMenuSub | Root for nested submenu | | ContextMenuSubTrigger | Item that opens submenu | | ContextMenuSubContent | Submenu popup content | ## Props ### ContextMenu | Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | - | Controlled open state | | onOpenChange | (open: boolean) => void | - | Called when open state changes | | defaultOpen | boolean | false | Uncontrolled initial open state | ### ContextMenuContent Automatically renders portal and positioner. | Prop | Type | Default | Description | |------|------|---------|-------------| | align | "start" \| "end" \| "center" | "start" | Alignment relative to trigger | | alignOffset | number | 4 | Offset from alignment edge (px) | | side | "top" \| "right" \| "bottom" \| "left" | "right" | Which side to position menu | | sideOffset | number | 0 | Offset from trigger (px) | ### ContextMenuItem | Prop | Type | Default | Description | |------|------|---------|-------------| | variant | "default" \| "destructive" \| "success" \| "warning" \| "info" | "default" | Visual style variant | | inset | boolean | false | Extra left padding for alignment | | disabled | boolean | false | Disables interaction | | closeOnClick | boolean | true | Close menu when clicked | ### ContextMenuCheckboxItem | Prop | Type | Default | Description | |------|------|---------|-------------| | checked | boolean \| "indeterminate" | - | Controlled checked state | | onCheckedChange | (checked: boolean) => void | - | Called when checked changes | | disabled | boolean | false | Disables interaction | ### ContextMenuRadioGroup | Prop | Type | Default | Description | |------|------|---------|-------------| | value | string | - | Controlled selected value | | onValueChange | (value: string) => void | - | Called when selection changes | ### ContextMenuRadioItem | Prop | Type | Default | Description | |------|------|---------|-------------| | value | string | - | Value for this radio option | | disabled | boolean | false | Disables interaction | ### ContextMenuLabel | Prop | Type | Default | Description | |------|------|---------|-------------| | inset | boolean | false | Extra left padding for alignment | ### ContextMenuSubTrigger | Prop | Type | Default | Description | |------|------|---------|-------------| | inset | boolean | false | Extra left padding for alignment | Automatically includes chevron icon. ## Data Attributes | Attribute | When Present | Applied To | |-----------|--------------|------------| | data-open | Menu is open | ContextMenuContent | | data-closed | Menu is closed | ContextMenuContent | | data-highlighted | Item is keyboard-focused | ContextMenuItem, ContextMenuSubTrigger | | data-disabled | Item is disabled | ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem | | data-inset | Inset prop is true | ContextMenuLabel, ContextMenuItem, ContextMenuSubTrigger | | data-side | Position side | ContextMenuContent | ## Variants ContextMenuItem supports semantic variants: | Variant | Use Case | |---------|----------| | default | Standard actions | | destructive | Delete, remove, irreversible actions | | success | Approve, confirm actions | | warning | Actions requiring caution | | info | Informational actions | ## Examples ### Basic Context Menu ```tsx
Right-click here
Edit Copy Delete
``` ### With Keyboard Shortcuts ```tsx Edit ⌘E Copy ⌘C ``` ### Grouped Items with Labels ```tsx Actions Edit Copy Organization Add to Favorites Pin ``` ### Checkbox Items ```tsx function ViewOptions() { const [showGrid, setShowGrid] = useState(true) const [showLabels, setShowLabels] = useState(false) return ( View Options Show Grid Show Labels ) } ``` ### Radio Group ```tsx function SortMenu() { const [sortBy, setSortBy] = useState("name") return ( Sort By Name Date Size ) } ``` ### Nested Submenus ```tsx Edit Copy Share Email Copy Link Download ``` ### Inset Alignment Use `inset` prop to align items that don't have icons with items that do: ```tsx Navigation Profile Account Settings Privacy Settings ``` ### Variant Examples ```tsx Default Action Approve Mark for Review View Details Delete ``` ## Keyboard | Key | Action | |-----|--------| | Space / Enter | Activate focused item | | ArrowDown | Move focus to next item | | ArrowUp | Move focus to previous item | | ArrowRight | Open submenu (when focused on SubTrigger) | | ArrowLeft | Close submenu | | Escape | Close menu | | Tab | Move focus out and close | ## Accessibility - Right-click or Shift+F10 opens context menu - ARIA roles: `menu`, `menuitem`, `menuitemcheckbox`, `menuitemradio` - Focus is trapped within open menu - Screen readers announce menu state, selected items, and keyboard shortcuts - CheckboxItem and RadioItem include proper ARIA checked states ## Related - [DropdownMenu](./dropdown-menu.llm.md) - Click-triggered menu - [Menubar](./menubar.llm.md) - Application menu bar --- # Dialog Modal overlay component for capturing user attention, gathering input, or confirming actions. ## Import ```tsx import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@neynar/ui/dialog" ``` ## Anatomy ```tsx Open Title Description {/* Content */} Cancel ``` ## Components | Component | Description | |-----------|-------------| | Dialog | Root container that manages open state (controlled or uncontrolled) | | DialogTrigger | Button that opens the dialog, supports `render` prop for customization | | DialogContent | Main content container with automatic portal, overlay, and animations | | DialogHeader | Container for title and description with consistent spacing | | DialogTitle | Accessible heading element, required for screen readers | | DialogDescription | Optional description text with support for inline links | | DialogFooter | Action button container, stacks vertically on mobile | | DialogClose | Button that closes the dialog, works with `render` prop | | DialogOverlay | Backdrop overlay with blur effect (auto-included in DialogContent) | | DialogPortal | Portal to document root (auto-included in DialogContent) | ## Props ### Dialog | Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | - | Controlled open state | | onOpenChange | (open: boolean) => void | - | Callback when open state changes | | defaultOpen | boolean | - | Initial open state (uncontrolled) | ### DialogContent Automatically renders into a portal with overlay backdrop. Centered on screen with fade and zoom animations. | Prop | Type | Default | Description | |------|------|---------|-------------| | showCloseButton | boolean | true | Show close button (X) in top-right corner | | className | string | - | Additional CSS classes | ### DialogFooter | Prop | Type | Default | Description | |------|------|---------|-------------| | showCloseButton | boolean | false | Render a "Close" button automatically | | className | string | - | Additional CSS classes | ### Render Prop Pattern DialogTrigger and DialogClose support the `render` prop to customize the underlying element: ```tsx }> Delete Account }> Cancel ``` This allows you to use any component while preserving dialog functionality. ## Data Attributes Applied automatically for styling and animations: | Attribute | When Present | Usage | |-----------|--------------|-------| | data-open | Dialog is open | Styling open state, animations | | data-closed | Dialog is closed | Styling closed state, exit animations | | data-slot | Always | Component identification ("dialog", "dialog-trigger", etc.) | ## Examples ### Basic Dialog ```tsx }>Open Dialog Dialog Title This is a description providing context about the dialog. }>Cancel ``` ### Controlled State ```tsx function ControlledDialog() { const [open, setOpen] = useState(false) return ( <> }>Open Dialog Controlled Dialog State is controlled by React state. ) } ``` ### Form Dialog ```tsx }> Create Project Create New Project Set up a new project to organize your API keys.
}>Cancel
``` ### Destructive Confirmation ```tsx function DeleteConfirmation() { const [isDeleting, setIsDeleting] = useState(false) function handleDelete() { setIsDeleting(true) // Perform delete operation setTimeout(() => setIsDeleting(false), 1500) } return ( }> Revoke Key Revoke API Key? This action cannot be undone. Revoking this key will immediately stop all applications using it from accessing the API. }>Cancel ) } ``` ### Without Close Button For dialogs that require explicit user action: ```tsx }>Forced Action Action Required Please choose one of the options below. This dialog cannot be dismissed without making a selection. }>Option A }>Option B ``` ## Keyboard | Key | Action | |-----|--------| | Escape | Closes the dialog (unless showCloseButton={false}) | | Tab | Cycles through focusable elements within dialog | ## Accessibility - Automatically sets `role="dialog"` and `aria-modal="true"` - DialogTitle automatically provides `aria-labelledby` for the dialog - DialogDescription provides `aria-describedby` when present - Focus is trapped within the dialog when open - Focus returns to trigger element when closed - Escape key closes dialog by default ## Related - **AlertDialog** - For simpler confirmations with pre-styled variants - **Drawer** - For mobile-first slide-in panels - **Popover** - For non-modal contextual overlays - **Sheet** - For side-panel content --- # Drawer A sliding panel that appears from any screen edge (top, bottom, left, right) with drag-to-dismiss support, frosted glass overlay, and flexible composition. ## Import ```tsx import { Drawer, DrawerTrigger, DrawerContent, DrawerHeader, DrawerTitle, DrawerDescription, DrawerFooter, DrawerClose, DrawerPortal, DrawerOverlay, } from "@neynar/ui/drawer" ``` ## Anatomy ```tsx Open Title Description {/* Content */} Cancel ``` ## Components | Component | Description | |-----------|-------------| | Drawer | Root container, manages open/close state | | DrawerTrigger | Button or element that opens the drawer | | DrawerContent | Panel with auto-portal, overlay, and drag handle (bottom drawers) | | DrawerHeader | Header section, centered on top/bottom drawers | | DrawerTitle | Accessible title announced when opened | | DrawerDescription | Accessible description for screen readers | | DrawerFooter | Footer with actions, auto-positioned at bottom | | DrawerClose | Button or element that closes the drawer | | DrawerPortal | Manual portal container (auto-used by DrawerContent) | | DrawerOverlay | Frosted backdrop (auto-rendered by DrawerContent) | ## Props ### Drawer | Prop | Type | Default | Description | |------|------|---------|-------------| | direction | "top" \| "bottom" \| "left" \| "right" | "bottom" | Edge from which drawer slides | | open | boolean | - | Controlled open state | | defaultOpen | boolean | - | Initial open state (uncontrolled) | | onOpenChange | (open: boolean) => void | - | Called when state changes | | modal | boolean | true | Whether to block interaction with content behind | | dismissible | boolean | true | Whether drawer can be closed by clicking outside or dragging | | shouldScaleBackground | boolean | false | Whether to scale background when open | | handleOnly | boolean | false | Only allow dragging from handle (not entire drawer) | ### DrawerContent Automatically renders portal, overlay, and drag handle (bottom drawers only). Content panel is positioned based on `direction` prop: - **bottom**: Slides up from bottom, 80vh max height, rounded top corners - **top**: Slides down from top, 80vh max height, rounded bottom corners - **left**: Slides from left, 75% width (max 24rem), rounded right corners - **right**: Slides from right, 75% width (max 24rem), rounded left corners Extends all standard div props via `React.ComponentProps`. ### DrawerHeader Centered text on top/bottom drawers, left-aligned on side drawers. Extends standard div props. ### DrawerFooter Auto-positioned at bottom with `mt-auto`. Extends standard div props. ### DrawerTrigger / DrawerClose Both support custom rendering via Radix's `asChild` pattern: ```tsx ``` ## Data Attributes | Attribute | When Present | Applied To | |-----------|--------------|------------| | data-slot | Always | All components (for targeting) | | data-vaul-drawer-direction | Always | DrawerContent (value: "top"\|"bottom"\|"left"\|"right") | | data-open | Drawer open | DrawerOverlay (for animations) | | data-closed | Drawer closed | DrawerOverlay (for animations) | ## Examples ### Basic Usage ```tsx Drawer Title This is a description.

Your content here.

``` ### Controlled State ```tsx function ControlledDrawer() { const [open, setOpen] = useState(false) return ( <> Controlled Drawer
) } ``` ### Settings Panel (Right Drawer) ```tsx Notification Settings Manage your notification preferences
``` ### Mobile Navigation (Left Drawer) ```tsx Navigation
``` ### Filter Panel (Bottom Drawer) ```tsx Filter Options Refine your results
``` ### Non-Dismissible Drawer ```tsx Confirm Action This action requires confirmation

You must explicitly confirm or cancel.

``` ### Custom Layout (No Header) ```tsx

Custom Layout

Full control over structure

``` ## Keyboard | Key | Action | |-----|--------| | Escape | Close drawer (if dismissible) | | Tab | Navigate focusable elements | ## Accessibility - Uses Vaul library built on Radix UI primitives for ARIA compliance - `DrawerTitle` sets `aria-labelledby` on content - `DrawerDescription` sets `aria-describedby` on content - Focus trap when modal (default), focus returns to trigger on close - Drag gestures announce state changes to screen readers - Overlay has proper backdrop semantics ## Related - [Dialog](/Users/bobringer/work/neynar/worktrees/frontend-monorepo/neyn-8162-nas-baseui-upgrade./dialog.llm.md) - Modal dialog for desktop-first interactions - [Sheet](https://ui.shadcn.com/docs/components/sheet) - Similar side panel component (if available) - [Popover](/Users/bobringer/work/neynar/worktrees/frontend-monorepo/neyn-8162-nas-baseui-upgrade./popover.llm.md) - Floating content without blocking interaction --- # DropdownMenu A versatile dropdown menu component for displaying contextual actions and selections. ## Import ```tsx import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenuShortcut, } from "@neynar/ui/dropdown-menu" ``` ## Anatomy ```tsx Section Item ``` ## Components | Component | Description | |-----------|-------------| | DropdownMenu | Root container, manages open/close state and keyboard navigation | | DropdownMenuTrigger | Button that opens the menu | | DropdownMenuContent | Content container with automatic portal and positioning | | DropdownMenuItem | Individual menu item with optional variants | | DropdownMenuCheckboxItem | Menu item with checkbox for independent toggles | | DropdownMenuRadioGroup | Container for mutually exclusive radio items | | DropdownMenuRadioItem | Radio item for single selection within a group | | DropdownMenuLabel | Label for grouping related items | | DropdownMenuSeparator | Visual divider between menu sections | | DropdownMenuGroup | Logical container for related items | | DropdownMenuSub | Root container for nested submenus | | DropdownMenuSubTrigger | Trigger for opening a submenu | | DropdownMenuSubContent | Content container for submenu items | | DropdownMenuShortcut | Visual display of keyboard shortcuts | ## Props ### DropdownMenu | Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | - | Controlled open state | | onOpenChange | (open: boolean) => void | - | Called when open state changes | | defaultOpen | boolean | false | Uncontrolled initial open state | ### DropdownMenuTrigger Uses `render` prop pattern for custom trigger elements: ```tsx }> Open Menu ``` ### DropdownMenuContent Automatically renders in a portal with positioning system. | Prop | Type | Default | Description | |------|------|---------|-------------| | align | "start" \| "center" \| "end" | "start" | Horizontal alignment relative to trigger | | alignOffset | number | 0 | Offset in pixels from aligned position | | side | "top" \| "right" \| "bottom" \| "left" | "bottom" | Preferred side to position menu | | sideOffset | number | 4 | Distance in pixels from trigger | ### DropdownMenuItem | Prop | Type | Default | Description | |------|------|---------|-------------| | variant | "default" \| "destructive" \| "success" \| "warning" \| "info" | "default" | Visual style variant | | inset | boolean | false | Add extra left padding for alignment | | disabled | boolean | false | Disable item interaction | ### DropdownMenuCheckboxItem | Prop | Type | Default | Description | |------|------|---------|-------------| | checked | boolean \| "indeterminate" | - | Checked state | | onCheckedChange | (checked: boolean) => void | - | Called when checked state changes | ### DropdownMenuRadioGroup | Prop | Type | Default | Description | |------|------|---------|-------------| | value | string | - | Currently selected value | | onValueChange | (value: string) => void | - | Called when selection changes | ### DropdownMenuRadioItem | Prop | Type | Default | Description | |------|------|---------|-------------| | value | string | - | Value for this radio item | ### DropdownMenuLabel | Prop | Type | Default | Description | |------|------|---------|-------------| | inset | boolean | false | Add extra left padding for alignment with icon items | ### DropdownMenuSubTrigger | Prop | Type | Default | Description | |------|------|---------|-------------| | inset | boolean | false | Add extra left padding for alignment | Automatically displays chevron icon on the right. ### DropdownMenuSubContent Same props as DropdownMenuContent, but with different defaults: | Prop | Type | Default | Description | |------|------|---------|-------------| | side | "top" \| "right" \| "bottom" \| "left" | "right" | Preferred side to position submenu | | alignOffset | number | -3 | Offset for better visual alignment | ## Data Attributes (for styling) | Attribute | When Present | |-----------|--------------| | data-open | Menu or submenu is open | | data-closed | Menu or submenu is closed | | data-highlighted | Item is keyboard-highlighted or hovered | | data-disabled | Item is disabled | | data-inset | Item or label has inset padding | ## Variants DropdownMenuItem supports semantic variants: | Variant | Use Case | |---------|----------| | default | Standard actions | | destructive | Delete, remove, or dangerous actions | | success | Confirmations or positive actions | | warning | Caution-required actions | | info | Informational actions | ## Examples ### Basic Menu ```tsx Edit Duplicate Delete ``` ### User Account Menu ```tsx My Account Profile ⇧⌘P Settings ⌘S Log Out ``` ### Checkbox Items ```tsx function NotificationMenu() { const [notifications, setNotifications] = useState(true) const [marketing, setMarketing] = useState(false) return ( Notifications Push Notifications Marketing Emails ) } ``` ### Radio Group ```tsx function ThemeSelector() { const [theme, setTheme] = useState("system") return ( Theme Light Dark System ) } ``` ### Nested Submenus ```tsx Edit Duplicate Share Email Copy Link Export Delete ``` ### Organized with Groups ```tsx My Account Profile Settings Team Invite Members Team Settings ``` ## Keyboard | Key | Action | |-----|--------| | Space / Enter | Open menu (on trigger) / Activate item (in menu) | | ArrowDown / ArrowUp | Navigate between items | | ArrowRight | Open submenu | | ArrowLeft | Close submenu | | Escape | Close menu | | Tab | Move focus out and close menu | ## Accessibility - Built on Base UI Menu primitives with ARIA menu pattern - Automatic focus management and keyboard navigation - Screen reader announcements for menu state changes - All items are properly labeled and keyboard accessible - Checkbox and radio items announce their checked state - Disabled items are properly marked with `aria-disabled` ## Related - [ContextMenu](/components/context-menu) - Right-click menu with similar structure - [Select](/components/select) - For form-based single selection - [Combobox](/components/combobox) - For searchable selection - [Button](/components/button) - Common trigger element --- # Empty Compound component for displaying empty states with media, messaging, and call-to-action elements. ## Import ```tsx import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, EmptyContent, } from "@neynar/ui/empty" ``` ## Anatomy ```tsx Title Description text ``` ## Components | Component | Description | |-----------|-------------| | Empty | Root container with centered flex layout and dashed border | | EmptyHeader | Vertical stack for media, title, and description | | EmptyMedia | Icon/media container with optional background styling | | EmptyTitle | Large heading text | | EmptyDescription | Supporting text with link styling | | EmptyContent | Action area for buttons or links | ## Props ### Empty Standard div props with centered layout and dashed border. | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | | children | ReactNode | - | Empty state content | ### EmptyHeader Standard div props. Groups media, title, and description. | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | | children | ReactNode | - | Header content (media, title, description) | ### EmptyMedia | Prop | Type | Default | Description | |------|------|---------|-------------| | variant | "default" \| "icon" | "default" | Styling variant | | className | string | - | Additional CSS classes | | children | ReactNode | - | Icon or media element | **Variant Behavior:** - `default`: Transparent background, use with custom-sized icons - `icon`: Muted background with padding, auto-sizes icons to size-6 ### EmptyTitle Standard div props for the title heading. | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | | children | ReactNode | - | Title text | ### EmptyDescription Standard div props with link styling support. | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | | children | ReactNode | - | Description text (supports inline links) | Inline links (`
`) are automatically styled with underlines and hover effects. ### EmptyContent Standard div props for action elements. | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | | children | ReactNode | - | Action buttons or links | ## Data Attributes All components have unique `data-slot` attributes for styling hooks: | Attribute | Component | |-----------|-----------| | data-slot="empty" | Empty | | data-slot="empty-header" | EmptyHeader | | data-slot="empty-icon" | EmptyMedia | | data-slot="empty-title" | EmptyTitle | | data-slot="empty-description" | EmptyDescription | | data-slot="empty-content" | EmptyContent | EmptyMedia also includes: - `data-variant`: Current variant value ## Variants ### Media Variants | Variant | Use Case | |---------|----------| | default | Large custom-sized icons (size-12), transparent background | | icon | Standard icons with muted background, auto-sized to size-6 | ## Examples ### Basic Empty State ```tsx No results found Try adjusting your search terms or filters. ``` ### With Action Button ```tsx No webhooks configured Get started by creating your first webhook to receive real-time notifications. ``` ### With Multiple Actions ```tsx No data sources Connect your first data source to start analyzing data.
``` ### With Link in Description ```tsx No data available Start collecting data by setting up your first integration.{" "}
Learn more ``` ### Large Icon (Default Variant) ```tsx No documents Use the default variant with manually sized icons for larger displays. ``` ### Minimal (No Media) ```tsx No items to display Empty states can work without media icons for simpler contexts. ``` ### Success/Completed State ```tsx All caught up! You have no new notifications. We'll notify you when something happens. ``` ## Composition Patterns ### Minimal Pattern Header with media and title only (no description, no actions). ### Standard Pattern Header with media, title, and description (no actions). ### Action Pattern Complete header + content section with one or more buttons. ### Text-Only Pattern Header with title and description, no media or actions. ## Accessibility - Use semantic HTML elements for text content - Ensure sufficient color contrast for text and icons - Keep empty state messages clear and actionable - Provide keyboard-accessible actions when present - Consider loading states vs empty states for better UX ## Related - Button - For empty state actions - Alert - For error or warning states - Card - Often contains empty states --- # Field Comprehensive form field system with labels, descriptions, error handling, and flexible layouts for building accessible forms. ## Import ```tsx import { Field, FieldLabel, FieldContent, FieldDescription, FieldError, FieldGroup, FieldSet, FieldLegend, FieldSeparator, FieldTitle, } from "@neynar/ui/field" ``` ## Anatomy ```tsx Label Text Helper text ``` ## Components | Component | Description | |-----------|-------------| | Field | Base field wrapper with orientation support | | FieldLabel | Label for the field input | | FieldContent | Container for input + description + error | | FieldDescription | Helper text providing context | | FieldError | Error message display with auto-deduplication | | FieldGroup | Container for multiple related fields | | FieldSet | Semantic fieldset container for field groups | | FieldLegend | Legend for FieldSet with variant styles | | FieldSeparator | Visual separator between field sections | | FieldTitle | Title text for checkbox/switch fields | ## Props ### Field | Prop | Type | Default | Description | |------|------|---------|-------------| | orientation | "vertical" \| "horizontal" \| "responsive" | "vertical" | Layout orientation | | data-invalid | boolean | - | Apply error styling when true | | data-disabled | boolean | - | Apply disabled styling when true | ### FieldLabel Extends Label component. Use `htmlFor` to associate with input. ### FieldContent Container with consistent spacing for input + description + error. ### FieldDescription Helper text automatically styled for placement. Supports links with underline styling. ### FieldError | Prop | Type | Default | Description | |------|------|---------|-------------| | errors | Array<{ message?: string }> | - | Error objects to display | | children | ReactNode | - | Custom error content | Automatically deduplicates errors by message. Shows single error as text, multiple errors as bulleted list. ### FieldGroup Container for multiple fields with consistent spacing. Provides `@container/field-group` for responsive layouts. ### FieldSet Semantic `
` element for grouping related fields. Use with FieldLegend. ### FieldLegend | Prop | Type | Default | Description | |------|------|---------|-------------| | variant | "legend" \| "label" | "legend" | Visual style variant | ### FieldSeparator | Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | - | Optional label text on separator | ### FieldTitle Title text for fields with embedded controls (checkboxes, switches). Use inside FieldLabel. ## Orientation Variants | Variant | Behavior | |---------|----------| | vertical | Label and input stacked vertically (default) | | horizontal | Label and input side by side | | responsive | Vertical on mobile, horizontal on larger screens (requires FieldGroup) | ## Data Attributes | Attribute | When Present | Used For | |-----------|--------------|----------| | data-invalid | Field has errors | Error styling | | data-disabled | Field is disabled | Disabled styling | | data-orientation | Always | Current orientation value | | data-slot | Always | Component identification | ## Examples ### Basic Field ```tsx Username ``` ### Field with Description ```tsx Email Address We'll never share your email ``` ### Field with Error State ```tsx const [errors, setErrors] = useState({ email: { message: "Invalid email" } }) Email * ``` ### Horizontal Layout with Switch ```tsx
Enable Notifications Receive email updates
``` ### Grouped Fields with Separator ```tsx Name Security Settings Password ``` ### Field Set with Radio Group ```tsx
Event Type
Option One First choice description
Option Two Second choice description
``` ### Multiple Errors ```tsx Password ``` ### Responsive Layout ```tsx Phone Number Vertical on mobile, horizontal on larger screens ``` ### Disabled Field ```tsx Read Only Field This field cannot be modified ``` ## Accessibility - Uses semantic HTML elements (`fieldset`, `legend`, `label`) - FieldError uses `role="alert"` for screen reader announcements - Supports `aria-invalid` on inputs for error states - FieldLabel properly associates with inputs via `htmlFor` - FieldDescription provides additional context without cluttering labels - Disabled state reduces opacity for visual indication ## Best Practices - Always use `htmlFor` on FieldLabel to associate with input `id` - Set `data-invalid` on Field and `aria-invalid` on input for error states - Use FieldContent when you have description or error messages - For checkbox/switch fields, use horizontal orientation with FieldTitle - Use FieldSet/FieldLegend for semantically related field groups - Use FieldGroup for visual grouping without semantic fieldset - Set `data-disabled` on Field when input is disabled - Use FieldSeparator to organize long forms into sections ## Related - [Input](./input.llm.md) - Text input component - [Textarea](./textarea.llm.md) - Multi-line text input - [Checkbox](./checkbox.llm.md) - Checkbox input - [Switch](./switch.llm.md) - Toggle switch - [Radio Group](./radio-group.llm.md) - Radio button group - [Label](./label.llm.md) - Base label component --- # HoverCard Displays rich preview content when hovering over a trigger element. ## Import ```tsx import { HoverCard, HoverCardTrigger, HoverCardContent } from "@neynar/ui/hover-card" ``` ## Anatomy ```tsx Hover over me Preview content appears here ``` ## Components | Component | Description | |-----------|-------------| | HoverCard | Root container, manages hover state and timing | | HoverCardTrigger | Element that triggers the hover card (link, button, text, avatar) | | HoverCardContent | Content container with automatic portal, positioning, and animations | ## Props ### HoverCard Inherits all props from `@base-ui/react/preview-card` Root component. | Prop | Type | Default | Description | |------|------|---------|-------------| | open | boolean | - | Controlled open state | | defaultOpen | boolean | false | Uncontrolled initial open state | | onOpenChange | (open: boolean) => void | - | Called when open state changes | ### HoverCardTrigger Inherits all props from `@base-ui/react/preview-card` Trigger component. | Prop | Type | Default | Description | |------|------|---------|-------------| | render | ReactElement \| function | - | Custom element or render function for the trigger | The `render` prop accepts a ReactElement or function for customization: ```tsx // As a ReactElement Hover}> Content // As a function Link}> Content ``` ### HoverCardContent Automatically renders portal and positioner. Combines props from Popup and Positioner components. | Prop | Type | Default | Description | |------|------|---------|-------------| | side | "top" \| "right" \| "bottom" \| "left" | "bottom" | Which side of trigger to position the card | | sideOffset | number | 4 | Distance from trigger in pixels | | align | "start" \| "center" \| "end" | "center" | How to align relative to the trigger | | alignOffset | number | 4 | Additional alignment axis offset in pixels | | className | string | - | Custom CSS classes (merged with defaults) | | initialFocus | boolean \| RefObject \| function | - | Element to focus when opened | | finalFocus | boolean \| RefObject \| function | - | Element to focus when closed | ## Data Attributes Use these for custom styling and animations: | Attribute | When Present | Description | |-----------|--------------|-------------| | data-open | Card is open | Apply open state styles | | data-closed | Card is closed | Apply closed state styles | | data-side | Always | Value: "top" \| "right" \| "bottom" \| "left" | | data-align | Always | Value: "start" \| "center" \| "end" | | data-starting-style | Opening animation | Initial animation state | | data-ending-style | Closing animation | Final animation state | ## Examples ### User Profile Preview ```tsx
DR

Dan Romero

@dwr

Building Farcaster and Warpcast. Previously VP Eng at Coinbase.

124.5K{" "} followers
428{" "} following
``` ### Simple Text Preview ```tsx

Learn more about{" "} typography

Typography is the art and science of arranging type to make written language clear, visually appealing, and effective.

{" "}in design.

``` ### Positioned Above with Custom Width ```tsx React 19

React 19

Latest version of React with improved concurrent rendering, automatic batching, and new hooks for better performance.

``` ### Rich Content with Avatar and Actions ```tsx
FC

Farcaster Protocol

A sufficiently decentralized social network.

``` ### Multiple Triggers in Feed ```tsx
{posts.map((post) => (
{post.time}

{post.content}

))}
``` ## Keyboard HoverCard is primarily mouse/touch interaction, but keyboard navigation is supported: | Key | Action | |-----|--------| | Hover | Open hover card after delay | | Mouse leave | Close hover card after delay | | Tab | Navigate through interactive elements inside content | | Escape | Close hover card immediately | ## Accessibility - Automatically manages focus when opening/closing - Content is rendered in a portal to avoid z-index issues - Uses ARIA attributes for screen reader support - Supports keyboard navigation for interactive content - Trigger remains accessible and keyboard-navigable - Respects user's motion preferences for animations ## Styling Notes - Default width is `w-64` (256px), customize with `className` - Uses frosted glass effect with `bg-popover` at 75% opacity and backdrop blur - Includes entry/exit animations via data attributes - Side-specific slide animations (slides from opposite direction) - Respects `--transform-origin` CSS variable for proper animation origins - Compatible with all themes (classic, frosted, sketch) ## Related - [Tooltip](/components/tooltip.llm.md) - For simple text hints (non-interactive) - [Popover](/components/popover.llm.md) - For click-triggered interactive content - [Dialog](/components/dialog.llm.md) - For modal overlays - [Avatar](/components/avatar.llm.md) - Commonly used in hover card triggers --- # InputGroup Compound component for adding icons, text, buttons, or keyboard shortcuts to inputs and textareas. ## Import ```tsx import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupText, InputGroupTextarea, } from "@neynar/ui/input-group" ``` ## Anatomy ```tsx ``` ## Components | Component | Description | |-----------|-------------| | InputGroup | Root container that manages unified border, focus ring, and validation states | | InputGroupInput | Input element without its own border/ring (integrated with group) | | InputGroupTextarea | Textarea element without its own border/ring (integrated with group) | | InputGroupAddon | Container for addon content (icons, text, buttons) at start/end | | InputGroupButton | Button sized for use within addons | | InputGroupText | Text label/prefix/suffix for addons (e.g., "https://", "USD") | ## Props ### InputGroup Root container. Extends standard `
` props. | Prop | Type | Default | Description | |------|------|---------|-------------| | className | string | - | Additional CSS classes | | data-disabled | boolean | - | Disables addon opacity when set to "true" | ### InputGroupAddon | Prop | Type | Default | Description | |------|------|---------|-------------| | align | "inline-start" \| "inline-end" \| "block-start" \| "block-end" | "inline-start" | Position of addon relative to input | | className | string | - | Additional CSS classes | Clicking the addon automatically focuses the input (unless clicking a button inside). ### InputGroupButton Extends Button component props with custom sizes. | Prop | Type | Default | Description | |------|------|---------|-------------| | size | "xs" \| "sm" \| "icon-xs" \| "icon-sm" | "xs" | Button size optimized for input groups | | variant | ButtonVariant | "ghost" | Button visual style | | type | "button" \| "submit" \| "reset" | "button" | HTML button type | | className | string | - | Additional CSS classes | ### InputGroupText Extends standard `` props. Use for text prefixes/suffixes like "https://", "@", "/", "USD", "MB". ### InputGroupInput Extends standard `` props. Inherits all Input component functionality but without its own border/focus ring. ### InputGroupTextarea Extends standard `