# Corsair UI Every item's docs. Each item's source is at https://corsairui.vercel.app/llms/.md. ## Getting started Works in React 19 projects with Tailwind CSS 3.4 or 4 that have a `components.json` (`pnpm dlx shadcn@latest init`). @corsair-ui is listed in the shadcn registry directory, so there is nothing to configure: the CLI finds it and adds it to `components.json` on the first install. ```bash pnpm dlx shadcn@latest add @corsair-ui/theme # once: colours, radius and base styles pnpm dlx shadcn@latest add @corsair-ui/button # then any item ``` Items are copied into the project as source (`components/ui`, `components/blocks`, `hooks`, `lib`) and are meant to be edited and restyled there. Brand it by overriding `--primary`, `--primary-foreground` and `--ring`; dark mode follows the `dark` class on ``. ## Theme Corsair colour, radius and base styles for light and dark mode. Every component relies on these variables; override --primary, --primary-foreground and --ring to apply your brand. Docs: https://corsairui.vercel.app/docs/theme ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/theme ``` ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - The Themes page (/themes) builds these variables for another base, brand colour and radius, checks the contrast and gives the CSS to paste after the theme. - Presets install the theme with a palette in one command: theme-blue, theme-violet, theme-rose, theme-emerald and theme-amber. ### Accessibility - Foreground and background pairs are picked for contrast in both light and dark mode. - Focus rings use --ring on every interactive component. ### Examples On the docs page: Colours, Radius, Branding. The usage examples in the source comments (https://corsairui.vercel.app/llms/theme.md) are the reference. ## Theme: Amber The Corsair theme on a stone base with an amber primary and square corners. Installs the theme first, then sets its colours and radius; every text pair meets WCAG AA. Docs: https://corsairui.vercel.app/docs/theme-amber ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/theme-amber ``` Also adds: `theme`. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - It installs the theme first, then sets these colours and the radius. To change a colour afterwards, edit the variable in your global CSS, or build a new set on the Themes page (/themes). ### Accessibility - Every text pair (text, muted text, primary and destructive buttons, cards) reaches WCAG AA, 4.5:1, in light and dark mode. ### Examples On the docs page: Light and dark. The usage examples in the source comments (https://corsairui.vercel.app/llms/theme-amber.md) are the reference. ## Theme: Blue The Corsair theme on a slate base with a blue primary. Installs the theme first, then sets its colours and radius; every text pair meets WCAG AA. Docs: https://corsairui.vercel.app/docs/theme-blue ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/theme-blue ``` Also adds: `theme`. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - It installs the theme first, then sets these colours and the radius. To change a colour afterwards, edit the variable in your global CSS, or build a new set on the Themes page (/themes). ### Accessibility - Every text pair (text, muted text, primary and destructive buttons, cards) reaches WCAG AA, 4.5:1, in light and dark mode. ### Examples On the docs page: Light and dark. The usage examples in the source comments (https://corsairui.vercel.app/llms/theme-blue.md) are the reference. ## Theme: Emerald The Corsair theme on a zinc base with an emerald primary. Installs the theme first, then sets its colours and radius; every text pair meets WCAG AA. Docs: https://corsairui.vercel.app/docs/theme-emerald ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/theme-emerald ``` Also adds: `theme`. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - It installs the theme first, then sets these colours and the radius. To change a colour afterwards, edit the variable in your global CSS, or build a new set on the Themes page (/themes). ### Accessibility - Every text pair (text, muted text, primary and destructive buttons, cards) reaches WCAG AA, 4.5:1, in light and dark mode. ### Examples On the docs page: Light and dark. The usage examples in the source comments (https://corsairui.vercel.app/llms/theme-emerald.md) are the reference. ## Theme: Rose The Corsair theme on a stone base with a rose primary. Installs the theme first, then sets its colours and radius; every text pair meets WCAG AA. Docs: https://corsairui.vercel.app/docs/theme-rose ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/theme-rose ``` Also adds: `theme`. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - It installs the theme first, then sets these colours and the radius. To change a colour afterwards, edit the variable in your global CSS, or build a new set on the Themes page (/themes). ### Accessibility - Every text pair (text, muted text, primary and destructive buttons, cards) reaches WCAG AA, 4.5:1, in light and dark mode. ### Examples On the docs page: Light and dark. The usage examples in the source comments (https://corsairui.vercel.app/llms/theme-rose.md) are the reference. ## Theme: Violet The Corsair theme on a zinc base with a violet primary. Installs the theme first, then sets its colours and radius; every text pair meets WCAG AA. Docs: https://corsairui.vercel.app/docs/theme-violet ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/theme-violet ``` Also adds: `theme`. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - It installs the theme first, then sets these colours and the radius. To change a colour afterwards, edit the variable in your global CSS, or build a new set on the Themes page (/themes). ### Accessibility - Every text pair (text, muted text, primary and destructive buttons, cards) reaches WCAG AA, 4.5:1, in light and dark mode. ### Examples On the docs page: Light and dark. The usage examples in the source comments (https://corsairui.vercel.app/llms/theme-violet.md) are the reference. ## Accordion Stacked sections that open and close under their headings, one or several at a time, with a height animation. Docs: https://corsairui.vercel.app/docs/accordion ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/accordion ``` Also adds: `utils`. npm: `@radix-ui/react-accordion`, `clsx`, `lucide-react`, `tailwind-merge`. ### API Built on Radix Accordion (https://www.radix-ui.com/primitives/docs/components/accordion); its props pass through. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `type` | `"single" \| "multiple"` | | One section open at a time, or several. | | `collapsible` | `boolean` | `false` | With type="single": the open section can close again. | | `value / defaultValue / onValueChange` | `string \| string[]` | | The open sections, controlled or not; an array for multiple. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `accordion`, `accordion-content`, `accordion-content-inner`, `accordion-header`, `accordion-icon`, `accordion-item`, `accordion-trigger` ### Accessibility - Each trigger is a button in an h3, announced as expanded or collapsed. - Up and Down move between triggers, Home and End jump; the panel is labelled by its trigger. - Panels open without sliding with prefers-reduced-motion. ### Examples On the docs page: One at a time, Several open. The usage examples in the source comments (https://corsairui.vercel.app/llms/accordion.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/accordion.md ## Alert Callout for important messages on a neutral surface; default, success, warning and destructive tones colour the icon. Docs: https://corsairui.vercel.app/docs/alert ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/alert ``` Also adds: `utils`. npm: `class-variance-authority`, `clsx`, `tailwind-merge`. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `alert`, `alert-description`, `alert-title` State attributes: - `data-variant`: `default` ### Accessibility - role="alert" interrupts screen readers; pass role="status" for messages that are not urgent. - The tone is on the icon only, so the title has to say what happened. ### Examples On the docs page: Tones, Title only. The usage examples in the source comments (https://corsairui.vercel.app/llms/alert.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/alert.md ## Alert Dialog Modal that asks the user to confirm or cancel before an action, with action and cancel buttons. Docs: https://corsairui.vercel.app/docs/alert-dialog ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/alert-dialog ``` Also adds: `utils`, `button`. npm: `@radix-ui/react-alert-dialog`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`. ### API Built on Radix Alert Dialog (https://www.radix-ui.com/primitives/docs/components/alert-dialog); its props pass through. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `AlertDialogAction / AlertDialogCancel` | `button` | | Styled as the default and outline buttons; both close it. | | `overlayClassName` | `string` | | AlertDialogContent: classes for the overlay behind it. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - overlayClassName on AlertDialogContent styles the overlay behind it. data-slot: `alert-dialog`, `alert-dialog-action`, `alert-dialog-cancel`, `alert-dialog-content`, `alert-dialog-description`, `alert-dialog-footer`, `alert-dialog-header`, `alert-dialog-overlay`, `alert-dialog-portal`, `alert-dialog-title`, `alert-dialog-trigger` ### Accessibility - role="alertdialog": for decisions that need an answer. - Focus starts on Cancel, the safe choice. A click outside does not close it; Escape does. ### Examples On the docs page: Confirm a destructive action. The usage examples in the source comments (https://corsairui.vercel.app/llms/alert-dialog.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/alert-dialog.md ## Area Chart One or more series as gradient-filled areas, stacked or overlapping, with a legend and a keyboard slider over the points. Docs: https://corsairui.vercel.app/docs/area-chart ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/area-chart ``` Also adds: `utils`, `use-in-view`, `use-entrance`. npm: `clsx`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `series` | `AreaChartSeries[]` | | Each has name and data (one value per label, missing ones count as 0), and optionally color. | | `labels` | `string[]` | | A label per position. | | `name` | `string` | `the series names` | Accessible name of the chart. | | `stacked` | `boolean` | `false` | Stacks the series instead of overlapping them. | | `curve` | `"smooth" \| "linear"` | `"smooth"` | Smooth never overshoots the points. | | `locale / numberFormat` | `string / Intl.NumberFormatOptions` | `"en-US"` | Number formatting; both can come from a server component. | | `formatValue / formatValueText` | `(value, index, series) => … / (values, index) => string` | | Tooltip values; what screen readers hear for a position. | | `defaultIndex / onIndexChange` | `number / (index) => void` | `the last position` | The position selected at first, and when it changes. | | `showXAxis / tickCount` | `boolean / number` | `true / 6` | X axis labels (needs labels), at most tickCount, ending on the latest point. | | `showLegend / showDot` | `boolean` | `more than one series / true` | The legend under the chart, and a dot per series at the cursor. | | `animated` | `boolean` | `true` | Draws the areas in from the left the first time and glides the cursor. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - Series without a color use --primary, then lighter mixes of it with --background, so the chart follows the theme. - The root has data-stacked when stacked; area-chart-plot has data-state ("armed", "play" or "static") for the entrance. - The parts are area-chart-area, area-chart-line, area-chart-cursor, area-chart-dot, area-chart-tooltip and area-chart-legend. data-slot: `area-chart`, `area-chart-area`, `area-chart-axis`, `area-chart-cursor`, `area-chart-dot`, `area-chart-legend`, `area-chart-legend-item`, `area-chart-line`, `area-chart-plot`, `area-chart-tooltip`, `area-chart-tooltip-anchor` State attributes: - `data-stacked` (computed) - `data-state` (computed) ### Accessibility - It is a slider over the positions: arrow keys, Page Up and Down, Home and End move the cursor. - Each position is announced with its label and every series' name and value; the drawing is hidden from assistive technology. - The legend is a plain list of the series names. - The areas are simply shown with prefers-reduced-motion. ### Examples On the docs page: Two series, stacked, One series. The usage examples in the source comments (https://corsairui.vercel.app/llms/area-chart.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/area-chart.md ## Avatar A round picture of a person with a fallback while it loads or when it fails, in three sizes, plus an overlapping group. Docs: https://corsairui.vercel.app/docs/avatar ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/avatar ``` Also adds: `utils`. npm: `@radix-ui/react-avatar`, `class-variance-authority`, `clsx`, `tailwind-merge`. ### API Built on Radix Avatar (https://www.radix-ui.com/primitives/docs/components/avatar); its props pass through. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `size` | `"sm" \| "default" \| "lg"` | `"default"` | 24, 32 or 48 px. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `avatar`, `avatar-fallback`, `avatar-group`, `avatar-image` State attributes: - `data-size`: `default` ### Accessibility - AvatarImage needs an alt. The fallback shows until the image loads, and if it fails. ### Examples On the docs page: Sizes and fallback, Group. The usage examples in the source comments (https://corsairui.vercel.app/llms/avatar.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/avatar.md ## Badge Short status or metadata label. Solid, secondary and outline styles, plus success, warning and destructive statuses shown as a coloured dot. Docs: https://corsairui.vercel.app/docs/badge ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/badge ``` Also adds: `utils`. npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `variant` | `"default" \| "secondary" \| "outline" \| "success" \| "warning" \| "destructive"` | `"default"` | Status tones draw a coloured dot, or colour the first icon. | | `asChild` | `boolean` | `false` | Style a link as a badge. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `badge` State attributes: - `data-variant`: `default` ### Accessibility - The colour is decoration: the text has to say what the status is. ### Examples On the docs page: Styles, Status, With icons, As a link. The usage examples in the source comments (https://corsairui.vercel.app/llms/badge.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/badge.md ## Bar Chart One series as vertical or horizontal bars that grow in on first view, with a keyboard slider over the bars. Docs: https://corsairui.vercel.app/docs/bar-chart ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/bar-chart ``` Also adds: `utils`, `use-in-view`, `use-entrance`. npm: `clsx`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `data / labels` | `number[] / string[]` | | Values, negative ones below a zero line, and a label per value. | | `name` | `string` | | Series name: tooltip and accessible name. | | `color` | `string` | `"var(--primary)"` | Bar colour, set as --bar-chart-color. | | `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Bars rising from the bottom, or one row per bar running from the left. | | `radius` | `number` | `4` | Corner radius at the end of each bar, in px. | | `showValues` | `boolean` | `false` | Writes each value at the end of its bar. | | `locale / numberFormat` | `string / Intl.NumberFormatOptions` | `"en-US"` | Number formatting; both can come from a server component. | | `formatValue / formatValueText` | `(value, index) => …` | | Tooltip and bar values; what screen readers hear. | | `defaultIndex / onIndexChange` | `number / (index) => void` | `the last bar` | The bar selected at first, and when the selection changes. | | `showXAxis / tickCount` | `boolean / number` | `true / 12` | Category labels (needs labels); vertical charts show at most tickCount, horizontal ones label every row. | | `animated` | `boolean` | `true` | Grows the bars the first time they come into view and glides the tooltip. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - color is set as --bar-chart-color on the root; the bars and the tooltip swatch fill with it, so a class or style can change it too. - Each bar-chart-bar has data-active on the selected bar, data-dimmed on the others while engaged (they fade to 40%) and data-negative below zero. - The root has data-orientation; bar-chart-plot has data-state ("armed", "play" or "static") for the entrance and bar-chart-tooltip has data-state "open" or "closed". data-slot: `bar-chart`, `bar-chart-axis`, `bar-chart-bar`, `bar-chart-plot`, `bar-chart-tooltip`, `bar-chart-tooltip-anchor`, `bar-chart-value`, `bar-chart-zero` State attributes: - `data-active` (computed) - `data-dimmed` (computed) - `data-engaged` (computed) - `data-negative` (computed) - `data-orientation` (computed) - `data-state`: `closed`, `open` ### Accessibility - It is a slider over the bars: arrow keys, Page Up and Down, Home and End move between them. Horizontal charts move down the rows with Down. - Each bar is announced with its label and value; the drawing itself is hidden from assistive technology. - The bars are simply shown with prefers-reduced-motion. ### Examples On the docs page: Weekly visitors, Horizontal, with values, Negative values. The usage examples in the source comments (https://corsairui.vercel.app/llms/bar-chart.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/bar-chart.md ## Bars Spinner Twelve bars around a hub fading in turn, announced as Loading. No JavaScript. Docs: https://corsairui.vercel.app/docs/bars-spinner ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/bars-spinner ``` Also adds: `utils`. npm: `clsx`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `size` | `number \| string` | | Width and height; or use a size-* class. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `bars-spinner` ### Accessibility - It is role="status", announced as "Loading". Pass a specific aria-label when you can. - With prefers-reduced-motion it turns at half speed rather than stopping. ### Examples On the docs page: Sizes and colours, In a button. The usage examples in the source comments (https://corsairui.vercel.app/llms/bars-spinner.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/bars-spinner.md ## Breadcrumb The trail of links from the top of a site to the current page, with separators and an ellipsis. Docs: https://corsairui.vercel.app/docs/breadcrumb ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/breadcrumb ``` Also adds: `utils`. npm: `@radix-ui/react-slot`, `clsx`, `lucide-react`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `BreadcrumbLink asChild` | `boolean` | `false` | Render your router's link with the link styles. | | `BreadcrumbEllipsis label` | `string` | `"More"` | Read instead of the dots; it can name a menu trigger around it. | | `BreadcrumbSeparator children` | `ReactNode` | `chevron` | Your own mark between items. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `breadcrumb`, `breadcrumb-ellipsis`, `breadcrumb-item`, `breadcrumb-link`, `breadcrumb-list`, `breadcrumb-page`, `breadcrumb-separator` ### Accessibility - A nav named "Breadcrumb" around an ordered list, so the number of levels is announced. - BreadcrumbPage is announced as the current page; separators are hidden. ### Examples On the docs page: Trail, Collapsed. The usage examples in the source comments (https://corsairui.vercel.app/llms/breadcrumb.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/breadcrumb.md ## Button Triggers an action. Variants default, secondary, outline, ghost, destructive and link; sizes sm, default, lg and three square icon sizes. Supports asChild and a loading state. Docs: https://corsairui.vercel.app/docs/button ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/button ``` Also adds: `utils`. npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `variant` | `"default" \| "secondary" \| "outline" \| "ghost" \| "destructive" \| "link" \| "raised"` | `"default"` | Visual weight. raised stands on a 4 px base and sinks into it while pressed. | | `size` | `"sm" \| "default" \| "lg" \| "icon-sm" \| "icon" \| "icon-lg"` | `"default"` | Height and padding; the icon sizes are square. | | `loading` | `boolean` | `false` | Shows a spinner, sets aria-busy and blocks clicks. | | `asChild` | `boolean` | `false` | Styles the child element, e.g. a link, instead of rendering a button. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - Icons inside without a size-* class are set to size-4. Give an icon its own size-* (size-5, size-[18px]) to change it; w-* and h-* alone are overridden. data-slot: `button`, `button-spinner` State attributes: - `data-size`: `default` - `data-variant`: `default` ### Accessibility - A native button, so Enter and Space work and it is announced as a button. - Icon-only sizes need an aria-label. - With asChild, a disabled link gets aria-disabled instead of the disabled attribute. ### Examples On the docs page: Variants, Sizes, With icons, Icon only, Loading, Disabled, As a link, Raised. The usage examples in the source comments (https://corsairui.vercel.app/llms/button.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/button.md ## Calendar Month grid for picking a date, several dates or a range, with dropdown captions and week numbers. Built on DayPicker. Docs: https://corsairui.vercel.app/docs/calendar ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/calendar ``` Also adds: `utils`, `button`. npm: `@daypicker/react`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`. ### API Built on DayPicker (https://daypicker.dev/api/react); its props pass through. | Prop | Type | Default | Description | | --- | --- | --- | --- | | `mode` | `"single" \| "multiple" \| "range"` | | What can be selected; pass selected and onSelect to match. | | `captionLayout` | `"label" \| "dropdown" \| "dropdown-months" \| "dropdown-years"` | `"label"` | Month and year dropdowns for dates far from today. | | `buttonVariant` | `Button variant` | `"ghost"` | Style of the previous and next month buttons. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `calendar` State attributes: - `data-day` (computed) - `data-range-end` (computed) - `data-range-middle` (computed) - `data-range-start` (computed) - `data-selected-single` (computed) ### Accessibility - A grid you move through with the arrow keys; Page Up and Page Down change month. - Day and month names follow the locale prop. ### Examples On the docs page: Single date, Range over two months, Month and year dropdowns, Week numbers and disabled days. The usage examples in the source comments (https://corsairui.vercel.app/llms/calendar.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/calendar.md ## Card Container with header, title, description, action, content and footer parts. Docs: https://corsairui.vercel.app/docs/card ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/card ``` Also adds: `utils`. npm: `clsx`, `tailwind-merge`. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `card`, `card-action`, `card-content`, `card-description`, `card-footer`, `card-header`, `card-title` ### Accessibility - Plain layout: use a heading inside CardTitle when the card starts a section. ### Examples On the docs page: With action and footer, Sign in. The usage examples in the source comments (https://corsairui.vercel.app/llms/card.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/card.md ## Carousel Slides on a native scroll-snap track, with keyboard steps, previous and next buttons, dots and an optional coverflow look. Docs: https://corsairui.vercel.app/docs/carousel ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/carousel ``` Also adds: `utils`, `button`. npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `index / defaultIndex / onIndexChange` | `number / number / (index) => void` | | The active slide, controlled or not. | | `loop` | `boolean` | `false` | Wrap around at the ends. | | `align` | `"start" \| "center"` | `"start"` | Where slides come to rest. | | `variant` | `"default" \| "coverflow"` | `"default"` | Coverflow centres the active slide and shrinks the others. | | `useCarousel / useCarouselItem` | `hooks` | | The index and controls; whether a slide is the active one. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). - Each dot is a button (carousel-dot, with data-active) around its visible part, carousel-dot-indicator: size the indicator, e.g. [&_[data-slot=carousel-dot][data-active=true]>[data-slot=carousel-dot-indicator]]:w-6. data-slot: `carousel`, `carousel-content`, `carousel-dot`, `carousel-dot-indicator`, `carousel-dots`, `carousel-item`, `carousel-next`, `carousel-previous`, `carousel-track` State attributes: - `data-active` (computed) - `data-align` (computed) - `data-variant` (computed) ### Accessibility - A region with aria-roledescription="carousel"; each slide is a group named "2 of 5". - The track takes focus: arrow keys, Home and End. Buttons and dots are real buttons. - Native scroll snapping, so swipe and trackpads work. Controls jump with prefers-reduced-motion. ### Examples On the docs page: Several at once, Coverflow, looping. The usage examples in the source comments (https://corsairui.vercel.app/llms/carousel.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/carousel.md ## Checkbox Checkbox with checked, unchecked and indeterminate states. Docs: https://corsairui.vercel.app/docs/checkbox ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/checkbox ``` Also adds: `utils`. npm: `@radix-ui/react-checkbox`, `clsx`, `lucide-react`, `tailwind-merge`. ### API Built on Radix Checkbox (https://www.radix-ui.com/primitives/docs/components/checkbox); its props pass through. ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `checkbox`, `checkbox-indicator` ### Accessibility - Space toggles it; checked="indeterminate" is announced as mixed. - The mark animates only when motion is allowed. ### Examples On the docs page: Default, States, Select all. The usage examples in the source comments (https://corsairui.vercel.app/llms/checkbox.md) are the reference. Source and usage examples: https://corsairui.vercel.app/llms/checkbox.md ## Code Block A block of code with an optional caption, a language label and a copy button. Docs: https://corsairui.vercel.app/docs/code-block ### Install ```bash pnpm dlx shadcn@latest add @corsair-ui/code-block ``` Also adds: `utils`, `button`, `copy-button`. npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`. ### API | Prop | Type | Default | Description | | --- | --- | --- | --- | | `code` | `string` | | What the copy button copies. | | `language / title` | `string / ReactNode` | | A small label and a caption. | | `showCopyButton / copyLabel` | `boolean / string` | `true / "Copy code"` | The copy button and its name. | | `wrap` | `boolean` | `false` | Wrap long lines instead of scrolling sideways. | | `children` | `ReactNode` | | Highlighted markup of your own to show instead of the raw code. | ### Styling Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`). data-slot: `code-block`, `code-block-code`, `code-block-language`, `code-block-pre`, `code-block-title`, `code-block-toolbar` State attributes: - `data-language` (computed) - `data-wrap` (computed) ### Accessibility - A figure named by its title. The scrolling
 takes focus so it scrolls from the keyboard.
- The copy button announces when it copied.

### Examples

On the docs page: Title, language and copy. The usage examples in the source comments (https://corsairui.vercel.app/llms/code-block.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/code-block.md

## Collapsible

A region that a trigger button shows and hides, with a height animation.

Docs: https://corsairui.vercel.app/docs/collapsible

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/collapsible
```

Also adds: `utils`.
npm: `@radix-ui/react-collapsible`, `clsx`, `tailwind-merge`.

### API

Built on Radix Collapsible (https://www.radix-ui.com/primitives/docs/components/collapsible); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open / defaultOpen / onOpenChange` | `boolean / boolean / (open) => void` |  | Collapsible: controlled or uncontrolled. |
| `CollapsibleTrigger asChild` | `boolean` | `false` | Turn your own button into the trigger. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `collapsible`, `collapsible-content`, `collapsible-trigger`

### Accessibility

- The trigger is a button announced as expanded or collapsed, pointing at the region.
- Give an icon-only trigger an aria-label.
- The region opens without sliding with prefers-reduced-motion.

### Examples

On the docs page: Show more. The usage examples in the source comments (https://corsairui.vercel.app/llms/collapsible.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/collapsible.md

## Color Swatches

Pick one colour from a row of swatches: a radio group with arrow keys and form support.

Docs: https://corsairui.vercel.app/docs/color-swatches

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/color-swatches
```

Also adds: `utils`.
npm: `@radix-ui/react-radio-group`, `clsx`, `tailwind-merge`.

### API

Built on Radix Radio Group (https://www.radix-ui.com/primitives/docs/components/radio-group); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors` | `(string \| { value, color?, label?, disabled? })[]` |  | CSS colours, or swatches with their own value, label and colour. |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Swatch size. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `color-swatch`, `color-swatches`

### Accessibility

- It is a radio group: Tab reaches the picked swatch, arrow keys move between them.
- Each swatch is named by its label, or its value. Name the group with aria-label or a FieldLegend.
- name submits the value with a native form.

### Examples

On the docs page: In a field set, Sizes, and a disabled swatch. The usage examples in the source comments (https://corsairui.vercel.app/llms/color-swatches.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/color-swatches.md

## Combobox

A select with a search box, for long lists of options.

Docs: https://corsairui.vercel.app/docs/combobox

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/combobox
```

Also adds: `utils`, `command`, `popover`.
npm: `@radix-ui/react-popover`, `clsx`, `cmdk`, `lucide-react`, `tailwind-merge`.

### API

Built on cmdk (https://github.com/pacocoursey/cmdk); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `{ value, label, keywords?, disabled? }[]` |  | The choices; keywords make other words find an option. |
| `value / defaultValue` | `string \| null` |  | The picked value. |
| `onValueChange` | `(value: string \| null) => void` |  | Called on every pick. |
| `name` | `string` |  | Submits the value in a native form. |
| `placeholder, searchPlaceholder, emptyMessage` | `ReactNode` |  | English defaults you can replace. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `combobox-icon`, `combobox-trigger`

State attributes:

- `data-empty` (computed)

### Accessibility

- The trigger has role="combobox" with aria-expanded and aria-controls pointing at the list.
- Type to filter, arrow keys to move, Enter to pick, Escape to close.

### Examples

On the docs page: Default, Long list, Invalid and disabled. The usage examples in the source comments (https://corsairui.vercel.app/llms/combobox.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/combobox.md

## Command

Searchable list navigated with the arrow keys; the base for comboboxes and command menus.

Docs: https://corsairui.vercel.app/docs/command

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/command
```

Also adds: `utils`.
npm: `clsx`, `cmdk`, `lucide-react`, `tailwind-merge`.

### API

Built on cmdk (https://github.com/pacocoursey/cmdk); its props pass through.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `command`, `command-empty`, `command-group`, `command-input`, `command-input-wrapper`, `command-item`, `command-list`, `command-separator`, `command-shortcut`

### Accessibility

- Arrow keys move through the results and Enter runs the highlighted one.
- Give the Command a label that says what it searches.

### Examples

On the docs page: Command menu. The usage examples in the source comments (https://corsairui.vercel.app/llms/command.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/command.md

## Context Menu

A menu of actions for an area of the page that opens on right click or long press, with checkbox and radio items, labels, shortcuts and submenus.

Docs: https://corsairui.vercel.app/docs/context-menu

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/context-menu
```

Also adds: `utils`.
npm: `@radix-ui/react-context-menu`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Context Menu (https://www.radix-ui.com/primitives/docs/components/context-menu); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "destructive"` | `"default"` | ContextMenuItem: destructive for actions that cannot be undone. |
| `inset` | `boolean` | `false` | Item, Label and SubTrigger: line up with items that have an indicator. |
| `ContextMenuTrigger asChild` | `boolean` | `false` | Make your own element the area; it renders a span otherwise. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `context-menu`, `context-menu-checkbox-item`, `context-menu-checkbox-item-indicator`, `context-menu-content`, `context-menu-group`, `context-menu-item`, `context-menu-label`, `context-menu-portal`, `context-menu-radio-group`, `context-menu-radio-item`, `context-menu-radio-item-indicator`, `context-menu-separator`, `context-menu-shortcut`, `context-menu-sub`, `context-menu-sub-content`, `context-menu-sub-trigger`, `context-menu-trigger`

State attributes:

- `data-inset` (computed)
- `data-variant` (computed)

### Accessibility

- Opens on right click, long press, or Shift+F10 and the menu key while something inside has focus.
- Keep a focusable element inside the area so keyboard users can reach the menu.
- It is hidden until asked for: offer every action another way too.
- It appears without fading or zooming with prefers-reduced-motion.

### Examples

On the docs page: On an area. The usage examples in the source comments (https://corsairui.vercel.app/llms/context-menu.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/context-menu.md

## Copy Button

An icon button that copies a value and swaps to a check mark, announcing it to screen readers.

Docs: https://corsairui.vercel.app/docs/copy-button

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/copy-button
```

Also adds: `utils`, `button`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` |  | The text that lands on the clipboard. |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Square icon sizes. |
| `timeout` | `number` | `1600` | Ms the check stays. |
| `onCopied` | `(value) => void` |  | Only after the clipboard accepted it. |
| `label / copiedLabel` | `string` | `"Copy to clipboard" / "Copied"` | Name, and what is announced. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- data-state is idle or copied, and each icon has its own slot: [&_[data-slot=copy-button-check-icon]]:text-success colours only the check.

data-slot: `copy-button`, `copy-button-check-icon`, `copy-button-copy-icon`

State attributes:

- `data-state`: `copied`, `idle`

### Accessibility

- "Copied" is announced through a live region, and never claimed when the browser refused.
- It stays enabled while it shows the check, so keyboard focus is never dropped.

### Examples

On the docs page: Sizes and outline, In an input group. The usage examples in the source comments (https://corsairui.vercel.app/llms/copy-button.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/copy-button.md

## Corner Frame

A box with bracket marks on its corners, in any size, thickness, colour and inset.

Docs: https://corsairui.vercel.app/docs/corner-frame

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/corner-frame
```

Also adds: `utils`.
npm: `@radix-ui/react-slot`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `corners` | `"all" \| ("top-left" \| "top-right" \| "bottom-left" \| "bottom-right")[]` | `"all"` | Which corners get a bracket. |
| `size / thickness / inset` | `string` | `"12px" / "2px" / "0px"` | CSS lengths; a negative inset sits outside the box. |
| `color` | `string` | `var(--primary)` | Sets --corner-frame-color. |
| `asChild` | `boolean` | `false` | Frame your own element. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `corner-frame`, `corner-frame-corner`

State attributes:

- `data-corner` (computed)

### Accessibility

- The brackets are decorative and hidden from screen readers.

### Examples

On the docs page: Corners, size, colour and inset. The usage examples in the source comments (https://corsairui.vercel.app/llms/corner-frame.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/corner-frame.md

## Dash Button

A pill button whose fill gives way to a dashed outline that marches on hover.

Docs: https://corsairui.vercel.app/docs/dash-button

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/dash-button
```

Also adds: `utils`, `button`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Button sizes. |
| `dashColor` | `string` |  | Colour of the outline; defaults to muted foreground. |
| `asChild` | `boolean` | `false` | Style a link or another element instead. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `dash-button`, `dash-button-outline`

### Accessibility

- The outline is aria-hidden; the focus ring comes from Button.
- The dashes march only on hover and focus, and hold still with prefers-reduced-motion.

### Examples

On the docs page: Sizes, As a link, in brass. The usage examples in the source comments (https://corsairui.vercel.app/llms/dash-button.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/dash-button.md

## Data Table

A table with sortable headers, a text filter, pagination and row selection, built on TanStack Table v9.

Docs: https://corsairui.vercel.app/docs/data-table

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/data-table
```

Also adds: `utils`, `table`, `button`, `pagination`, `checkbox`, `input`.
npm: `@radix-ui/react-checkbox`, `@radix-ui/react-slot`, `@tanstack/react-table`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on TanStack Table (https://tanstack.com/table); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `columns / data` | `DataTableColumnDef[] / TData[]` |  | TanStack Table v9 column definitions and the rows; keep both stable. |
| `getRowId` | `(row, index) => string` | `the index` | A stable id per row, the key of rowSelection. |
| `enableSorting / enableRowSelection` | `boolean` | `true / false` | Sort buttons on columns with a string header; a checkbox column with select-all. |
| `rowSelection / defaultRowSelection / onRowSelectionChange` | `Record` |  | Selected rows by id, controlled or not. |
| `globalFilter / defaultGlobalFilter / onGlobalFilterChange` | `string` |  | The filter text, controlled or not; a row matches when any value contains it. |
| `filterLabel / filterPlaceholder` | `string \| null / string` | `"Filter rows" / "Filter…"` | The filter input's name and placeholder; null hides the input. |
| `pageSize` | `number \| null` | `10` | Rows per page; null shows every row without pagination. |
| `caption / emptyMessage / toolbar` | `ReactNode` | `– / "No results." / –` | A string caption is read by screen readers only; toolbar sits beside the filter. |
| `labels` | `Partial` |  | Names of the checkboxes and page buttons, and the status announcements. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Selected rows take data-state="selected". The parts are data-table-toolbar, data-table-filter, data-table-body and data-table-pagination.
- It is built from Table, Pagination, Checkbox and Input, so their styles and slots apply inside.

data-slot: `data-table`, `data-table-body`, `data-table-empty`, `data-table-filter`, `data-table-pagination`, `data-table-select-all`, `data-table-select-row`, `data-table-status`, `data-table-toolbar`

State attributes:

- `data-state`: `selected`

### Accessibility

- Sort buttons are in the headers, whose aria-sort gives the direction.
- Every checkbox is named, and the header one shows a dash when only some rows are selected.
- A visually hidden status region announces the selected count and the number of matching rows.
- Previous and Next stay focusable at the ends and are marked aria-disabled.

### Examples

On the docs page: Payments, Without pagination. The usage examples in the source comments (https://corsairui.vercel.app/llms/data-table.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/data-table.md

## Date Picker

DatePicker and DateRangePicker: a calendar in a popover behind a button that shows the chosen date or range.

Docs: https://corsairui.vercel.app/docs/date-picker

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/date-picker
```

Also adds: `utils`, `button`, `calendar`, `popover`.
npm: `@daypicker/react`, `@radix-ui/react-popover`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on DayPicker (https://daypicker.dev/api/react); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / defaultValue` | `Date \| null` |  | The picked date. |
| `onValueChange` | `(date?: Date) => void` |  | Called on every pick. |
| `formatValue` | `(date: Date) => string` | `Intl medium date` | Text on the trigger; pass one with a fixed locale when rendering on the server. |
| `name` | `string` |  | Submits yyyy-mm-dd in a native form. |
| `calendarProps` | `CalendarProps` |  | Disabled days, locale, captionLayout or the month to open on. |
| `DateRangePicker names` | `{ from: string; to: string }` |  | Submits both ends of the range. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `date-picker`, `date-picker-icon`, `date-range-picker`

State attributes:

- `data-empty` (computed)

### Accessibility

- The trigger is a button; opening moves focus into the calendar and closing returns it.
- Give the trigger a label; the chosen date is its visible text.

### Examples

On the docs page: Date picker, Date range, Only future weekdays. The usage examples in the source comments (https://corsairui.vercel.app/llms/date-picker.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/date-picker.md

## Dialog

Modal window centred over the page, with a focus trap, overlay, close button, header, footer, title and description parts.

Docs: https://corsairui.vercel.app/docs/dialog

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/dialog
```

Also adds: `utils`.
npm: `@radix-ui/react-dialog`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Dialog (https://www.radix-ui.com/primitives/docs/components/dialog); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `showCloseButton` | `boolean` | `true` | DialogContent: the close icon in the corner. |
| `closeLabel` | `string` | `"Close"` | Accessible name of the close icon. |
| `overlayClassName` | `string` |  | DialogContent: classes for the overlay behind it (tint, blur, z-index). |
| `open / defaultOpen / onOpenChange` | `boolean / boolean / (open) => void` |  | Dialog: controlled or uncontrolled. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- overlayClassName styles the overlay (bg-black/80 backdrop-blur-md, a higher z-index) without rebuilding DialogContent from the primitives.

data-slot: `dialog`, `dialog-close`, `dialog-close-button`, `dialog-content`, `dialog-description`, `dialog-footer`, `dialog-header`, `dialog-overlay`, `dialog-portal`, `dialog-title`, `dialog-trigger`

### Accessibility

- Focus moves into the dialog, stays trapped there and returns to the trigger on close.
- Escape and a click on the overlay close it; the page behind does not scroll.
- DialogTitle names it and DialogDescription describes it. Keep a title, with sr-only if it should not show.
- Enter and exit animations are off with prefers-reduced-motion.

### Examples

On the docs page: With a form, Controlled. The usage examples in the source comments (https://corsairui.vercel.app/llms/dialog.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/dialog.md

## Dock

A floating bar of icon links pinned to the bottom or top of the viewport, with tooltips and an optional reveal after scrolling.

Docs: https://corsairui.vercel.app/docs/dock

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/dock
```

Also adds: `utils`, `tooltip`, `use-in-view`.
npm: `@radix-ui/react-slot`, `@radix-ui/react-tooltip`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `showAfter` | `number \| string` | `0` | Scroll distance before it shows: px or a CSS length ("80vh"). |
| `open` | `boolean` |  | Controls it instead of showAfter. |
| `position` | `"bottom" \| "top"` | `"bottom"` | Where it floats. |
| `label` | `string` |  | DockItem: its accessible name and tooltip. asChild renders a link. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- showAfter takes pixels or any CSS length (400, "80vh"): the dock appears once the page has scrolled that far, with no scroll listener.
- Icons without a size-* class are set to size-5; give one its own size-* to change it.

data-slot: `dock`, `dock-item`, `dock-sentinel`, `dock-separator`, `dock-wrapper`

State attributes:

- `data-position` (computed)
- `data-state`: `hidden`, `visible`

### Accessibility

- A nav landmark, "Quick links" by default. Every item has a name and a tooltip on hover and focus.
- While hidden it is inert: out of the tab order and away from screen readers.

### Examples

On the docs page: Controlled, After a scroll distance. The usage examples in the source comments (https://corsairui.vercel.app/llms/dock.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/dock.md

## Donut Chart

Parts of a whole as a ring with the total in the middle and a keyboard-navigable legend of values and shares.

Docs: https://corsairui.vercel.app/docs/donut-chart

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/donut-chart
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `DonutChartItem[]` |  | Each has label and value (negative counts as 0), and optionally color. Drawn clockwise from the top. |
| `name` | `string` |  | Accessible name of the legend. |
| `thickness` | `number` | `24` | Width of the ring, in px. |
| `centerLabel / centerValue` | `ReactNode` | `the highlighted part, or the total` | Small and large text in the hole. |
| `totalLabel` | `string` | `"Total"` | Label for the total in the hole. |
| `locale / numberFormat / percentFormat` | `string / Intl.NumberFormatOptions` | `"en-US" / – / percent, 1 decimal` | Number and share formatting; all can come from a server component. |
| `formatValue` | `(value, index) => ReactNode` |  | Legend and hole values; index is -1 for the total. |
| `defaultIndex / onIndexChange` | `number / (index \| null) => void` | `none` | The part highlighted at first, and when it changes. |
| `animated` | `boolean` | `true` | Sweeps the segments in the first time the chart comes into view. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Parts without a color use --primary, then lighter mixes of it with --background, down to 30%.
- The highlighted donut-chart-segment and donut-chart-legend-button have data-active; the other segments have data-dimmed and fade to 40%.
- donut-chart-ring has data-state ("armed", "play" or "static") for the sweep; thickness sets the ring width.

data-slot: `donut-chart`, `donut-chart-center`, `donut-chart-legend`, `donut-chart-legend-button`, `donut-chart-legend-item`, `donut-chart-ring`, `donut-chart-segment`, `donut-chart-track`

State attributes:

- `data-active` (computed)
- `data-dimmed` (computed)
- `data-state` (computed)

### Accessibility

- The ring is hidden from assistive technology; the legend, a list named by name, carries every value and share.
- Each legend entry is a toggle button with aria-pressed: Tab reaches the list, the arrow keys, Home and End move between entries, Enter or Space pins one and Escape clears it.
- The segments are simply shown with prefers-reduced-motion.

### Examples

On the docs page: Traffic sources, Custom colours. The usage examples in the source comments (https://corsairui.vercel.app/llms/donut-chart.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/donut-chart.md

## Drawer

A bottom sheet dialog for phones that can also be dragged down to close.

Docs: https://corsairui.vercel.app/docs/drawer

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/drawer
```

Also adds: `utils`.
npm: `@radix-ui/react-dialog`, `clsx`, `tailwind-merge`.

### API

Built on Radix Dialog (https://www.radix-ui.com/primitives/docs/components/dialog); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open / defaultOpen / onOpenChange` | `boolean / boolean / (open) => void` |  | Drawer: controlled or uncontrolled. |
| `showHandle` | `boolean` | `true` | DrawerContent: the grab handle at the top; decorative. |
| `closeThreshold` | `number` | `0.25` | DrawerContent: share of its height it has to be dragged down to close. |
| `overlayClassName` | `string` |  | DrawerContent: classes for the overlay (tint, blur, z-index). |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- While dragged, drawer-content has data-dragging="true" and the offset in --drawer-drag; the transition is off until it is let go.
- overlayClassName styles the overlay behind it; the grab handle is drawer-handle.

data-slot: `drawer`, `drawer-close`, `drawer-content`, `drawer-description`, `drawer-footer`, `drawer-handle`, `drawer-header`, `drawer-overlay`, `drawer-portal`, `drawer-title`, `drawer-trigger`

### Accessibility

- A dialog named by DrawerTitle: focus moves in, stays trapped and returns to the trigger on close.
- Escape and a click on the overlay close it; dragging down is an extra for pointers, never the only way out.
- Keep a DrawerTitle, with sr-only if it should not show.
- With prefers-reduced-motion it appears without sliding and does not spring back.

### Examples

On the docs page: Default, Scrolling content. The usage examples in the source comments (https://corsairui.vercel.app/llms/drawer.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/drawer.md

## Dropdown Menu

A menu of actions and options that opens from a button, with checkbox and radio items, labels, shortcuts and submenus.

Docs: https://corsairui.vercel.app/docs/dropdown-menu

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/dropdown-menu
```

Also adds: `utils`.
npm: `@radix-ui/react-dropdown-menu`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Dropdown Menu (https://www.radix-ui.com/primitives/docs/components/dropdown-menu); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "destructive"` | `"default"` | DropdownMenuItem: destructive for actions that cannot be undone. |
| `inset` | `boolean` | `false` | Item, Label and SubTrigger: line up with items that have an indicator. |
| `DropdownMenuShortcut` | `span` |  | A hint at the end of an item; handling the shortcut is up to you. |
| `sideOffset` | `number` | `4` | DropdownMenuContent: gap to the trigger in px. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `dropdown-menu`, `dropdown-menu-checkbox-item`, `dropdown-menu-checkbox-item-indicator`, `dropdown-menu-content`, `dropdown-menu-group`, `dropdown-menu-item`, `dropdown-menu-label`, `dropdown-menu-portal`, `dropdown-menu-radio-group`, `dropdown-menu-radio-item`, `dropdown-menu-radio-item-indicator`, `dropdown-menu-separator`, `dropdown-menu-shortcut`, `dropdown-menu-sub`, `dropdown-menu-sub-content`, `dropdown-menu-sub-trigger`, `dropdown-menu-trigger`

State attributes:

- `data-inset` (computed)
- `data-variant` (computed)

### Accessibility

- The trigger is a button with a menu popup; Enter, Space or ArrowDown open it.
- Arrow keys and typeahead move through items; ArrowRight and ArrowLeft open and close submenus; Escape returns focus to the trigger.
- Checkbox and radio items are announced with their checked state.
- It appears without fading or zooming with prefers-reduced-motion.

### Examples

On the docs page: Actions, Options and a submenu. The usage examples in the source comments (https://corsairui.vercel.app/llms/dropdown-menu.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/dropdown-menu.md

## Dropzone

A file drop area on a native file input that checks type, size and count, and lists the chosen files.

Docs: https://corsairui.vercel.app/docs/dropzone

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/dropzone
```

Also adds: `utils`, `button`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `accept` | `string` |  | Accepted files, as in the input's accept ("image/*,.pdf"). Checked on drop too. |
| `multiple / maxFiles` | `boolean / number` | `false / –` | More than one file, and how many at most. Without multiple, a new file replaces the old one. |
| `maxSize` | `number` |  | Largest accepted file, in bytes. |
| `files / defaultFiles / onFilesChange` | `File[] / File[] / (files) => void` |  | The chosen files, controlled or not. |
| `onReject` | `(rejections: { file, reason }[]) => void` |  | Files turned away, with "type", "size" or "count". |
| `name / required / disabled` | `string / boolean / boolean` |  | Form fields: with name, the chosen files submit with the form. |
| `labels` | `Partial` |  | The prompt, the hint line, and the remove and error messages. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The root and dropzone-area take data-dragging while files are over it and data-disabled when disabled.
- The parts are dropzone-area, dropzone-prompt, dropzone-hint, dropzone-error, dropzone-files and dropzone-file.

data-slot: `dropzone`, `dropzone-area`, `dropzone-error`, `dropzone-file`, `dropzone-file-size`, `dropzone-files`, `dropzone-hint`, `dropzone-input`, `dropzone-prompt`, `dropzone-remove`

State attributes:

- `data-reason` (computed)

### Accessibility

- A native file input, visually hidden but in the Tab order; the drop area is its label, so Enter, Space or a click open the picker.
- Rejected files are listed in a role="alert" message tied to the input with aria-describedby.
- Each chosen file has a named remove button; removing one moves focus to the next.
- Only colours transition, and not with prefers-reduced-motion.

### Examples

On the docs page: Images, up to 2 MB, Single file. The usage examples in the source comments (https://corsairui.vercel.app/llms/dropzone.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/dropzone.md

## Empty State

What a list, table or page shows when it has nothing in it yet, with an icon, title, description and actions.

Docs: https://corsairui.vercel.app/docs/empty-state

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/empty-state
```

Also adds: `utils`.
npm: `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"dashed" \| "plain"` | `"dashed"` | EmptyState: a dashed border, or none. |
| `as` | `"h2" \| "h3" \| "h4"` | `"h2"` | EmptyStateTitle: the heading level, to fit the page outline. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- variant is exposed as data-variant ("dashed" or "plain"). Icons without a size-* class are set to size-6.

data-slot: `empty-state`, `empty-state-actions`, `empty-state-description`, `empty-state-icon`, `empty-state-title`

State attributes:

- `data-variant`: `dashed`

### Accessibility

- A plain block, not a live region: it is read in page order.
- The title is a real heading, so it shows up in the headings list.
- EmptyStateIcon is hidden from screen readers.

### Examples

On the docs page: No projects, Plain, no results. The usage examples in the source comments (https://corsairui.vercel.app/llms/empty-state.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/empty-state.md

## Field

Layout for a control with its label, description and error, plus fieldset and group helpers. Independent of any form library.

Docs: https://corsairui.vercel.app/docs/field

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/field
```

Also adds: `utils`, `label`.
npm: `@radix-ui/react-label`, `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Horizontal puts a checkbox or switch before its label. |
| `FieldError errors` | `{ message?: string }[]` |  | Messages in the shape most form libraries use; duplicates are dropped. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `field`, `field-content`, `field-description`, `field-error`, `field-group`, `field-label`, `field-legend`, `field-set`

State attributes:

- `data-orientation` (computed)
- `data-variant` (computed)

### Accessibility

- FieldError is a live region (role alert), so errors are announced as they appear.
- Point aria-describedby at the description and error ids, or use FormField, which does it for you.

### Examples

On the docs page: Label, control and description, Error, Horizontal, Fieldset. The usage examples in the source comments (https://corsairui.vercel.app/llms/field.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/field.md

## Form Field (react-hook-form)

FormField: a react-hook-form Controller laid out with Field, with the id, label and ARIA wiring done for you.

Docs: https://corsairui.vercel.app/docs/form

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/form
```

Also adds: `utils`, `label`, `field`.
npm: `@radix-ui/react-label`, `class-variance-authority`, `clsx`, `react-hook-form`, `tailwind-merge`.

### API

Built on React Hook Form (https://react-hook-form.com/docs/usecontroller/controller); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `control, name, rules` | `Controller props` |  | Passed to Controller. |
| `label` | `ReactNode` |  | Rendered as a FieldLabel for the control. |
| `description` | `ReactNode` |  | Help text under the control. |
| `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Horizontal for checkboxes and switches. |
| `render` | `({ field, fieldState }) => ReactElement` |  | Render the control; field carries id, aria-invalid and aria-describedby. |

### Accessibility

- Generates the id, points the label at it and wires aria-invalid and aria-describedby.

### Examples

On the docs page: Contact form. The usage examples in the source comments (https://corsairui.vercel.app/llms/form.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/form.md

## Hover Card

A preview card that appears while a link is hovered or focused, for supplementary content.

Docs: https://corsairui.vercel.app/docs/hover-card

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/hover-card
```

Also adds: `utils`.
npm: `@radix-ui/react-hover-card`, `clsx`, `tailwind-merge`.

### API

Built on Radix Hover Card (https://www.radix-ui.com/primitives/docs/components/hover-card); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `openDelay / closeDelay` | `number` | `400 / 150` | HoverCard: in ms, long enough to move onto the card. |
| `align / sideOffset` | `"start" \| "center" \| "end" / number` | `"center" / 4` | HoverCardContent: position next to the trigger. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `hover-card`, `hover-card-content`, `hover-card-trigger`

### Accessibility

- Opens on hover and on keyboard focus of the trigger.
- Touch and screen reader users may never see it: the link must work on its own and the card holds nothing essential.
- It appears without fading or zooming with prefers-reduced-motion.

### Examples

On the docs page: Profile preview. The usage examples in the source comments (https://corsairui.vercel.app/llms/hover-card.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/hover-card.md

## Input

Single-line text control. Its error state comes from aria-invalid, so any form library can drive it.

Docs: https://corsairui.vercel.app/docs/input

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/input
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `input`

### Accessibility

- Give it a Label or an aria-label.
- The error style comes from aria-invalid="true", which is also what screen readers hear.

### Examples

On the docs page: Default, Types, Invalid, Disabled. The usage examples in the source comments (https://corsairui.vercel.app/llms/input.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/input.md

## Input Group

An input with icons, text or buttons attached on either side.

Docs: https://corsairui.vercel.app/docs/input-group

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/input-group
```

Also adds: `utils`, `input`.
npm: `class-variance-authority`, `clsx`, `tailwind-merge`.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `input-group`, `input-group-addon`, `input-group-control`, `input-group-text`

State attributes:

- `data-align` (computed)

### Accessibility

- The group draws the focus ring for the input inside.
- Buttons inside it need their own aria-label when they only show an icon.

### Examples

On the docs page: With an icon, Text addons, With a button, Invalid. The usage examples in the source comments (https://corsairui.vercel.app/llms/input-group.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/input-group.md

## Input OTP

One-time code input drawn as separate slots, with paste and autofill support.

Docs: https://corsairui.vercel.app/docs/input-otp

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/input-otp
```

Also adds: `utils`.
npm: `clsx`, `input-otp`, `lucide-react`, `tailwind-merge`.

### API

Built on input-otp (https://github.com/guilhermerodz/input-otp); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `maxLength` | `number` |  | Number of slots; required. |
| `pattern` | `string` |  | Allowed characters, e.g. REGEXP_ONLY_DIGITS. |
| `onComplete` | `(code: string) => void` |  | Called when every slot is filled. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `input-otp`, `input-otp-group`, `input-otp-separator`, `input-otp-slot`

State attributes:

- `data-active` (computed)

### Accessibility

- One real input under the slots, so paste, autofill and screen readers treat it as one field.
- Set autoComplete="one-time-code" for codes sent by SMS.

### Examples

On the docs page: Six digits, Controlled, Invalid. The usage examples in the source comments (https://corsairui.vercel.app/llms/input-otp.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/input-otp.md

## Kbd

A key or key combination shown as a small keycap, alone or in a group.

Docs: https://corsairui.vercel.app/docs/kbd

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/kbd
```

Also adds: `utils`.
npm: `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"default" \| "sm"` | `"default"` | Kbd: a 24 px or 20 px keycap. |
| `KbdGroup` | `span` |  | Keys pressed together in a row; put the separator (+) in yourself. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- size is exposed as data-size ("default" or "sm"). Icons without a size-* class are set to size-3.

data-slot: `kbd`, `kbd-group`

State attributes:

- `data-size`: `default`

### Accessibility

- A native kbd element: its text is read as is.
- A symbol alone (⌘, ⇧) may be read by its Unicode name or not at all: add the word in an sr-only span.

### Examples

On the docs page: Shortcut, In a sentence. The usage examples in the source comments (https://corsairui.vercel.app/llms/kbd.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/kbd.md

## Label

Accessible name for a form control.

Docs: https://corsairui.vercel.app/docs/label

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/label
```

Also adds: `utils`.
npm: `@radix-ui/react-label`, `clsx`, `tailwind-merge`.

### API

Built on Radix Label (https://www.radix-ui.com/primitives/docs/components/label); its props pass through.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `label`

### Accessibility

- Clicking it focuses or toggles its control; point htmlFor at the control's id.
- Dims when the control after it is disabled.

### Examples

On the docs page: With an input, Required. The usage examples in the source comments (https://corsairui.vercel.app/llms/label.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/label.md

## Line Chart

One series as a line, with a cursor that snaps to points; a slider for keyboards and screen readers.

Docs: https://corsairui.vercel.app/docs/line-chart

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/line-chart
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data / labels` | `number[] / string[]` |  | Values and a label per value. |
| `name` | `string` |  | Series name: tooltip and accessible name. |
| `color` | `string` | `"var(--primary)"` | Line and dot colour. |
| `curve` | `"smooth" \| "linear"` | `"smooth"` | Smooth never overshoots the points. |
| `locale / numberFormat` | `string / Intl.NumberFormatOptions` | `"en-US"` | Number formatting; both can come from a server component. |
| `formatValue / formatValueText` | `(value, index) => …` |  | Tooltip content; what screen readers hear. |
| `reveal` | `boolean` | `false` | Colour only up to the cursor. |
| `showFill / showDot / showXAxis` | `boolean` | `true` | Parts to draw. |
| `tickCount` | `number` | `6` | Most X axis labels, evenly spaced and ending on the latest point. |
| `onIndexChange` | `(index) => void` |  | When the selected point changes. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `line-chart`, `line-chart-axis`, `line-chart-cursor`, `line-chart-dot`, `line-chart-plot`, `line-chart-tooltip`, `line-chart-tooltip-anchor`

### Accessibility

- It is a slider over the points: arrow keys, Page Up and Down, Home and End move the cursor.
- Each point is announced with its label and value.
- On touch it follows a horizontal drag and leaves vertical scrolling to the page.

### Examples

On the docs page: Default, Reveal, straight lines, currency, Line only. The usage examples in the source comments (https://corsairui.vercel.app/llms/line-chart.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/line-chart.md

## Nav Link

A navigation link that marks the current page with aria-current and data-active. Supports asChild for router links.

Docs: https://corsairui.vercel.app/docs/nav-link

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/nav-link
```

Also adds: `utils`.
npm: `@radix-ui/react-slot`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` | `false` | Sets aria-current="page" and data-active. |
| `asChild` | `boolean` | `false` | Renders your router's link (next/link, react-router) instead of . |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `nav-link`

State attributes:

- `data-active` (computed)

### Accessibility

- The current page is announced through aria-current, not only by colour.

### Examples

On the docs page: Current page. The usage examples in the source comments (https://corsairui.vercel.app/llms/nav-link.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/nav-link.md

## Navigation Menu

Site navigation with links and triggers that open panels of links in a shared, animated viewport.

Docs: https://corsairui.vercel.app/docs/navigation-menu

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/navigation-menu
```

Also adds: `utils`.
npm: `@radix-ui/react-navigation-menu`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Navigation Menu (https://www.radix-ui.com/primitives/docs/components/navigation-menu); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `viewport` | `boolean` | `true` | One shared, resizing panel; false opens each panel under its trigger. |
| `active` | `boolean` | `false` | NavigationMenuLink: the current page, with aria-current="page". |
| `navigationMenuTriggerStyle()` | `() => string` |  | Trigger classes for top-level links that sit next to triggers. |
| `delayDuration` | `number` | `200` | NavigationMenu: hover delay before a panel opens, in ms. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `navigation-menu`, `navigation-menu-content`, `navigation-menu-indicator`, `navigation-menu-item`, `navigation-menu-link`, `navigation-menu-list`, `navigation-menu-trigger`, `navigation-menu-trigger-icon`, `navigation-menu-viewport`, `navigation-menu-viewport-position`

State attributes:

- `data-viewport` (computed)

### Accessibility

- A nav landmark named "Main"; pass aria-label when there is another one.
- Tab and arrow keys move between items; Enter or Space opens a panel, Escape closes it and returns focus.
- Triggers are buttons announced as expanded or collapsed.
- Panels appear without sliding or zooming with prefers-reduced-motion.

### Examples

On the docs page: Panels and a link, Without the viewport. The usage examples in the source comments (https://corsairui.vercel.app/llms/navigation-menu.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/navigation-menu.md

## Number Input

Number field with decrement and increment buttons, min, max and step, following the spinbutton keyboard pattern.

Docs: https://corsairui.vercel.app/docs/number-input

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/number-input
```

Also adds: `utils`, `button`, `input`, `input-group`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / defaultValue` | `number \| null` |  | null when empty. |
| `onValueChange` | `(value: number \| null) => void` |  | Called when the value commits. |
| `min, max` | `number` |  | Typed values are clamped on blur and Enter. |
| `step` | `number` | `1` | Arrow keys and buttons move by this. |
| `largeStep` | `number` | `step × 10` | Page Up, Page Down and Shift. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `number-input`

### Accessibility

- role="spinbutton" with aria-valuenow, min and max.
- The − and + buttons stay out of the tab order: the keys already do what they do.

### Examples

On the docs page: Default, Decimals, Invalid and disabled. The usage examples in the source comments (https://corsairui.vercel.app/llms/number-input.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/number-input.md

## Pagination

Page links with previous and next, an ellipsis, and a helper that works out which page numbers to show.

Docs: https://corsairui.vercel.app/docs/pagination

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/pagination
```

Also adds: `utils`, `button`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `isActive` | `boolean` | `false` | PaginationLink: the current page, with aria-current="page". |
| `label` | `string` | `"Previous" / "Next"` | PaginationPrevious and Next: visible text, from sm up. |
| `asChild` | `boolean` | `false` | PaginationLink: render your router's link. |
| `getPaginationRange` | `({ page, pageCount, siblings?, boundaries? }) => (number \| "ellipsis")[]` |  | The numbers to show; siblings and boundaries default to 1. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `pagination`, `pagination-content`, `pagination-ellipsis`, `pagination-item`, `pagination-link`, `pagination-next`, `pagination-previous`

State attributes:

- `data-active` (computed)

### Accessibility

- A nav named "Pagination"; the current page has aria-current="page".
- Previous and Next are named "Go to previous page" and "Go to next page".
- At the ends, leave off href and pass aria-disabled: they dim and leave the Tab order.

### Examples

On the docs page: Controlled. The usage examples in the source comments (https://corsairui.vercel.app/llms/pagination.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/pagination.md

## Password Input

Password field with a button that shows what was typed.

Docs: https://corsairui.vercel.app/docs/password-input

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/password-input
```

Also adds: `utils`, `button`, `input`, `input-group`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `toggleLabel` | `string` | `"Show password"` | Name of the reveal button; its pressed state says whether it shows. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `password-input`

### Accessibility

- The reveal button uses aria-pressed.
- Set autoComplete to "current-password" or "new-password" for password managers.

### Examples

On the docs page: Default, New password, Disabled. The usage examples in the source comments (https://corsairui.vercel.app/llms/password-input.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/password-input.md

## Popover

Floating panel anchored to a trigger, with header, title and description parts.

Docs: https://corsairui.vercel.app/docs/popover

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/popover
```

Also adds: `utils`.
npm: `@radix-ui/react-popover`, `clsx`, `tailwind-merge`.

### API

Built on Radix Popover (https://www.radix-ui.com/primitives/docs/components/popover); its props pass through.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `popover`, `popover-anchor`, `popover-content`, `popover-description`, `popover-header`, `popover-title`, `popover-trigger`

### Accessibility

- Focus moves into the panel and back to the trigger when it closes; Escape closes it.
- Enter and exit animations run only when motion is allowed.

### Examples

On the docs page: With a form, Aligned to the start. The usage examples in the source comments (https://corsairui.vercel.app/llms/popover.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/popover.md

## Progress

A progress bar for tasks with a known amount, or an indeterminate one when the amount is unknown.

Docs: https://corsairui.vercel.app/docs/progress

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/progress
```

Also adds: `utils`.
npm: `@radix-ui/react-progress`, `clsx`, `tailwind-merge`.

### API

Built on Radix Progress (https://www.radix-ui.com/primitives/docs/components/progress); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number \| null` | `null` | 0 to max, clamped; null means the amount is unknown. |
| `max` | `number` | `100` | The value that means done. |
| `getValueLabel` | `(value, max) => string` |  | What screen readers read out; a percentage by default. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `progress`, `progress-indicator`

### Accessibility

- A progressbar with aria-valuenow and aria-valuemax. Name it with aria-label or aria-labelledby.
- Indeterminate bars slide a segment across; with prefers-reduced-motion they hold still at half opacity.

### Examples

On the docs page: Determinate, Indeterminate. The usage examples in the source comments (https://corsairui.vercel.app/llms/progress.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/progress.md

## QR Code

A crisp SVG QR code in dots, rounded or square modules. Renders on the server, and can draw itself in.

Docs: https://corsairui.vercel.app/docs/qr-code

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/qr-code
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `qrcode`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` |  | The text or URL to encode. |
| `size` | `number` | `200` | Width and height in px. |
| `variant` | `"dots" \| "rounded" \| "squares"` | `"dots"` | Module style; corners stay solid. |
| `errorCorrection` | `"L" \| "M" \| "Q" \| "H"` | `"M"` | Damage it survives, 7% to 30%. |
| `margin` | `number` | `2` | Quiet zone, in modules. |
| `color / background` | `string` | `"#0b0b0c" / "#ffffff"` | Keep it dark on light. |
| `reveal / revealDuration` | `"load" \| "in-view" / number` | `— / 800` | Draw the code in with a circle that grows from the centre. The server HTML is the full code. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `qr-code`, `qr-code-reveal`, `qr-code-reveal-shape`

State attributes:

- `data-state` (computed)

### Accessibility

- It is role="img", named after what it encodes; pass aria-label to describe it better.
- Put the link or text next to it too: not everyone can scan.

### Examples

On the docs page: Module styles, Colours and a wider quiet zone, Drawing itself in. The usage examples in the source comments (https://corsairui.vercel.app/llms/qr-code.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/qr-code.md

## Radio Group

A set of options where exactly one can be selected.

Docs: https://corsairui.vercel.app/docs/radio-group

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/radio-group
```

Also adds: `utils`.
npm: `@radix-ui/react-radio-group`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Radio Group (https://www.radix-ui.com/primitives/docs/components/radio-group); its props pass through.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `radio-group`, `radio-group-indicator`, `radio-group-item`

### Accessibility

- Arrow keys move between options; the group takes a single tab stop.
- Name the group with aria-label or a FieldSet and FieldLegend.

### Examples

On the docs page: Default, Horizontal, with a disabled option. The usage examples in the source comments (https://corsairui.vercel.app/llms/radio-group.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/radio-group.md

## Rating

Stars for giving a score as a radio group, or for showing one read-only with fractional fills.

Docs: https://corsairui.vercel.app/docs/rating

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/rating
```

Also adds: `utils`.
npm: `@radix-ui/react-radio-group`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Radio Group (https://www.radix-ui.com/primitives/docs/components/radio-group); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / defaultValue / onValueChange` | `number / number / (value) => void` |  | The score. |
| `max` | `number` | `5` | How many stars. |
| `size` | `"xs" \| "sm" \| "default" \| "lg"` | `"default"` | Stars of 12, 16, 20 or 24 px. |
| `readOnly` | `boolean` | `false` | Shows a score, fractions included, as one image. |
| `getValueLabel / starLabel` | `(value, max) => string / (n) => string` |  | What screen readers hear: "4.5 out of 5", "3 stars". |
| `name` | `string` |  | Submits the score with a form. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Each star is an empty icon (data-filled="false") under a filled one (data-filled="true"): [&_[data-filled=false]]:text-muted and [&_[data-filled=true]]:text-primary recolour them.
- size="xs" gives 12 px stars for dense cards; any other size goes through [&_[data-slot=rating-star]]:size-*.

data-slot: `rating`, `rating-item`, `rating-star`

State attributes:

- `data-fill`: `empty`, `full`, `partial`
- `data-filled`: `false`, `true`
- `data-hovered` (computed)
- `data-readonly` (computed)
- `data-size` (computed)
- `data-value` (computed)

### Accessibility

- Interactive, it is a radio group: arrow keys pick a score and each star has a name.
- Read-only, it is role="img" with the score in words.

### Examples

On the docs page: Read-only, Interactive. The usage examples in the source comments (https://corsairui.vercel.app/llms/rating.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/rating.md

## Resizable

Panels resized by dragging or with the keyboard, side by side or stacked.

Docs: https://corsairui.vercel.app/docs/resizable

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/resizable
```

Also adds: `utils`.
npm: `clsx`, `lucide-react`, `react-resizable-panels`, `tailwind-merge`.

### API

Built on react-resizable-panels (https://github.com/bvaughn/react-resizable-panels); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | ResizablePanelGroup: side by side or stacked. |
| `defaultSize / minSize / maxSize` | `string \| number` |  | ResizablePanel: strings such as "30" are percentages, numbers are pixels. |
| `collapsible / collapsedSize` | `boolean / string \| number` |  | ResizablePanel: lets it shrink to collapsedSize. |
| `withHandle` | `boolean` | `false` | ResizableHandle: a grip on the line, easier to find and grab. |
| `aria-label` | `string` | `"Resize"` | ResizableHandle: name it after the panel it resizes when there are several. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Each handle has data-separator ("inactive", "hover", "active", "focus", "disabled") and aria-orientation; the active one takes the ring colour.
- withHandle adds the grip, resizable-handle-grip. The group has data-orientation.

data-slot: `resizable-handle`, `resizable-handle-grip`, `resizable-panel`, `resizable-panel-group`

State attributes:

- `data-orientation` (computed)

### Accessibility

- Each handle is a focusable separator whose value is its position between the panels' limits.
- The arrow keys resize (Left and Right side by side, Up and Down stacked), Home and End jump to the limits, Enter collapses a collapsible panel.
- Sizes change instantly; nothing animates.

### Examples

On the docs page: Side by side, Stacked. The usage examples in the source comments (https://corsairui.vercel.app/llms/resizable.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/resizable.md

## Scroll Area

A scrolling box with thin themed scrollbars that keeps native scrolling.

Docs: https://corsairui.vercel.app/docs/scroll-area

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/scroll-area
```

Also adds: `utils`.
npm: `@radix-ui/react-scroll-area`, `clsx`, `tailwind-merge`.

### API

Built on Radix Scroll Area (https://www.radix-ui.com/primitives/docs/components/scroll-area); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `"hover" \| "always" \| "scroll" \| "auto"` | `"hover"` | When the scrollbars show. |
| `ScrollBar orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Add a horizontal one for sideways content. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `scroll-area`, `scroll-area-corner`, `scroll-area-scrollbar`, `scroll-area-thumb`, `scroll-area-viewport`

### Accessibility

- Native scrolling: wheel, touch, and arrow and Page keys once focus is inside.
- Safari does not put the box in the Tab order: keep something focusable inside when keyboard users must reach it all.
- The scrollbars are hidden from screen readers; wrap the box in a labelled section when it needs a name.

### Examples

On the docs page: Vertical, Horizontal. The usage examples in the source comments (https://corsairui.vercel.app/llms/scroll-area.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/scroll-area.md

## Segmented Control

A switch between two or more options with one thumb that slides to the active one, in three sizes. Never empty.

Docs: https://corsairui.vercel.app/docs/segmented-control

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/segmented-control
```

Also adds: `utils`, `use-media-query`.
npm: `@radix-ui/react-toggle-group`, `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

Built on Radix Toggle Group (https://www.radix-ui.com/primitives/docs/components/toggle-group); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / defaultValue / onValueChange` | `string / string / (value: string) => void` |  | The active item. Pressing it again keeps it: the control is never empty. |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Height and text size of the items. |
| `thumbClassName` | `string` |  | Classes for the sliding thumb, such as bg-primary. |
| `duration / easing` | `number / string` | `320 / "cubic-bezier(0.16, 1, 0.3, 1)"` | The slide, in ms and any CSS easing. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The thumb (segmented-control-thumb) moves by transform only and takes the active item's size. Until it is measured the active item paints the pill itself, so the server HTML already looks right.
- The root gets data-ready once the thumb is placed; the active item has data-state="on".

data-slot: `segmented-control`, `segmented-control-item`, `segmented-control-thumb`

State attributes:

- `data-size`: `default`

### Accessibility

- A radio group: one Tab stop, the arrow keys move between items, and Space or Enter picks one. Give it an aria-label.
- The thumb is decoration and hidden from screen readers.
- With prefers-reduced-motion the thumb jumps instead of sliding.

### Examples

On the docs page: Billing period, Sizes, Brand thumb, Driving a split flap. The usage examples in the source comments (https://corsairui.vercel.app/llms/segmented-control.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/segmented-control.md

## Select

Pick one option from a list, with groups, labels and separators.

Docs: https://corsairui.vercel.app/docs/select

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/select
```

Also adds: `utils`.
npm: `@radix-ui/react-select`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Select (https://www.radix-ui.com/primitives/docs/components/select); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `SelectTrigger size` | `"sm" \| "default"` | `"default"` | Trigger height. |
| `SelectContent position` | `"popper" \| "item-aligned"` | `"popper"` | Open below the trigger, or over it like a native select. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `select`, `select-content`, `select-group`, `select-icon`, `select-item`, `select-label`, `select-scroll-down-button`, `select-scroll-up-button`, `select-separator`, `select-trigger`, `select-value`

State attributes:

- `data-size` (computed)

### Accessibility

- Typeahead, arrow keys, Home and End move through the options.
- The trigger needs a label, like any form control.

### Examples

On the docs page: Default, Groups, Small, invalid and disabled. The usage examples in the source comments (https://corsairui.vercel.app/llms/select.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/select.md

## Separator

Horizontal or vertical divider.

Docs: https://corsairui.vercel.app/docs/separator

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/separator
```

Also adds: `utils`.
npm: `@radix-ui/react-separator`, `clsx`, `tailwind-merge`.

### API

Built on Radix Separator (https://www.radix-ui.com/primitives/docs/components/separator); its props pass through.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Its length is a plain class: className="w-8" makes a short rule, h-4 a short vertical one.

data-slot: `separator`

### Accessibility

- Decorative by default; pass decorative={false} when it separates content semantically.

### Examples

On the docs page: Horizontal and vertical. The usage examples in the source comments (https://corsairui.vercel.app/llms/separator.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/separator.md

## Sheet

Dialog that slides in from any edge of the screen, for menus, filters and side panels.

Docs: https://corsairui.vercel.app/docs/sheet

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/sheet
```

Also adds: `utils`.
npm: `@radix-ui/react-dialog`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Dialog (https://www.radix-ui.com/primitives/docs/components/dialog); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `"top" \| "right" \| "bottom" \| "left"` | `"right"` | SheetContent: the edge it slides in from. |
| `showCloseButton / closeLabel` | `boolean / string` | `true / "Close"` | The close icon and its name. |
| `overlayClassName` | `string` |  | SheetContent: classes for the overlay behind it. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- overlayClassName on SheetContent styles the overlay behind it.

data-slot: `sheet`, `sheet-close`, `sheet-close-button`, `sheet-content`, `sheet-description`, `sheet-footer`, `sheet-header`, `sheet-overlay`, `sheet-portal`, `sheet-title`, `sheet-trigger`

State attributes:

- `data-side` (computed)

### Accessibility

- A dialog: focus trap, Escape, scroll lock and focus return, named by SheetTitle.
- Wrap links in SheetClose so following one closes the panel.
- It appears without sliding with prefers-reduced-motion.

### Examples

On the docs page: Sides, Navigation menu. The usage examples in the source comments (https://corsairui.vercel.app/llms/sheet.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/sheet.md

## Sidebar

An application sidebar that collapses to icons or off canvas on desktop and opens as a sheet on mobile, with a keyboard shortcut.

Docs: https://corsairui.vercel.app/docs/sidebar

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/sidebar
```

Also adds: `utils`, `button`, `separator`, `sheet`, `tooltip`, `use-media-query`.
npm: `@radix-ui/react-dialog`, `@radix-ui/react-separator`, `@radix-ui/react-slot`, `@radix-ui/react-tooltip`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open / defaultOpen / onOpenChange` | `boolean / boolean / (open) => void` | `defaultOpen true` | SidebarProvider: the desktop column, controlled or not. |
| `shortcut` | `string \| null` | `"b"` | SidebarProvider: toggles it with ⌘ or Ctrl; null turns it off. |
| `width` | `string` | `"16rem"` | SidebarProvider: any CSS length, set as --sidebar-width. |
| `side / collapsible` | `"left" \| "right" / "icon" \| "offcanvas" \| "none"` | `"left" / "icon"` | Sidebar: the edge it sits on and what collapsing does. |
| `mobileLabel` | `string` | `"Navigation"` | Sidebar: the visually hidden title of the sheet below 768px. |
| `isActive / tooltip / asChild` | `boolean / string / boolean` | `false / – / false` | SidebarMenuButton: the current entry, the tooltip while collapsed to icons, and your own link (with aria-current="page" when active). |
| `useSidebar` | `() => { open, setOpen, toggle, isMobile, openMobile, setOpenMobile, state }` |  | The state, for your own controls. Throws outside a SidebarProvider. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- width on SidebarProvider sets --sidebar-width (16rem by default); the icon strip is --sidebar-width-icon, 3rem.
- The sidebar carries data-state (expanded or collapsed), data-collapsible and data-side, and is group/sidebar: its children can use group-data-[state=collapsed]/sidebar:. sidebar-wrapper has the same data-state, for the rest of the page.
- To keep it inside a box instead of the viewport, give SidebarProvider min-h-0 h-full and Sidebar h-full: they default to min-h-svh and sticky top-0 h-svh.

data-slot: `sidebar`, `sidebar-content`, `sidebar-footer`, `sidebar-group`, `sidebar-group-label`, `sidebar-header`, `sidebar-inner`, `sidebar-inset`, `sidebar-menu`, `sidebar-menu-badge`, `sidebar-menu-button`, `sidebar-menu-item`, `sidebar-separator`, `sidebar-trigger`, `sidebar-wrapper`

State attributes:

- `data-active` (computed)
- `data-collapsible` (computed)
- `data-mobile`: `true`
- `data-side` (computed)
- `data-size`: `default`
- `data-state`: `collapsed`, `expanded`

### Accessibility

- An aside landmark named "Sidebar" by aria-label, next to SidebarInset, a main landmark. Menus are lists.
- SidebarTrigger reports aria-expanded, points aria-controls at the sidebar and lists the shortcut in aria-keyshortcuts.
- Collapsed to icons, the text is visually hidden but stays the name, and tooltips show on hover and focus. Collapsed off canvas, it is inert.
- Below 768px it opens in a sheet that traps focus, closes on Escape and gives focus back. The width change is off with prefers-reduced-motion.

### Examples

On the docs page: Collapsible to icons, Off canvas, on the right. The usage examples in the source comments (https://corsairui.vercel.app/llms/sidebar.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/sidebar.md

## Site Header

A sticky page header with brand, navigation, actions and a sheet menu for small screens, flagging data-scrolled once the page scrolls.

Docs: https://corsairui.vercel.app/docs/site-header

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/site-header
```

Also adds: `utils`, `button`, `sheet`, `use-in-view`.
npm: `@radix-ui/react-dialog`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `sticky` | `boolean` | `true` | SiteHeader: sticks to the top. data-scrolled is true once the page leaves the top. |
| `SiteHeaderNav aria-label` | `string` | `"Main"` | Name of the navigation landmark; hidden below md. |
| `SiteHeaderMenu` | `label, title, side, open, onOpenChange` | `"Open menu", "Menu", "right"` | The mobile menu: a button below md that opens its children in a sheet. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `site-header`, `site-header-actions`, `site-header-brand`, `site-header-content`, `site-header-menu`, `site-header-menu-body`, `site-header-menu-trigger`, `site-header-nav`, `site-header-sentinel`

State attributes:

- `data-scrolled` (computed)
- `data-sticky` (computed)

### Accessibility

- A header landmark with a named nav. The mobile menu is a dialog with a title.
- data-scrolled comes from an IntersectionObserver, not a scroll listener.

### Examples

On the docs page: Brand, links, action and mobile menu. The usage examples in the source comments (https://corsairui.vercel.app/llms/site-header.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/site-header.md

## Skeleton

Placeholder shape shown while content loads.

Docs: https://corsairui.vercel.app/docs/skeleton

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/skeleton
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `skeleton`

### Accessibility

- Hidden from screen readers; mark the region that is loading with aria-busy="true".
- With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Profile, List. The usage examples in the source comments (https://corsairui.vercel.app/llms/skeleton.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/skeleton.md

## Slider

Pick a number or a range by dragging or with the arrow keys, horizontal or vertical.

Docs: https://corsairui.vercel.app/docs/slider

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/slider
```

Also adds: `utils`.
npm: `@radix-ui/react-slider`, `clsx`, `tailwind-merge`.

### API

Built on Radix Slider (https://www.radix-ui.com/primitives/docs/components/slider); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `thumbLabels` | `string[]` |  | An accessible name for each thumb, e.g. minimum and maximum price. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `slider`, `slider-range`, `slider-thumb`, `slider-track`

### Accessibility

- Arrow keys step, Page Up and Page Down jump, Home and End go to the ends.
- Each thumb is announced with its own value.

### Examples

On the docs page: Default, Range, Vertical and disabled. The usage examples in the source comments (https://corsairui.vercel.app/llms/slider.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/slider.md

## Spinner

Animated status indicator for work in progress, announced as Loading.

Docs: https://corsairui.vercel.app/docs/spinner

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/spinner
```

Also adds: `utils`.
npm: `clsx`, `lucide-react`, `tailwind-merge`.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `spinner`

### Accessibility

- Announced as "Loading" by default; pass a more specific aria-label when you can.
- Slows down instead of stopping with prefers-reduced-motion, so progress stays visible.

### Examples

On the docs page: Sizes, Inline. The usage examples in the source comments (https://corsairui.vercel.app/llms/spinner.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/spinner.md

## Spotify Card

A track card with cover art, a link to Spotify and a preview clip that spins a record while it plays.

Docs: https://corsairui.vercel.app/docs/spotify-card

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/spotify-card
```

Also adds: `utils`, `spotify-track`.
npm: `clsx`, `lucide-react`, `spotify-url-info`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `track` | `SpotifyTrack` |  | From getSpotifyTrack on the server. |
| `volume` | `number` | `0.4` | Preview volume, from 0 to 1. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `spotify-card`, `spotify-card-record`, `spotify-card-skeleton`

State attributes:

- `data-state`: `idle`, `playing`

### Accessibility

- The cover is a toggle button ("Play preview of …") with aria-pressed; only one preview plays at a time.
- The record holds still with prefers-reduced-motion.

### Examples

On the docs page: From a link, Without a preview clip. The usage examples in the source comments (https://corsairui.vercel.app/llms/spotify-card.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/spotify-card.md

## Stat

Key figures as a description list: label, value and a caption with an optional trend arrow.

Docs: https://corsairui.vercel.app/docs/stat

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/stat
```

Also adds: `utils`.
npm: `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `trend` | `"up" \| "down" \| "flat"` |  | StatCaption: an arrow and words for screen readers. |
| `trendLabel` | `string` | `"Increase" / "Decrease" / "No change"` | Those words. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `stat`, `stat-caption`, `stat-group`, `stat-label`, `stat-trend-icon`, `stat-value`

State attributes:

- `data-trend` (computed)

### Accessibility

- StatGroup is a description list, so each label is read with its value.
- The trend is not shown by colour and icon alone.

### Examples

On the docs page: With trends and tickers. The usage examples in the source comments (https://corsairui.vercel.app/llms/stat.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/stat.md

## Stepper

The steps of a multi-step flow with complete, current and upcoming states, horizontal or vertical.

Docs: https://corsairui.vercel.app/docs/stepper

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/stepper
```

Also adds: `utils`.
npm: `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value / defaultValue / onValueChange` | `number / number / (value) => void` | `defaultValue 0` | The current step, 0-based; steps before it are complete. |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Steps in a row or a column. |
| `linear` | `boolean` | `true` | StepperTrigger stays disabled on steps not reached yet. |
| `label / labels` | `string / Partial` | `"Progress" / "Completed", "Current", "Upcoming"` | The nav's name, and the state read after each step number. |
| `step` | `number` |  | StepperItem: this step's 0-based index. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Every stepper-item, stepper-indicator and stepper-trigger has data-state: "complete", "current" or "upcoming".
- Inside an item, group-data-[state=complete]/stepper-item: styles any child; the separator uses it to colour the line.

data-slot: `stepper`, `stepper-description`, `stepper-indicator`, `stepper-item`, `stepper-list`, `stepper-separator`, `stepper-title`, `stepper-trigger`

State attributes:

- `data-orientation` (computed)
- `data-state` (computed)

### Accessibility

- A nav landmark around an ordered list, so the number of steps is announced.
- The current step has aria-current="step"; each indicator reads its state after the number, since the check and colours are not read.
- StepperTrigger is a native button: Tab reaches it, Enter or Space moves to the step.
- Colour changes are off with prefers-reduced-motion.

### Examples

On the docs page: Checkout, Vertical, with descriptions. The usage examples in the source comments (https://corsairui.vercel.app/llms/stepper.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/stepper.md

## Story

A 1080 × 1920 story put together from parts (header, eyebrow, title, grid of tiles, list of rows, highlight, image, call to action), laid out inside the safe area, with themes, patterns and a background photo. Drawn by Satori: render it with story-frame or renderStoryPng().

Docs: https://corsairui.vercel.app/docs/story

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/story
```

Also adds: `story-export`.
npm: `satori`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `theme / colors` | `"midnight" \| "paper" \| "ocean" \| "sunset" \| "mono" \| StoryTheme / Partial` | `"midnight"` | A preset or colours of your own, and overrides on top: { accent: "#e11d48" }. Exported as STORY_THEMES. |
| `decoration` | `"none" \| "glow" \| "dots" \| "grid"` | `"none"` | A light or a pattern behind the content, in the theme's colours. |
| `image / imageDim` | `string / number` | `— / 0.55` | A photo behind everything (a URL that allows CORS, or a data URL), dimmed and faded into the background. |
| `fontFamily / radius / justify / gap` | `string / number / "start" \| "center" \| "end" \| "between" / number` | `first font / 36 / "start" / 48` | The family to draw with, the corners of tiles and cards, and how parts spread. |
| `Parts` | `StoryHeader, StoryBrand, StoryEyebrow, StoryTitle, StoryText, StoryBody, StoryGrid, StoryTile, StoryList, StoryRow, StoryChip, StoryHighlight, StoryImage, StoryFooter, StoryCta, StoryLinkHint, StoryDivider` |  | Put together inside Story. Grids and rows take max and more, so long lists end in a +N instead of overflowing. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Content sits inside the safe area, clear of the profile bar and the reply field. StoryBody takes the height left, so the footer stays at the bottom.
- Parts take style, and your own elements can read the theme as CSS variables: var(--story-accent), var(--story-surface), var(--story-muted).
- Like everything Satori draws, it is inline styles and flexbox, no classes. Render it with story-frame or renderStoryPng() in the browser.
- On a server it renders with next/og: return new ImageResponse(…, { width: 1080, height: 1920, fonts: [{ name: "Barlow", data, weight: 700 }] }) from a route, then link or download the PNG.

### Accessibility

- storyAlt(story) reads the story in order for the image's alt. Tiles and chips with an alt are read by it ("19h, booked").

### Examples

On the docs page: Five stories, one set of parts, Themes, Long lists and other languages. The usage examples in the source comments (https://corsairui.vercel.app/llms/story.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/story.md

## Story Frame

A 9:16 story preview that is the exported PNG itself, with Share (the phone's share sheet, Instagram included), Download and Copy link, and the safe area the apps draw over.

Docs: https://corsairui.vercel.app/docs/story-frame

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/story-frame
```

Also adds: `utils`, `button`, `story-export`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `satori`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `story` | `ReactNode` |  | The story element, such as a Story. Keep it stable (useMemo) so it is not drawn on every render. |
| `fonts` | `StoryFont[] \| null` |  | From useStoryFonts([{ name, url, weight }]). The frame waits while it is null. |
| `alt` | `string` |  | What the story shows, for screen readers. |
| `link` | `string` |  | Copied when sharing and by its own button: Instagram takes no links from other apps, so the person pastes it into a link sticker. |
| `fileName / shareTitle` | `string` | `"story.png"` | Name of the shared or downloaded file, and the title some share sheets show. |
| `defaultShowSafeArea` | `boolean` | `false` | Starts with the bands the apps draw over shown. |
| `labels` | `Partial` |  | Every word on the buttons and in the announcements, for other languages. |
| `onShare / onError` | `(result: "shared" \| "cancelled" \| "unsupported" \| "downloaded") => void / (error) => void` |  | After each share or download, and when drawing fails. |
| `children` | `ReactNode` |  | More buttons, after the built-in ones. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The root has data-state="rendering" | "ready" | "error". The preview keeps its 9:16 box while the first image draws, so nothing moves.
- useStoryImage(story, { fonts }) is the drawing without the frame, for a layout of your own.
- The React Native StoryFrame lays the story out at full size, captures it with react-native-view-shot and shares it with expo-sharing.

data-slot: `story-frame`, `story-frame-actions`, `story-frame-preview`, `story-frame-safe-area`

State attributes:

- `data-state`: `copied`, `idle`

### Accessibility

- The preview is an  named by alt. A polite live region says when the story is drawn, shared or downloaded.
- Safe area is a toggle button with aria-pressed.

### Examples

On the docs page: Share a story, Your own template. The usage examples in the source comments (https://corsairui.vercel.app/llms/story-frame.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/story-frame.md

## Switch

On/off control for settings that apply immediately.

Docs: https://corsairui.vercel.app/docs/switch

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/switch
```

Also adds: `utils`.
npm: `@radix-ui/react-switch`, `clsx`, `tailwind-merge`.

### API

Built on Radix Switch (https://www.radix-ui.com/primitives/docs/components/switch); its props pass through.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `switch`, `switch-thumb`

### Accessibility

- Announced as a switch with its on or off state; Space toggles it.
- For settings that apply at once; inside a submitted form, prefer a checkbox.

### Examples

On the docs page: Default, Settings list. The usage examples in the source comments (https://corsairui.vercel.app/llms/switch.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/switch.md

## Table

A data table on native table elements, with a scroll container, selected rows and sortable column headers.

Docs: https://corsairui.vercel.app/docs/table

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/table
```

Also adds: `utils`.
npm: `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `scrollLabel` | `string` |  | Table: names the scroll container and makes it a focusable region. |
| `sort` | `"asc" \| "desc" \| false` |  | TableHead: sets aria-sort; leave it out on columns that do not sort. |
| `direction / onSort` | `"asc" \| "desc" \| false / (next) => void` | `false` | TableSortButton: the arrow shown and the next direction on click. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `table`, `table-body`, `table-caption`, `table-cell`, `table-container`, `table-footer`, `table-head`, `table-header`, `table-row`, `table-sort-button`, `table-sort-icon`

State attributes:

- `data-direction`: `none`

### Accessibility

- Native table elements: rows, columns and headers come for free; TableCaption names it.
- With scrollLabel, a wide table can be reached with Tab and scrolled with the arrow keys.
- Selected rows take data-state="selected"; say so in a cell too, with a labelled checkbox.
- Pass the same direction to TableSortButton and TableHead sort, so the column's aria-sort is read.

### Examples

On the docs page: Cargo manifest. The usage examples in the source comments (https://corsairui.vercel.app/llms/table.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/table.md

## Tabs

Sections of content shown one at a time, with a segmented or underlined tab list, horizontal or vertical.

Docs: https://corsairui.vercel.app/docs/tabs

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/tabs
```

Also adds: `utils`.
npm: `@radix-ui/react-tabs`, `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

Built on Radix Tabs (https://www.radix-ui.com/primitives/docs/components/tabs); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "line"` | `"default"` | TabsList: pills on a track, or an underline. |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Tabs: arrow keys follow it. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `tabs`, `tabs-content`, `tabs-list`, `tabs-trigger`

State attributes:

- `data-variant` (computed)

### Accessibility

- Arrow keys move between tabs, Home and End jump; Tab moves into the panel.

### Examples

On the docs page: Pills, Underline, Vertical. The usage examples in the source comments (https://corsairui.vercel.app/llms/tabs.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/tabs.md

## Textarea

Multi-line text control with the same states as Input.

Docs: https://corsairui.vercel.app/docs/textarea

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/textarea
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `textarea`

### Accessibility

- The error style comes from aria-invalid="true".

### Examples

On the docs page: Default, Invalid, Disabled. The usage examples in the source comments (https://corsairui.vercel.app/llms/textarea.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/textarea.md

## Timeline

A vertical list of events with a coloured dot for each status and a line joining them.

Docs: https://corsairui.vercel.app/docs/timeline

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/timeline
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `status` | `"default" \| "success" \| "warning" \| "destructive"` | `"default"` | TimelineItem: colours the dot. |
| `dateTime` | `string` |  | TimelineTime: the machine-readable moment; the visible text is yours. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Each timeline-item has data-status ("default", "success", "warning", "destructive"); the dot reads it through group-data-[status=…]/timeline-item.
- The dot uses --success, --warning and --destructive, with a ring in --background to cut the line.

data-slot: `timeline`, `timeline-connector`, `timeline-content`, `timeline-description`, `timeline-dot`, `timeline-item`, `timeline-time`, `timeline-title`

State attributes:

- `data-status` (computed)

### Accessibility

- An ordered list, so the number of events and each one's position are announced.
- The dot and the line are hidden from screen readers: when the status matters, say it in the text.
- TimelineTime is a time element with a machine-readable dateTime.

### Examples

On the docs page: Order history, Deployments. The usage examples in the source comments (https://corsairui.vercel.app/llms/timeline.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/timeline.md

## Toast

Brief notifications shown with a toast() call and a Toaster mounted once, with variants, an optional action, swipe to dismiss and screen reader announcements.

Docs: https://corsairui.vercel.app/docs/toast

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/toast
```

Also adds: `utils`.
npm: `@radix-ui/react-toast`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Toast (https://www.radix-ui.com/primitives/docs/components/toast); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `toast(options)` | `{ title, description, action, variant, duration }` |  | Shows a toast; returns { id, dismiss, update }. toast.dismiss(id?) closes one or all. |
| `variant` | `"default" \| "success" \| "destructive"` | `"default"` | Destructive toasts interrupt screen readers; the others wait their turn. |
| `action` | `{ label, onClick, altText }` |  | altText says how to do the same without the toast. |
| `position` | `"top-left" \| "top-center" \| … \| "bottom-right"` | `"bottom-right"` | Toaster: where toasts stack. Mount it once. |
| `duration / label / closeLabel` | `number / string / string` | `5000 / "Notifications" / "Close"` | Toaster: time before closing, the region's name, the close button's name. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `toast`, `toast-action`, `toast-body`, `toast-close`, `toast-description`, `toast-icon`, `toast-title`, `toaster`

State attributes:

- `data-position` (computed)
- `data-variant` (computed)

### Accessibility

- Each toast is announced when it appears. F8 moves focus to the stack.
- The timer pauses on hover, on focus and while the window is in the background.
- At most three are open; toasts can be swiped away. No sliding with prefers-reduced-motion.

### Examples

On the docs page: Variants, With an action. The usage examples in the source comments (https://corsairui.vercel.app/llms/toast.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/toast.md

## Toggle

A button that stays pressed, in default and outline variants and three sizes.

Docs: https://corsairui.vercel.app/docs/toggle

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/toggle
```

Also adds: `utils`.
npm: `@radix-ui/react-toggle`, `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

Built on Radix Toggle (https://www.radix-ui.com/primitives/docs/components/toggle); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "outline"` | `"default"` | Plain or with a border. |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Height. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `toggle`

### Accessibility

- Announced with aria-pressed; icon-only toggles need an aria-label.

### Examples

On the docs page: Default, Outline with text, Sizes. The usage examples in the source comments (https://corsairui.vercel.app/llms/toggle.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/toggle.md

## Toggle Group

A set of toggles with single or multiple selection, joined or spaced apart.

Docs: https://corsairui.vercel.app/docs/toggle-group

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/toggle-group
```

Also adds: `utils`, `toggle`.
npm: `@radix-ui/react-toggle`, `@radix-ui/react-toggle-group`, `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

Built on Radix Toggle Group (https://www.radix-ui.com/primitives/docs/components/toggle-group); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `"single" \| "multiple"` |  | One pressed item at a time, or any number. |
| `spacing` | `0 \| 1 \| 2` | `0` | 0 joins the items into one bar. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `toggle-group`, `toggle-group-item`

State attributes:

- `data-size`: `default`
- `data-spacing` (computed)
- `data-variant`: `default`

### Accessibility

- A toolbar with one tab stop; arrow keys move between items.
- Give the group an aria-label.

### Examples

On the docs page: Single, Multiple, Spaced apart. The usage examples in the source comments (https://corsairui.vercel.app/llms/toggle-group.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/toggle-group.md

## Tooltip

A short label shown on hover and keyboard focus.

Docs: https://corsairui.vercel.app/docs/tooltip

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/tooltip
```

Also adds: `utils`.
npm: `@radix-ui/react-tooltip`, `clsx`, `tailwind-merge`.

### API

Built on Radix Tooltip (https://www.radix-ui.com/primitives/docs/components/tooltip); its props pass through.

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `tooltip`, `tooltip-content`, `tooltip-provider`, `tooltip-trigger`

### Accessibility

- Opens on hover and on keyboard focus; Escape closes it.
- It adds to the trigger's name: icon-only buttons still need an aria-label.

### Examples

On the docs page: Default, Sides. The usage examples in the source comments (https://corsairui.vercel.app/llms/tooltip.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/tooltip.md

## Tweet Card

A post from X in your theme, with no embed script: a card, a server component that fetches by id, and a client one.

Docs: https://corsairui.vercel.app/docs/tweet-card

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/tweet-card
```

Also adds: `utils`.
npm: `clsx`, `lucide-react`, `react-tweet`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `tweet` | `Tweet` |  | TweetCard: the post, from getTweet, useTweet or your own fetch. |
| `id` | `string` |  | Tweet (server) and ClientTweet (browser): fetch by id. |
| `showDate / showLikes / showCopyLink` | `boolean` | `true` | Parts to show. |
| `locale / timeZone` | `string` | `"en-US" / "UTC"` | For the date. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `tweet-card`, `tweet-card-skeleton`, `tweet-card-unavailable`

### Accessibility

- It is an article named after the author; the text is selectable and its links are real links.
- The text is decoded as text, never injected as HTML.
- Photos carry their alt text; videos have controls instead of autoplaying.

### Examples

On the docs page: Fetched by id, Loading. The usage examples in the source comments (https://corsairui.vercel.app/llms/tweet-card.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/tweet-card.md

## YouTube Embed

A YouTube video that costs one poster image until it is played, with player controls through its ref.

Docs: https://corsairui.vercel.app/docs/youtube-embed

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/youtube-embed
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `videoId / title` | `string` |  | The video, and its name. |
| `load` | `"click" \| "in-view" \| "eager"` | `"click"` | When the player loads. |
| `poster / posterSrc` | `"hqdefault" \| "sddefault" \| "maxresdefault" / string` | `"hqdefault"` | YouTube's thumbnail, or your own image. |
| `autoplay / muted / loop / controls / start` | `boolean / boolean / boolean / boolean / number` |  | Player options. Autoplay without a click is muted. |
| `group` | `string` |  | Starting one embed pauses the others in its group. |
| `onPlay / onPause / onEnd` | `() => void` |  | Player state changes; data-state has it too. |
| `ref` | `{ play, pause, mute, unmute, setVolume, seekTo, load }` |  | Control it from your own buttons. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The ref handle plays, pauses, mutes, sets the volume (0 to 100) and seeks; load="in-view" with autoplay starts it muted when it scrolls into view, as browsers require.

data-slot: `youtube-embed`, `youtube-embed-iframe`, `youtube-embed-play`, `youtube-embed-poster`

State attributes:

- `data-state` (computed)

### Accessibility

- The play button is named after the video ("Play: Big Buck Bunny") and the iframe has a title.
- Pressing it moves focus into the player. youtube-nocookie.com by default.

### Examples

On the docs page: Click to load, A group, Controlled through ref. The usage examples in the source comments (https://corsairui.vercel.app/llms/youtube-embed.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/youtube-embed.md

## Blur Text

Text that comes into focus one character at a time, and can blur back out.

Docs: https://corsairui.vercel.app/docs/blur-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/blur-text
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` |  | Plain text; every character comes in. |
| `as` | `"p" \| "h1"…"h6" \| "span" \| "div"` | `"p"` | The element. |
| `trigger` | `"load" \| "in-view"` | `"load"` | On first paint, from CSS alone, or once in view. |
| `play` | `boolean` |  | Manual control: false holds, true plays. |
| `show` | `boolean` | `true` | false blurs it back out, last character first. |
| `delay / stagger / duration` | `number` | `0 / 20 / 600` | In ms. |
| `onAnimationComplete` | `() => void` |  | After the last character. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `blur-text`, `blur-text-character`

State attributes:

- `data-last` (computed)
- `data-state`: `hidden`

### Accessibility

- Screen readers get the sentence in one piece; the animated characters are hidden from them.
- Words never break between characters.
- Hidden with show={false}, it is hidden from screen readers too. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: On load, Out and back. The usage examples in the source comments (https://corsairui.vercel.app/llms/blur-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/blur-text.md

## Dissolve Text

Words or characters surfacing at scattered moments, the same on server and client.

Docs: https://corsairui.vercel.app/docs/dissolve-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/dissolve-text
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `split` | `"words" \| "characters"` | `"words"` | What fades in on its own. |
| `trigger / play` | `"load" \| "in-view" / boolean` | `"load"` | As in blur-text. |
| `delay / spread / duration` | `number` | `200 / 230 / 1000` | In ms: the wait, the window pieces pick their start from, and each fade. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `dissolve-text`, `dissolve-text-piece`

State attributes:

- `data-state` (computed)

### Accessibility

- Screen readers get the text in one piece.
- The scatter is seeded by the text, so hydration never mismatches. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Words, Characters. The usage examples in the source comments (https://corsairui.vercel.app/llms/dissolve-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/dissolve-text.md

## Flip Text

Kinetic type in 3D: characters or words turning into place around their horizontal axis, one after another.

Docs: https://corsairui.vercel.app/docs/flip-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/flip-text
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` |  | Plain text. |
| `split` | `"characters" \| "words"` | `"characters"` | What turns on its own. |
| `from` | `"first" \| "last" \| "center"` | `"first"` | Where the wave starts. |
| `angle` | `number` | `-90` | Starting turn in degrees: negative tips the top away, positive towards you. |
| `perspective` | `number` | `600` | Distance to the viewer in px; lower is deeper. |
| `origin` | `"center" \| "bottom" \| "top"` | `"center"` | The axis: a drum roll, standing up from the baseline, or dropping from the top. |
| `trigger / play` | `"load" \| "in-view" / boolean` | `"load"` | As in blur-text. |
| `delay / stagger / duration` | `number` | `0 / 40 / 800` | In ms. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Each flip-text-piece is an inline-block with its back face hidden; the root takes the type classes.
- The turn reads --flip-text-angle and --flip-text-perspective, which the props set on the root. data-state is "armed", "play" or "static".

data-slot: `flip-text`, `flip-text-piece`

State attributes:

- `data-last` (computed)
- `data-state` (computed)

### Accessibility

- Screen readers get the text in one piece; the turning pieces are hidden from them.
- Words never break between characters. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Rolling like a drum, Words standing up. The usage examples in the source comments (https://corsairui.vercel.app/llms/flip-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/flip-text.md

## Highlight Text

A marker that slides in behind a phrase, or a hand-drawn line drawn under it.

Docs: https://corsairui.vercel.app/docs/highlight-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/highlight-text
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"marker" \| "scribble"` | `"marker"` | A block behind the text, or a hand-drawn line under it. |
| `scribble / strokeWidth` | `"underline" \| "double" / number` | `"underline" / 3` | Scribble: one line or two, and their width. |
| `from` | `"left" \| "right" \| "top" \| "bottom"` | `"bottom"` | The side the marker slides in from. |
| `inverse` | `boolean` | `true` | Invert the text under the marker. Turn off for translucent markers. |
| `markerClassName` | `string` |  | Classes for the marker or the scribble, e.g. its colour. |
| `trigger / play` | `"load" \| "in-view" / boolean` | `"load"` | As in blur-text. |
| `delay / duration` | `number` | `0 / 600` | In ms. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `highlight-text`, `highlight-text-marker`, `highlight-text-scribble`

State attributes:

- `data-state` (computed)
- `data-variant`: `marker`, `scribble`

### Accessibility

- The marker is aria-hidden; the text stays plain text in the sentence.
- Check the contrast of text over custom marker colours. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Inverted, Coloured markers, Scribble. The usage examples in the source comments (https://corsairui.vercel.app/llms/highlight-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/highlight-text.md

## Number Ticker

A number that counts up to its value when it comes into view, formatted for the locale.

Docs: https://corsairui.vercel.app/docs/number-ticker

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/number-ticker
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number` |  | Where it ends. |
| `from` | `number` | `0` | Where it starts. |
| `duration / delay` | `number` | `1600 / 0` | In ms. |
| `locale / format` | `string / Intl.NumberFormatOptions` | `"en-US"` | How the number is written. |
| `trigger / once / play` | `"load" \| "in-view" / boolean / boolean` | `"in-view" / true` | When it counts. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `number-ticker`, `number-ticker-value`

State attributes:

- `data-state` (computed)

### Accessibility

- Screen readers get the final value once; the moving digits are hidden from them.
- The server HTML has the final value. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Formats. The usage examples in the source comments (https://corsairui.vercel.app/llms/number-ticker.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/number-ticker.md

## Roll Text

Characters that roll up to a copy of themselves on hover or keyboard focus.

Docs: https://corsairui.vercel.app/docs/roll-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/roll-text
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` |  | The text. |
| `stagger / duration` | `number` | `20 / 400` | In ms. |
| `as` | `"span" \| "div" \| "p" \| "h1"…"h6"` | `"span"` | The element. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `roll-text`, `roll-text-character`, `roll-text-visual`

### Accessibility

- Screen readers get the text once. It rolls on hover and on keyboard focus of a parent with the group class.
- It holds still with prefers-reduced-motion.

### Examples

On the docs page: In links, Slower, wider stagger. The usage examples in the source comments (https://corsairui.vercel.app/llms/roll-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/roll-text.md

## Scramble Text

Text that decodes itself: noise sweeps across, then each character locks in.

Docs: https://corsairui.vercel.app/docs/scramble-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/scramble-text
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` |  | Plain text. |
| `speed` | `number` | `40` | Ms between one character settling and the next. |
| `characters` | `string` | `"01<>[]{}/\\|=~^"` | The noise shown before each character settles. |
| `trigger / play` | `"load" \| "in-view" / boolean` | `"load"` | As in blur-text. |
| `delay` | `number` | `0` | Ms before it starts. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `scramble-text`, `scramble-text-output`

State attributes:

- `data-state` (computed)

### Accessibility

- The real text is in the server HTML and read in one piece by screen readers.
- It writes to the DOM once per frame, without re-rendering React.
- With prefers-reduced-motion the text just appears.

### Examples

On the docs page: Default, Binary noise. The usage examples in the source comments (https://corsairui.vercel.app/llms/scramble-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/scramble-text.md

## Shimmer Text

A band of light sweeping across text now and then, paused off screen.

Docs: https://corsairui.vercel.app/docs/shimmer-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/shimmer-text
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `duration / pause / delay` | `number` | `1.8 / 2 / 0.8` | In seconds. |
| `highlight` | `string` |  | Colour of the light; defaults to the page colour mixed into the text. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `shimmer-text`

### Accessibility

- The text keeps its colour; only a band of light passes over it.
- It pauses off screen. With prefers-reduced-motion it is plain text.

### Examples

On the docs page: Status line, Heading. The usage examples in the source comments (https://corsairui.vercel.app/llms/shimmer-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/shimmer-text.md

## Slide Text

Words, characters or lines rising into place from below their own baseline.

Docs: https://corsairui.vercel.app/docs/slide-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/slide-text
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` |  | Plain text; "\n" separates lines. |
| `split` | `"words" \| "characters" \| "lines"` | `"words"` | What rises on its own. |
| `from` | `"first" \| "last" \| "center"` | `"first"` | Where the wave starts. |
| `trigger / play` | `"load" \| "in-view" / boolean` | `"load"` | As in blur-text. |
| `delay / stagger / duration` | `number` | `0 / 80 / 650` | In ms. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `slide-text`, `slide-text-piece`

State attributes:

- `data-last` (computed)
- `data-state` (computed)

### Accessibility

- Screen readers get the text in one piece.
- The mask leaves room for descenders, so nothing is clipped. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Words, Characters from the centre, Lines. The usage examples in the source comments (https://corsairui.vercel.app/llms/slide-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/slide-text.md

## Split Flap

A split-flap board: when the value changes, only the characters that changed flip, like a departure board.

Docs: https://corsairui.vercel.app/docs/split-flap

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/split-flap
```

Also adds: `utils`, `use-in-view`, `use-media-query`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` |  | The text on the board; each character is a cell. |
| `length / pad` | `number / string` | `— / " "` | Cells to reserve, padded at the start, so nothing around the board moves. |
| `cycle` | `string` |  | Characters a cell steps through on its way, like 0123456789 for a clock. |
| `from / stagger / duration` | `"first" \| "last" / number / number` | `"first" / 40 / 300` | Which end flips first, the wait between cells and one flip, in ms. |
| `cellClassName / divider` | `string / boolean` | `— / false` | Turn cells into tiles, and draw the line where the flaps meet. |
| `live` | `"off" \| "polite"` | `"off"` | Announce each new value to screen readers. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Give the cells a background with cellClassName (bg-card rounded-sm px-1): the flaps take the cell's background, which hides the half behind them.
- Cells are 1ch wide with tabular numbers. Wide letters in proportional fonts look best with font-mono or a wider cell (w-[1.2ch]).
- Flipping cells have data-flipping; the root has data-state "idle" or "flipping".

data-slot: `split-flap`, `split-flap-cell`, `split-flap-character`, `split-flap-divider`, `split-flap-flap`, `split-flap-half`

State attributes:

- `data-flipping` (computed)
- `data-half` (computed)
- `data-state`: `flipping`, `idle`

### Accessibility

- Screen readers get the value in one piece; the cells are hidden from them.
- The server HTML already shows the value, and the first render never flips.
- Off screen, or with prefers-reduced-motion, the characters swap in place.

### Examples

On the docs page: Departure board, A clock that steps through digits, Plain cells. The usage examples in the source comments (https://corsairui.vercel.app/llms/split-flap.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/split-flap.md

## Text Reveal

Reveals text word by word, when it comes into view or tied to the scroll position.

Docs: https://corsairui.vercel.app/docs/text-reveal

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/text-reveal
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` |  | Plain text, split into words. |
| `as` | `"h1" … "h6" \| "p" \| "span" \| "div"` | `"p"` | The element to render. |
| `trigger` | `"in-view" \| "scroll"` | `"in-view"` | Words come in once, or follow the scroll position. |
| `stagger / duration` | `number` | `40 / 500` | In-view timing, in ms. |
| `dim` | `number` | `0.15` | Scroll: opacity of words not reached yet. |
| `range` | `[start, end]` | `[10, 55]` | Scroll: where the reveal runs, as % of the text crossing the viewport. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `text-reveal`, `text-reveal-word`

State attributes:

- `data-state`: `hidden`, `visible`
- `data-trigger` (computed)

### Accessibility

- Screen readers get the sentence in one piece; the animated words are hidden from them.
- With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: In view, Tied to the scroll. The usage examples in the source comments (https://corsairui.vercel.app/llms/text-reveal.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/text-reveal.md

## Text Signature

Writes any text in any font as a signature: outlines drawn by the pen, then inked.

Docs: https://corsairui.vercel.app/docs/text-signature

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/text-signature
```

Also adds: `utils`, `use-in-view`, `signature`.
npm: `clsx`, `opentype.js`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `text` | `string` |  | What to write. |
| `font` | `string` |  | URL of a TTF, OTF or WOFF font (not WOFF2), served from your site or with CORS. |
| `fontSize` | `number` | `72` | Size in viewBox units; scale the SVG with CSS. |
| `ink / inkWidth` | `boolean / number` | `true / fontSize × 0.18` | Fill behind the pen. |
| `…` | `SignatureProps` |  | Timing, trigger and stroke props of Signature. |

### Accessibility

- It is role="img", named by the text unless you pass an aria-label.
- The font parser loads on demand, only on pages that use it. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: In a handwriting font, Finer pen, slower hand. The usage examples in the source comments (https://corsairui.vercel.app/llms/text-signature.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/text-signature.md

## Wave Text

Bands of colour washing through text like a swell, on one CSS animation.

Docs: https://corsairui.vercel.app/docs/wave-text

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/wave-text
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors` | `string[]` |  | Colours of the bands, in order. |
| `base` | `string` | `"currentColor"` | The text colour between waves. |
| `shape` | `"radial" \| "linear"` | `"radial"` | Ripples from below, or rising. |
| `bands / bandWidth` | `number` | `8 / 4` | How many bands, and how wide, in %. |
| `duration` | `number` | `3.75` | Seconds for one wave. |
| `repeat` | `boolean` | `false` | Keep sending waves. |
| `trigger / play` | `"load" \| "in-view" / boolean` | `"load"` | As in blur-text. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `wave-text`

State attributes:

- `data-state` (computed)

### Accessibility

- The text rests in its own colour, so contrast holds between waves.
- Repeating waves pause off screen. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Ripples from below, Rising, on repeat. The usage examples in the source comments (https://corsairui.vercel.app/llms/wave-text.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/wave-text.md

## Custom Cursor

A dot and a trailing ring that follow a mouse pointer, growing over interactive elements and showing labels.

Docs: https://corsairui.vercel.app/docs/custom-cursor

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/custom-cursor
```

Also adds: `utils`, `use-media-query`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size / hoverSize / dotSize` | `number` | `12 / 40 / 4` | Ring, ring over something clickable, and the dot, in px. |
| `smoothing` | `number` | `0.2` | How quickly the ring catches up, 0 to 1. |
| `blend` | `boolean` | `true` | Invert what is underneath (mix-blend-difference). |
| `labels` | `Record` |  | Shown in the ring over data-cursor="". |
| `interactiveSelector / hideNativeCursor` | `string / boolean` | `links, buttons, fields / true` | What counts as clickable; whether the native cursor hides. |
| `children` | `ReactNode` |  | Drawn with the cursor; --cursor-x and --cursor-y follow the pointer. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- labels maps a data-cursor key to any content (text, an icon, a badge) shown inside the ring over elements with that data-cursor.
- hideNativeCursor={false} keeps the system cursor; blend={false} drops mix-blend-difference when the ring is recoloured.
- A data-cursor on  or  gives the whole page a label without making it interactive.

data-slot: `custom-cursor`, `custom-cursor-dot`, `custom-cursor-label`, `custom-cursor-ring`, `custom-cursor-ring-shape`

State attributes:

- `data-cursor`: `view`
- `data-label` (computed)
- `data-state` (computed)

### Accessibility

- Only for mouse and pen: on touch screens and with prefers-reduced-motion the native cursor stays.
- Decorative and hidden from screen readers; it never takes clicks.

### Examples

On the docs page: Hover states and labels. The usage examples in the source comments (https://corsairui.vercel.app/llms/custom-cursor.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/custom-cursor.md

## Magnetic

Pulls its content toward the mouse while hovered and springs it back on leave.

Docs: https://corsairui.vercel.app/docs/magnetic

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/magnetic
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `strength` | `number` | `0.3` | How far the content follows the pointer, as a fraction of its offset. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `magnetic`, `magnetic-content`

### Accessibility

- Only mouse and pen move it; the hit area and keyboard focus stay where they are.
- With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Strengths. The usage examples in the source comments (https://corsairui.vercel.app/llms/magnetic.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/magnetic.md

## Marquee

Content scrolling in a seamless loop, pausing on focus and off screen, and draggable by the pointer.

Docs: https://corsairui.vercel.app/docs/marquee

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/marquee
```

Also adds: `utils`, `use-in-view`, `use-media-query`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `direction` | `"left" \| "right" \| "up" \| "down"` | `"left"` | Which way the content travels. |
| `duration` | `number` | `20` | Seconds to travel its own length. |
| `gap` | `string` | `"1rem"` | Space between items, any CSS length. |
| `pauseOnHover` | `boolean` | `false` | Hold still under the pointer. |
| `fade / fadeAmount` | `boolean / number` | `true / 10` | Fade the edges, in %. |
| `repeat` | `number` | `2` | Copies in the loop; raise it for short content. |
| `draggable` | `boolean` | `false` | Grab the loop and drag it either way; it carries on from where it is let go. A press that moves under 5 px is still a click. |
| `label` | `string` | `"Scrolling content"` | Names the scroll box it becomes with reduced motion. |
| `paused` | `boolean` | `false` | Hold it still from outside, such as while a dialog is open. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- With draggable the root has data-draggable, and data-dragging="true" while it is held; the track pauses then.

data-slot: `marquee`, `marquee-group`, `marquee-track`

State attributes:

- `data-direction` (computed)
- `data-draggable` (computed)
- `data-paused` (computed)

### Accessibility

- The copies are aria-hidden and inert: one set of links for keyboards and screen readers.
- It stops while a link inside has focus, and pauses off screen.
- With prefers-reduced-motion it holds still and scrolls by hand, and draggable does nothing.
- Dragging is an extra for pointers: nothing is only reachable by dragging, and the click that ends a drag does not follow a link.

### Examples

On the docs page: Ports of call, Both ways, Vertical, Draggable, Paused from outside. The usage examples in the source comments (https://corsairui.vercel.app/llms/marquee.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/marquee.md

## Parallax

Drifts content at a different speed from the page as it scrolls past, with CSS scroll-driven animations and no JavaScript.

Docs: https://corsairui.vercel.app/docs/parallax

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/parallax
```

Also adds: `utils`.
npm: `@radix-ui/react-slot`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `offset` | `number` | `40` | Drift each way in px; negative values run ahead of the scroll. |
| `asChild` | `boolean` | `false` | Move the child instead of a wrapper. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `parallax`

### Accessibility

- Purely visual: nothing about the content or its order changes.
- Browsers without scroll-driven animations keep it still.
- With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Depth layers. The usage examples in the source comments (https://corsairui.vercel.app/llms/parallax.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/parallax.md

## Particles

Dots drifting slowly across a surface, laid out from a seed and moved with CSS alone.

Docs: https://corsairui.vercel.app/docs/particles

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/particles
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `count` | `number` | `30` | Up to 200. |
| `direction` | `"up" \| "down"` | `"up"` | Which way they drift. |
| `duration / size` | `number / [number, number]` | `14 / [1, 3]` | Average crossing time in seconds; smallest and largest dot in px. |
| `seed` | `number` | `1` | Another seed, another scatter; the same on server and client. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `particles`, `particles-dot`

State attributes:

- `data-direction` (computed)

### Accessibility

- Decorative and hidden from screen readers. Colour it with a text-* class.
- Pauses off screen; not shown at all with prefers-reduced-motion.

### Examples

On the docs page: Up and down. The usage examples in the source comments (https://corsairui.vercel.app/llms/particles.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/particles.md

## Preloader

A full-screen cover that counts to 100 on first load, waits for your content to be ready, then fades out.

Docs: https://corsairui.vercel.app/docs/preloader

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/preloader
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `duration` | `number` | `1500` | Minimum time on screen, in ms. |
| `ready` | `boolean` | `true` | While false, progress creeps toward 90% and waits. |
| `onComplete` | `() => void` |  | After the exit. |
| `children` | `ReactNode \| (progress) => ReactNode` |  | What shows, with the progress from 0 to 100 if you want it. |
| `label / exitDuration` | `string / number` | `"Loading" / 500` | Accessible name; fade-out time in ms. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The root is a fixed, full-screen grid that centres its children. Pass any layout as children (or a function of the progress), and change the root with className (place-items-end, p-8…).

data-slot: `preloader`, `preloader-bar`, `preloader-bar-fill`

State attributes:

- `data-state` (computed)

### Accessibility

- A progressbar with its value. Keep the page inert until onComplete.
- Without JavaScript it never shows; with prefers-reduced-motion it finishes as soon as ready.

### Examples

On the docs page: Counter and bar, Waiting until ready. The usage examples in the source comments (https://corsairui.vercel.app/llms/preloader.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/preloader.md

## Reveal

Fades and moves content into place when it scrolls into view, alone or staggered in a group. Visible without JavaScript.

Docs: https://corsairui.vercel.app/docs/reveal

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/reveal
```

Also adds: `utils`, `use-in-view`.
npm: `@radix-ui/react-slot`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `direction` | `"up" \| "down" \| "left" \| "right" \| "none"` | `"up"` | Where the content comes from. |
| `distance` | `number` | `16` | How far it travels, in px. |
| `duration / delay` | `number` | `600 / 0` | In ms. |
| `once` | `boolean` | `true` | With false it hides again when scrolled out. |
| `amount` | `number` | `0.2` | Visible fraction that starts it. |
| `RevealGroup stagger` | `number` | `80` | Delay between children, in ms. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `reveal`, `reveal-group`

State attributes:

- `data-state`: `hidden`, `visible`

### Accessibility

- Server-rendered content stays visible until the browser confirms it is off screen, so it works without JavaScript.
- With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Directions, Staggered group. The usage examples in the source comments (https://corsairui.vercel.app/llms/reveal.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/reveal.md

## Scroll Background

A background that blends from one colour to the next as you scroll, with CSS scroll-driven animations and no JavaScript.

Docs: https://corsairui.vercel.app/docs/scroll-background

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/scroll-background
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors` | `string[]` | `background, muted, a primary tint` | Any CSS colours, in order. The first shows before scrolling and where scroll timelines are missing. |
| `timeline` | `"view" \| "root" \| "nearest"` | `"view"` | The element crossing the window, the page, or the nearest scroll box. |
| `range` | `"cover" \| "contain"` | `"cover"` | View timeline: the whole crossing, or only while it fills the window. |
| `stops` | `number[]` |  | Where each colour is reached, in % of the timeline. Without it they are spread evenly. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The root takes the first colour as its background. Each further colour is a scroll-background-layer under the content, inside the root's own stacking context (isolate).
- Clip a wrapper with overflow-clip, not overflow-hidden: overflow-hidden makes a scroll box that never scrolls, and the colours stay put.

data-slot: `scroll-background`, `scroll-background-layer`, `scroll-background-layers`

State attributes:

- `data-timeline` (computed)

### Accessibility

- The colour layers are decorative and hidden from screen readers.
- Every colour has to contrast with the text on it; the blend between two passing colours stays between them.
- Pure CSS, and nothing moves, so it keeps following the scroll with reduced motion.

### Examples

On the docs page: Inside a scroll box, As it crosses the window, Uneven stops. The usage examples in the source comments (https://corsairui.vercel.app/llms/scroll-background.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/scroll-background.md

## Scroll Progress

A reading progress bar that fills as the page scrolls, with CSS scroll-driven animations and no JavaScript.

Docs: https://corsairui.vercel.app/docs/scroll-progress

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/scroll-progress
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `position` | `"top" \| "bottom"` | `"top"` | Edge of the window. |
| `timeline` | `"root" \| "nearest"` | `"root"` | The page, or the nearest scroll box (make it sticky there). |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `scroll-progress`

State attributes:

- `data-position` (computed)

### Accessibility

- Decorative and hidden from screen readers.
- Pure CSS; browsers without scroll-driven animations show no bar.

### Examples

On the docs page: Inside a scroll box, For the page. The usage examples in the source comments (https://corsairui.vercel.app/llms/scroll-progress.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/scroll-progress.md

## Scroll Scene

A scrollytelling scene: a stage pinned beside steps, crossfading to the slide of the step on screen, with a 0 to 1 scroll progress variable for scrubbing.

Docs: https://corsairui.vercel.app/docs/scroll-scene

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/scroll-scene
```

Also adds: `utils`, `use-scroll-spy`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `step / defaultStep / onStepChange` | `number / number / (step: number) => void` | `— / 0 / —` | The active step: the one crossing the middle of the viewport, or controlled. |
| `pinFrom` | `"sm" \| "md" \| "lg" \| "xl" \| false` | `"md"` | From this breakpoint the stage is pinned; below it each step shows its own slide. |
| `stageSide / stickyTop / stageHeight` | `"start" \| "end" / string / string` | `"start" / "0px" / "100svh"` | Where the pinned stage sits, sticks and how tall it is. |
| `distance / duration / easing` | `number / number / string` | `24 / 320 / "cubic-bezier(0.16, 1, 0.3, 1)"` | The slide swap: rise in px, length in ms and timing. |
| `progressRange` | `string` | `"contain 0% contain 100%"` | The animation-range --scroll-scene-progress runs 0 to 1 over. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Put ScrollSceneStage directly in ScrollScene and its slides directly in the stage: the small-screen copies are found there.
- Read the progress as var(--scroll-scene-progress, 1) in transforms or opacity to scrub with the scroll.
- Steps are 70svh tall when pinned; change that with the pinFrom prefix (md:min-h-[50svh]).

data-slot: `scroll-scene`, `scroll-scene-inline-slide`, `scroll-scene-slide`, `scroll-scene-stage`, `scroll-scene-step`, `scroll-scene-steps`

State attributes:

- `data-active-step` (computed)
- `data-pin-from`: `always`
- `data-stage-side` (computed)
- `data-state`: `active`, `inactive`
- `data-step` (computed)

### Accessibility

- The steps carry the story and read in order; inactive slides are inert and hidden from screen readers.
- Below the breakpoint the stage is display: none, so only one copy of each slide is in the accessibility tree.
- With prefers-reduced-motion slides swap with a short fade and the progress stays at 1.

### Examples

On the docs page: Pinned stage, Scrubbing with the progress. The usage examples in the source comments (https://corsairui.vercel.app/llms/scroll-scene.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/scroll-scene.md

## Smooth Scroll

Smooth wheel and trackpad scrolling for the page, off with reduced motion, with a scrollTo hook that works either way.

Docs: https://corsairui.vercel.app/docs/smooth-scroll

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/smooth-scroll
```

Also adds: `use-media-query`.
npm: `lenis`.

### API

Built on Lenis (https://lenis.darkroom.engineering); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `LenisOptions` | `{ autoRaf: true, anchors: true }` | SmoothScroll: read when it starts; change the key to apply new ones. |
| `useSmoothScroll()` | `{ lenis, scrollTo(target, { offset, immediate }) }` |  | Works with or without the provider, natively when Lenis is off. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- useSmoothScroll() returns { lenis, scrollTo } anywhere inside it: scrollTo(target, { offset }) for buttons and anchors, lenis for its events, stop() and start().

### Accessibility

- Off with prefers-reduced-motion: native scrolling, and scrollTo jumps.
- Put data-lenis-prevent on nested scroll areas, like long popovers.

### Examples

On the docs page: scrollTo, with or without it. The usage examples in the source comments (https://corsairui.vercel.app/llms/smooth-scroll.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/smooth-scroll.md

## Stamp

A rubber stamp that slams onto the page: it starts large and transparent and lands rotated with a small overshoot.

Docs: https://corsairui.vercel.app/docs/stamp

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/stamp
```

Also adds: `utils`, `use-in-view`, `use-entrance`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `rotate / from` | `number` | `-8 / 1.6` | Where it lands in degrees, and the scale it starts from. |
| `trigger / play` | `"load" \| "in-view" / boolean` | `"in-view"` | As in blur-text. |
| `delay / duration` | `number` | `0 / 320` | In ms. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- It is an inline-block with nothing but the rotation: give it the stamp look with classes (border-2, uppercase, tracking-widest, a colour).

data-slot: `stamp`

State attributes:

- `data-state` (computed)

### Accessibility

- The content stays readable to screen readers. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Sold out, Angles and weight. The usage examples in the source comments (https://corsairui.vercel.app/llms/stamp.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/stamp.md

## Tilt Scroll

Content that starts tilted back in perspective and straightens as you scroll, with CSS scroll-driven animations and no JavaScript.

Docs: https://corsairui.vercel.app/docs/tilt-scroll

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/tilt-scroll
```

Also adds: `utils`.
npm: `@radix-ui/react-slot`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `angle / scale / perspective` | `number` | `20 / 0.9 / 1200` | The starting lean in degrees, size and depth in px. |
| `timeline` | `"view" \| "root"` | `"view"` | The element crossing the viewport, or the page scroll from the top (for a hero in view at load). |
| `range` | `string` | `"entry 0% cover 40%" / "0px 60vh"` | The animation-range the straightening spans. |
| `flatBelow` | `"sm" \| "md" \| "lg" \| false` | `false` | Stay flat below this breakpoint, by CSS alone. |
| `asChild` | `boolean` | `false` | Tilt the child itself. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `tilt-scroll`

State attributes:

- `data-timeline` (computed)

### Accessibility

- Pure CSS; browsers without scroll-driven animations, and prefers-reduced-motion, show it flat.

### Examples

On the docs page: A dashboard that straightens, Steeper, with a shorter range. The usage examples in the source comments (https://corsairui.vercel.app/llms/tilt-scroll.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/tilt-scroll.md

## Wave Rating

The rating, animated: sweeping a finger, pen or mouse across the stars lifts them in a wave that crests under the pointer, a tip shows the score, and letting go pops the chosen star. Same props and radio group as rating.

Docs: https://corsairui.vercel.app/docs/wave-rating

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/wave-rating
```

Also adds: `utils`, `rating`, `use-media-query`.
npm: `@radix-ui/react-radio-group`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

Built on Radix Radio Group (https://www.radix-ui.com/primitives/docs/components/radio-group); its props pass through.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `Every Rating prop` | `RatingProps` |  | value, defaultValue, onValueChange, max, size, readOnly, disabled, name, getValueLabel and starLabel work as on Rating. |
| `showTip` | `boolean` | `true` | A label over the star under the pointer while it sweeps or hovers. |
| `tipLabel` | `(stars, max) => string` | `starLabel` | Text of the tip: words like "Smooth sailing" instead of "4 stars". |
| `haptics` | `boolean` | `true` | A short vibration per star under a finger, where the browser supports it (Chrome on Android). |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Each star rises inside data-slot="wave-rating-lift", which carries --wave-rating-lift from 0 to 1 as it does: use it for a glow or a colour of your own.
- The root has data-previewing while a pointer sweeps or hovers, and every radio up to the pointer has data-previewed.
- The tip is data-slot="wave-rating-tip", in bg-foreground and text-background.

data-slot: `wave-rating`, `wave-rating-item`, `wave-rating-lift`, `wave-rating-tip`

State attributes:

- `data-previewed` (computed)
- `data-previewing` (computed)
- `data-size` (computed)
- `data-value` (computed)

### Accessibility

- The same radio group as Rating: arrow keys pick a score, each star has a name, and the score submits with a form.
- The tip is decorative and hidden from screen readers; the radios already say the score.
- A sweep commits where the finger lets go. A vertical swipe still scrolls the page, and a gesture the browser takes over changes nothing.
- With prefers-reduced-motion the stars light up without rising, popping or travelling.

### Examples

On the docs page: Sweep to rate, Same props as Rating. The usage examples in the source comments (https://corsairui.vercel.app/llms/wave-rating.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/wave-rating.md

## Wipe Transition

Switches content behind a band that sweeps across it, swapping while covered, with a crossfade for phones and reduced motion.

Docs: https://corsairui.vercel.app/docs/wipe-transition

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/wipe-transition
```

Also adds: `utils`, `use-media-query`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `transitionKey` | `string \| number` |  | What the children show; a change runs the transition. |
| `cover / coverClassName` | `ReactNode / string` |  | What sweeps across: a bg-primary band by default. |
| `direction / duration / easing` | `"left" \| "right" \| "up" \| "down" / number / string` | `"right" / 600 / ease-in-out` | The way it travels and the total length, half to cover and half to reveal. |
| `mode` | `"wipe" \| "fade"` | `"wipe"` | Sweep a band, or crossfade (for phones). |
| `reducedDuration` | `number` | `150` | The crossfade used with reduced motion, in ms. |
| `fixed` | `boolean` | `false` | Cover the viewport, for a whole page taller than the screen. |
| `onCovered / onComplete` | `() => void` |  | Once the new content is in place under the cover (scroll to the top here), and once it has left. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- data-state is idle, covering or revealing, and data-mode says whether it wiped or faded.
- With fixed, an ancestor with a transform or filter traps the cover inside it.

data-slot: `wipe-transition`, `wipe-transition-content`, `wipe-transition-cover`

State attributes:

- `data-direction` (computed)
- `data-mode`: `fade`
- `data-state`: `idle`

### Accessibility

- The root is aria-busy while it runs and the content ignores the pointer; the cover is hidden from screen readers.
- If focus was inside the old content, it moves to the content wrapper instead of dropping to the page.
- With prefers-reduced-motion it always crossfades.

### Examples

On the docs page: Sweep between two sides, Fade, for phones. The usage examples in the source comments (https://corsairui.vercel.app/llms/wipe-transition.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/wipe-transition.md

## Animated Border

A border with a light travelling around it, paused off screen.

Docs: https://corsairui.vercel.app/docs/animated-border

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/animated-border
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `duration` | `number` | `6` | Seconds for one lap. |
| `color` | `string` | `var(--primary)` | Any CSS colour. |
| `width` | `number` | `1` | Border width, in px. |
| `contentClassName` | `string` |  | Classes for the surface inside. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `animated-border`, `animated-border-content`, `animated-border-light`

### Accessibility

- The light is aria-hidden and pauses off screen.
- With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Default, Colour, width and speed. The usage examples in the source comments (https://corsairui.vercel.app/llms/animated-border.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/animated-border.md

## Browser Frame

A browser window drawn in CSS around a page or a screenshot, with window dots, an address field and optional actions.

Docs: https://corsairui.vercel.app/docs/browser-frame

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/browser-frame
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `url` | `string` |  | The address in the bar, as text (not a link). |
| `announceUrl` | `boolean` | `false` | Let screen readers read the address. |
| `actions` | `ReactNode` |  | Content at the right end of the bar. |
| `aspect` | `number \| string` |  | Reserve the window's box; without it the content sets the height. |
| `barClassName / viewportClassName` | `string` |  | Classes for the bar and the viewport. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `browser-frame`, `browser-frame-actions`, `browser-frame-address`, `browser-frame-bar`, `browser-frame-dot`, `browser-frame-dots`, `browser-frame-viewport`

### Accessibility

- The window dots are always hidden from screen readers, and the address too unless announceUrl.
- With actions the bar stays in the accessibility tree, so they can be reached.

### Examples

On the docs page: Around a dashboard, With actions and a ratio. The usage examples in the source comments (https://corsairui.vercel.app/llms/browser-frame.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/browser-frame.md

## Flip Card

A card with two faces that turns over in 3D on hover, from a trigger, or from a prop.

Docs: https://corsairui.vercel.app/docs/flip-card

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/flip-card
```

Also adds: `utils`, `use-media-query`.
npm: `@radix-ui/react-slot`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `flipped / defaultFlipped / onFlippedChange` | `boolean / boolean / (flipped: boolean) => void` |  | Whether the back is showing. |
| `trigger` | `"hover" \| "click" \| "manual"` | `"hover"` | Hover and focus (a tap on touch screens), a FlipCardTrigger, or the prop alone. |
| `axis / duration / perspective / easing` | `"x" \| "y" / number / number / string` | `"y" / 600 / 1000 / ease-out` | The turn. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Both faces share one grid cell, so the card is as big as the larger face. Style each face with its className; asChild makes a Card the face.

data-slot: `flip-card`, `flip-card-trigger`

State attributes:

- `data-axis` (computed)
- `data-side` (computed)
- `data-state`: `flipped`, `unflipped`
- `data-trigger` (computed)

### Accessibility

- Hover mode keeps both faces readable; tabbing into the back turns the card. Put links on the back, not the front.
- In click and manual modes the face turned away is inert and hidden, and focus moves to the face that turned up.
- FlipCardTrigger has aria-pressed. With prefers-reduced-motion the faces crossfade.

### Examples

On the docs page: Stickers on hover, With triggers, A week that turns over. The usage examples in the source comments (https://corsairui.vercel.app/llms/flip-card.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/flip-card.md

## Hover Reveal

A second layer seen through a soft circle that follows the pointer.

Docs: https://corsairui.vercel.app/docs/hover-reveal

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/hover-reveal
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `reveal` | `ReactNode` |  | The layer seen through the circle. |
| `radius` | `number` | `120` | In px. |
| `softness` | `number` | `0.4` | 0 for a hard edge, 1 fades from the centre. |
| `revealClassName` | `string` |  | Classes for the revealed layer. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `hover-reveal`, `hover-reveal-layer`

State attributes:

- `data-state`: `active`, `idle`

### Accessibility

- The revealed layer is hidden from screen readers and inert, so children carry the meaning.
- Touch screens see the base only. The circle opens at once with prefers-reduced-motion.

### Examples

On the docs page: Colour under the outline, Hard edge. The usage examples in the source comments (https://corsairui.vercel.app/llms/hover-reveal.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/hover-reveal.md

## Path Beam

A beam of light travelling along the paths of a flow diagram, one after another, on view, with the scroll or from your own progress.

Docs: https://corsairui.vercel.app/docs/path-beam

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/path-beam
```

Also adds: `utils`, `use-in-view`, `use-media-query`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `paths / viewBox` | `string[] / string` |  | SVG paths, travelled in order. |
| `trigger` | `"in-view" \| "scroll" \| "manual"` | `"in-view"` | Run when it shows up, follow the scroll, or follow progress. |
| `duration / delay / stagger` | `number` | `1200 / 0 / 0` | In ms: per path, before the first, and between paths. |
| `beamLength / strokeWidth / glow` | `number / number / boolean` | `0.2 / 2 / true` | The beam as a fraction of each path, its width, and a fainter wider copy under it. |
| `loop` | `boolean` | `false` | Run again and again; pauses off screen. |
| `progress` | `number` | `0` | Manual: where the beam is, 0 to 1 over every path. |
| `onPathComplete / onComplete` | `(index: number) => void / () => void` |  | Light the nodes as the beam arrives. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Each path's group (path-beam-path) has data-state idle, running or done; the svg has the overall one. In manual mode --path-beam-progress is set too.
- trackClassName and beamClassName take stroke colours (stroke-border, stroke-primary by default).

data-slot: `path-beam`, `path-beam-beam`, `path-beam-glow`, `path-beam-path`, `path-beam-track`

State attributes:

- `data-index` (computed)
- `data-state` (computed)
- `data-trigger` (computed)

### Accessibility

- The svg is decorative: label the diagram it sits on (role="img" with an aria-label, or text in the nodes).
- With prefers-reduced-motion the track shows and the beam does not run.

### Examples

On the docs page: A payment flow, Driven by your own progress, On a loop. The usage examples in the source comments (https://corsairui.vercel.app/llms/path-beam.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/path-beam.md

## Phone Frame

A generic phone drawn in CSS around a screenshot or any content, with an island, a notch or no cut-out, that scales with its width.

Docs: https://corsairui.vercel.app/docs/phone-frame

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/phone-frame
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `notch` | `"island" \| "notch" \| "none"` | `"island"` | The cut-out at the top of the screen. |
| `aspect` | `number \| string` | `"9 / 19.5"` | Width / height of the frame, reserved at first paint. |
| `bezel / radius` | `string` | `"3.5cqw" / "14cqw"` | Any CSS length; cqw units follow the frame's width. |
| `buttons` | `boolean` | `true` | Decorative side buttons. They sit just outside the frame's box. |
| `screenClassName` | `string` |  | Classes for the screen. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Set the width with a class (w-64, w-72): everything inside scales with it. For a shadow that follows the rounded body, use drop-shadow-*, not shadow-*.
- A server component with no motion, so it sits inside parallax, magnetic or tilt wrappers.

data-slot: `phone-frame`, `phone-frame-body`, `phone-frame-button`, `phone-frame-notch`, `phone-frame-screen`

State attributes:

- `data-notch` (computed)
- `data-side` (computed)

### Accessibility

- The body, cut-out and buttons are decoration and hidden from screen readers; the children keep their own semantics, so give images an alt.

### Examples

On the docs page: Cut-outs, Sizes and ratios. The usage examples in the source comments (https://corsairui.vercel.app/llms/phone-frame.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/phone-frame.md

## Signature

Draws SVG strokes as if by hand, with optional ink filling in behind the pen, on view or with the scroll.

Docs: https://corsairui.vercel.app/docs/signature

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/signature
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `paths` | `string[]` |  | SVG path data, drawn in order. |
| `trigger` | `"in-view" \| "scroll" \| "manual"` | `"in-view"` | Draw once when visible, with the scroll position, or follow progress. |
| `progress` | `number` | `0` | Manual: how much is drawn, 0 to 1. Strokes share it by length. |
| `duration / delay` | `number` | `2400 / 0` | In-view timing, in ms. |
| `scrollRange` | `string` | `"entry 25% cover 50%"` | Scroll: the CSS animation-range it spans. |
| `strokeWidth` | `number` | `2` | Uses the current text colour. With ink, 0 draws the fill alone. |
| `ink / inkWidth` | `boolean / number` | `false / 14` | Fill closed shapes behind the pen; how wide the ink spreads. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `signature`

State attributes:

- `data-state`: `blank`, `drawing`, `drawn`, `in-view`, `manual`
- `data-trigger` (computed)

### Accessibility

- With an aria-label it is role="img"; without one it is hidden from screen readers.
- With prefers-reduced-motion it holds still and shows the finished state.
- In manual mode the parent is in charge: pass 1 when it should look finished.

### Examples

On the docs page: Wordmark, Ink, Route on the scroll, Driven by your own progress, Fill only. The usage examples in the source comments (https://corsairui.vercel.app/llms/signature.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/signature.md

## Spotlight

A soft light that follows the pointer across a surface and along its edge.

Docs: https://corsairui.vercel.app/docs/spotlight

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/spotlight
```

Also adds: `utils`.
npm: `@radix-ui/react-slot`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `number` | `320` | Radius of the light, in px. |
| `color` | `string` | `var(--foreground)` | Any CSS colour. |
| `ring` | `boolean` | `true` | Also light the nearest edge. |
| `asChild` | `boolean` | `false` | Light a Card or any single child. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `spotlight`, `spotlight-light`, `spotlight-ring`

### Accessibility

- Decorative layers are aria-hidden and ignore the pointer.
- Keyboard focus inside lights the surface too, so the effect is not mouse-only.

### Examples

On the docs page: On a Card, Colours. The usage examples in the source comments (https://corsairui.vercel.app/llms/spotlight.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/spotlight.md

## Aurora

Slow, soft light drifting behind its content, from three radial gradients.

Docs: https://corsairui.vercel.app/docs/aurora

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/aurora
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors` | `[string, string, string]` |  | One CSS colour per light. |
| `duration` | `number` | `20` | Seconds for one drift. |
| `intensity` | `number` | `0.3` | Opacity of the lights. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `aurora`, `aurora-lights`

### Accessibility

- The lights are aria-hidden; check the contrast of text on top of them.
- They pause off screen. With prefers-reduced-motion it holds still and shows the finished state.

### Examples

On the docs page: Theme colours, Custom colours. The usage examples in the source comments (https://corsairui.vercel.app/llms/aurora.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/aurora.md

## Grain

Film grain over a surface, from a tile of SVG noise, with an optional stepped jitter.

Docs: https://corsairui.vercel.app/docs/grain

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/grain
```

Also adds: `utils`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `opacity` | `number` | `0.12` | Strength, 0 to 1. |
| `frequency / size` | `number` | `0.8 / 180` | Fineness of the noise; tile size in px. |
| `blend` | `CSS mix-blend-mode` | `"overlay"` | How it mixes with what is under it. |
| `fixed / animated` | `boolean` | `false` | Cover the viewport; jitter like film. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `grain`, `grain-noise`

State attributes:

- `data-animated` (computed)

### Accessibility

- Decorative, hidden from screen readers, lets clicks through. Works as a server component.
- The jitter holds still with prefers-reduced-motion.

### Examples

On the docs page: Over a gradient, Animated. The usage examples in the source comments (https://corsairui.vercel.app/llms/grain.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/grain.md

## Light Rays

Soft beams of light falling from above, from a small WebGL2 shader.

Docs: https://corsairui.vercel.app/docs/light-rays

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/light-rays
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `colors` | `string \| [string, string]` | `"#ffffff"` | Theme variables work. |
| `intensity` | `number` | `0.6` | Brightness, from 0 to 1. |
| `density` | `number` | `12` | Beams in the fan. |
| `reach` | `number` | `0.8` | How far down, as a share of the diagonal. |
| `origin / spread` | `number` | `0.5 / 60` | Source across the top; fan width in degrees. |
| `speed / resolution` | `number` | `1 / 0.5` | Drift pace; render scale. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `light-rays`, `light-rays-fallback`

### Accessibility

- Decorative: aria-hidden and pointer-events none. Check the contrast of text on top.
- It pauses off screen and in background tabs, and draws one still frame with prefers-reduced-motion.
- Without WebGL2 a CSS glow stands in.

### Examples

On the docs page: Two colours, Narrow and dense, from the left. The usage examples in the source comments (https://corsairui.vercel.app/llms/light-rays.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/light-rays.md

## Topography

Contour lines like a topographic map over slowly shifting terrain, from one WebGL2 shader.

Docs: https://corsairui.vercel.app/docs/topography

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/topography
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `string` | `var(--muted-foreground)` | Line colour; theme variables work. |
| `density / scale` | `number` | `0.5 / 0.5` | How many lines; how broad the hills, 0 to 1. |
| `thickness / indexEvery` | `number` | `1 / 5` | Line width in px; every nth line stronger (0 for none). |
| `speed / opacity` | `number` | `0.3 / 0.6` | Pace of the terrain; strength of the lines. |
| `pointer` | `boolean` | `false` | The terrain rises under a mouse or pen. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `topography`, `topography-fallback`

### Accessibility

- Decorative and hidden from screen readers. Check the contrast of text on top.
- Pauses off screen, draws one still frame with prefers-reduced-motion, and falls back to CSS rings without WebGL2.

### Examples

On the docs page: Default, Theme colour and pointer. The usage examples in the source comments (https://corsairui.vercel.app/llms/topography.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/topography.md

## Warp Gradient

A liquid field of colour carried along by layered noise, from one WebGL2 shader.

Docs: https://corsairui.vercel.app/docs/warp-gradient

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/warp-gradient
```

Also adds: `utils`, `use-in-view`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `preset` | `"tide" \| "brass" \| "abyss" \| "dusk" \| "fog"` | `"tide"` | A starting point; any setting passed alongside overrides it. |
| `colors` | `string[]` |  | Two to five colours. Theme variables work. |
| `pattern` | `"bands" \| "cells" \| "split"` |  | What the colours are laid on: soft bands, a grid of cells, or one divide. |
| `density / softness` | `number` |  | From 0 to 1: how many bands or cells fit (for split, how sharp the divide is); how much the colours melt together. |
| `warp / turbulence` | `number` |  | From 0 to 1: how far the flow pushes the pattern; fine detail in the flow. |
| `scale / rotation / speed` | `number` |  | Size of the flow from 0 to 1, turn in degrees, pace. |
| `grain` | `number` | `0` | Film grain, from 0 to 1. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `warp-gradient`, `warp-gradient-fallback`, `warp-gradient-grain`

### Accessibility

- Decorative: aria-hidden. Check the contrast of text on top.
- It pauses off screen and in background tabs, and draws one still frame with prefers-reduced-motion.
- Without WebGL2 a CSS gradient of the same colours stands in.

### Examples

On the docs page: Presets, Your own colours, with grain. The usage examples in the source comments (https://corsairui.vercel.app/llms/warp-gradient.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/warp-gradient.md

## Call to Action

A closing band with a title, a description and one or two actions.

Docs: https://corsairui.vercel.app/docs/cta

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/cta
```

Also adds: `utils`, `button`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `ReactNode` | `"Start building with Acme today"` | In an h2; null hides it. |
| `description` | `ReactNode` | `example copy` | A sentence under the title; null hides it. |
| `actions` | `ReactNode` | `"Start for free" and "Talk to sales"` | Buttons or links; null hides them. |
| `variant` | `"muted" \| "primary" \| "outline"` | `"muted"` | The band's surface. Exposed as data-variant. |
| `align` | `"center" \| "start"` | `"center"` | Centred, or text on the start side and actions on the other from md. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `cta`, `cta-actions`, `cta-band`, `cta-container`, `cta-content`, `cta-description`, `cta-title`

State attributes:

- `data-align` (computed)
- `data-variant` (computed)

### Accessibility

- The title is an h2 and the default actions are links.
- On the primary band the default buttons switch to secondary and ghost to keep their contrast; choose your own actions with the same care.

### Examples

On the docs page: Default, Primary, split. The usage examples in the source comments (https://corsairui.vercel.app/llms/cta.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/cta.md

## Contact Form

A contact section with a header, contact details and a validated message form that reports pending, success and error states.

Docs: https://corsairui.vercel.app/docs/contact-form

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/contact-form
```

Also adds: `utils`, `button`, `card`, `label`, `field`, `input`, `textarea`.
npm: `@radix-ui/react-label`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `eyebrow` | `ReactNode` | `"Contact"` | Small line above the title; null hides it. |
| `title / description` | `ReactNode` | `"Get in touch" / example copy` | The header; the title is an h2. |
| `details` | `ReactNode` | `an email link and reply time` | Under the description; null hides it. |
| `labels` | `Partial` |  | Overrides for name, email, message, submit, pending, success and error; the rest keep their English defaults. |
| `onSubmit` | `(values: ContactFormValues) => void \| Promise` |  | Gets { name, email, message } instead of a native submit. Resolving resets the form and shows success; throwing keeps the values and shows the error. |
| `formProps` | `ComponentProps<"form">` |  | Props for the form, such as action and method when there is no onSubmit. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `contact-form`, `contact-form-card`, `contact-form-container`, `contact-form-description`, `contact-form-details`, `contact-form-error`, `contact-form-eyebrow`, `contact-form-footer`, `contact-form-form`, `contact-form-header`, `contact-form-submit`, `contact-form-success`, `contact-form-title`

State attributes:

- `data-status` (computed)

### Accessibility

- The title is an h2. Every field has a visible label and is required, and name and email have autoComplete, so the browser checks them before onSubmit runs.
- Success goes to a role="status" region and the error to a role="alert" region; both stay mounted so they are announced.
- While onSubmit runs, the button is disabled, marked aria-busy and shows the pending label.

### Examples

On the docs page: Default, Failed request. The usage examples in the source comments (https://corsairui.vercel.app/llms/contact-form.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/contact-form.md

## Dashboard

An application page with a collapsible sidebar, a header, stat cards, a line chart and a recent-activity table.

Docs: https://corsairui.vercel.app/docs/dashboard

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/dashboard
```

Also adds: `utils`, `avatar`, `badge`, `button`, `card`, `table`, `pagination`, `checkbox`, `input`, `data-table`, `line-chart`, `separator`, `sheet`, `tooltip`, `use-media-query`, `sidebar`, `stat`.
npm: `@radix-ui/react-avatar`, `@radix-ui/react-checkbox`, `@radix-ui/react-dialog`, `@radix-ui/react-separator`, `@radix-ui/react-slot`, `@radix-ui/react-tooltip`, `@tanstack/react-table`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `brand / nav / user` | `ReactNode / DashboardNavGroup[] / DashboardUser \| null` | `Acme examples` | The sidebar: a logo and name, groups of links ({ label, href, icon?, badge?, active? }), and the account at the bottom (null hides it). |
| `title / actions` | `ReactNode` | `"Dashboard" / a "New report" button` | The header; the title is an h1. null hides the actions. |
| `stats` | `DashboardStat[]` | `four examples` | Stat cards, each with label and value, and optionally id, caption and trend. An empty array hides the row. |
| `trendLabels` | `Partial>` | `"Increase" / "Decrease" / "No change"` | Read before a caption with a trend, since the arrow is only a picture. |
| `chart` | `DashboardChart \| null` | `monthly revenue` | A title, description, labels and any LineChart props; null hides the card. |
| `table` | `DashboardTable \| null` | `recent activity` | A title, description and any DataTable props; null hides the card. |
| `providerProps / sidebarProps / insetProps` | `SidebarProviderProps / SidebarProps / main props` |  | Passed to SidebarProvider (defaultOpen, open, shortcut, width), Sidebar (collapsible, side) and the main element. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The root is the SidebarProvider wrapper, with data-slot="dashboard" and data-state (expanded or collapsed).
- To contain it in a box, give it a height and clip it: className="h-[640px] min-h-0 overflow-hidden", sidebarProps={{ className: "h-full" }} and insetProps={{ className: "overflow-auto" }}.
- The parts are dashboard-header, dashboard-title, dashboard-actions, dashboard-content, dashboard-stats, dashboard-chart, dashboard-table and dashboard-user.

data-slot: `dashboard`, `dashboard-actions`, `dashboard-brand`, `dashboard-chart`, `dashboard-chart-title`, `dashboard-content`, `dashboard-header`, `dashboard-nav-group`, `dashboard-stats`, `dashboard-table`, `dashboard-table-title`, `dashboard-title`, `dashboard-user`, `dashboard-user-email`, `dashboard-user-name`

### Accessibility

- The sidebar is a named complementary landmark and the page a main landmark with an h1; ⌘B or Ctrl+B toggles the sidebar unless providerProps.shortcut is null.
- The active link is announced with aria-current="page"; collapsed to icons, each link keeps its name and shows a tooltip.
- Stats are a description list, the chart is a slider the arrow keys move along and the table has sortable headers and a filter.
- The sidebar width change and the chart cursor stop with prefers-reduced-motion.

### Examples

On the docs page: Default, Collapsed, without the table. The usage examples in the source comments (https://corsairui.vercel.app/llms/dashboard.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/dashboard.md

## FAQ

A frequently asked questions section with a header and the answers in an accordion, split or stacked.

Docs: https://corsairui.vercel.app/docs/faq

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/faq
```

Also adds: `utils`, `accordion`.
npm: `@radix-ui/react-accordion`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `eyebrow` | `ReactNode` | `"FAQ"` | Small line above the title; null hides it. |
| `title / description` | `ReactNode` | `"Frequently asked questions" / example copy` | The header; the title is an h2. null hides either. |
| `items` | `FaqItem[]` | `six examples` | Each has question and answer, and optionally id (the key and accordion value; item- without one). |
| `layout` | `"stacked" \| "split"` | `"split"` | Header beside the questions on large screens, or centred above them. |
| `type` | `"single" \| "multiple"` | `"single"` | One answer open at a time (and it can close), or several. |
| `defaultValue` | `string \| string[]` |  | Items open on first render, by id or item-. |
| `footer` | `ReactNode` | `"Still have questions? Contact us"` | Under the header; null hides it. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `faq`, `faq-answer`, `faq-container`, `faq-description`, `faq-eyebrow`, `faq-footer`, `faq-header`, `faq-item`, `faq-list`, `faq-question`, `faq-title`

State attributes:

- `data-layout` (computed)

### Accessibility

- The title is an h2; each question is a button in an h3, announced as expanded or collapsed.
- Tab reaches the questions, Enter or Space toggles them, and Up, Down, Home and End move between them.
- Answers open without sliding with prefers-reduced-motion.

### Examples

On the docs page: Split, Stacked. The usage examples in the source comments (https://corsairui.vercel.app/llms/faq.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/faq.md

## Feature Grid

A grid of features with an icon, a title and a description under a centred section header.

Docs: https://corsairui.vercel.app/docs/feature-grid

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/feature-grid
```

Also adds: `utils`, `card`.
npm: `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `eyebrow` | `ReactNode` | `"Features"` | Small line above the title; null hides it. |
| `title / description` | `ReactNode` | `example copy` | The header; the title is an h2. null hides either. |
| `features` | `FeatureGridFeature[]` | `six examples` | Each has title, and optionally id (the key), icon, description and href (links the whole card). |
| `columns` | `2 \| 3 \| 4` | `3` | Columns on large screens. One on mobile and two from sm. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `feature-grid`, `feature-grid-container`, `feature-grid-description`, `feature-grid-eyebrow`, `feature-grid-header`, `feature-grid-icon`, `feature-grid-item`, `feature-grid-item-description`, `feature-grid-item-title`, `feature-grid-list`, `feature-grid-title`

State attributes:

- `data-columns` (computed)
- `data-linked` (computed)

### Accessibility

- The features are a list; each title is an h3 under the section's h2.
- Icons are hidden from screen readers, so the title has to say what the feature is.
- With href, the title's link covers the card and the card shows the focus ring; the link is named by the title.

### Examples

On the docs page: Default, Two columns with links. The usage examples in the source comments (https://corsairui.vercel.app/llms/feature-grid.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/feature-grid.md

## Hero

The opening section of a landing page with an eyebrow, a title, a description, actions and an optional media slot.

Docs: https://corsairui.vercel.app/docs/hero

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/hero
```

Also adds: `utils`, `badge`, `button`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `eyebrow` | `ReactNode` | `pill link to #changelog` | Small line above the title; null hides it. |
| `title` | `ReactNode` | `example copy` | The page title, in the h1. |
| `description` | `ReactNode` | `example copy` | A sentence or two under the title. |
| `actions` | `ReactNode` | `"Get started" and "Learn more"` | Buttons or links under the description; null hides them. |
| `media` | `ReactNode` |  | An image or illustration: beside the text with align="start", under it when centred. |
| `align` | `"center" \| "start"` | `"center"` | Centred text, or text on the start side. Exposed as data-align. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `hero`, `hero-actions`, `hero-container`, `hero-content`, `hero-description`, `hero-eyebrow`, `hero-media`, `hero-title`

State attributes:

- `data-align` (computed)

### Accessibility

- The title is the only h1 among the blocks, so use one Hero per page.
- Images in media need an alt text, or alt="" when they are decorative.
- The default eyebrow and actions are real links; the eyebrow's arrow is hidden.

### Examples

On the docs page: Default, With media. The usage examples in the source comments (https://corsairui.vercel.app/llms/hero.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/hero.md

## Login Form

A centred sign-in card with email and password fields, a remember-me checkbox, optional sign-in providers and pending, success and error states.

Docs: https://corsairui.vercel.app/docs/login-form

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/login-form
```

Also adds: `utils`, `button`, `card`, `checkbox`, `label`, `field`, `input`, `input-group`, `password-input`, `separator`.
npm: `@radix-ui/react-checkbox`, `@radix-ui/react-label`, `@radix-ui/react-separator`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title / description` | `ReactNode` | `"Sign in to your account" / example copy` | The card header; the title is an h1. |
| `forgotHref / signupHref` | `string \| null` | `"#forgot-password" / "#sign-up"` | Targets of the forgot password and sign-up links; null hides either. |
| `showRemember` | `boolean` | `true` | Shows the Remember me checkbox. |
| `providers` | `LoginFormProvider[]` |  | Outline buttons under an or continue with separator. Each has label, and optionally id, icon, href (a link) or onClick (a button). |
| `labels` | `Partial` |  | Overrides for any string; the rest keep their English defaults. |
| `onSubmit` | `(values: LoginFormValues) => void \| Promise` |  | Gets { email, password, remember } instead of a native submit. Resolving resets the form and shows success; throwing keeps the values and shows the error. |
| `formProps` | `ComponentProps<"form">` |  | Props for the form, such as action and method when there is no onSubmit. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The section and login-form-form have data-status ("idle", "pending", "success" or "error") while onSubmit runs and after it settles.
- The parts are login-form-card, login-form-forgot, login-form-remember, login-form-submit, login-form-providers and login-form-signup.

data-slot: `login-form`, `login-form-card`, `login-form-container`, `login-form-content`, `login-form-description`, `login-form-error`, `login-form-footer`, `login-form-forgot`, `login-form-form`, `login-form-header`, `login-form-provider`, `login-form-providers`, `login-form-remember`, `login-form-separator`, `login-form-signup`, `login-form-submit`, `login-form-success`, `login-form-title`

State attributes:

- `data-status` (computed)

### Accessibility

- The section is named by its h1. Both fields are required with autoComplete ("email" and "current-password"), so the browser checks them and password managers fill them.
- The password field has a button that reveals what was typed; aria-pressed tells its state.
- Success goes to a role="status" region and the error to a role="alert" region; both stay mounted so they are announced.
- Provider icons are hidden from screen readers, so the label has to name the provider.

### Examples

On the docs page: Default, With providers. The usage examples in the source comments (https://corsairui.vercel.app/llms/login-form.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/login-form.md

## Logo Cloud

Customer or partner logos in a grid or a scrolling marquee under a small title.

Docs: https://corsairui.vercel.app/docs/logo-cloud

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/logo-cloud
```

Also adds: `utils`, `use-in-view`, `use-media-query`, `marquee`.
npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `ReactNode` | `"Trusted by teams at"` | Small centred line above the logos, in an h2; null hides it. |
| `logos` | `LogoCloudLogo[]` | `six text wordmarks` | Each has name (its accessible name, and the wordmark without a logo), and optionally id, logo (an image or SVG) and href. |
| `variant` | `"grid" \| "marquee"` | `"grid"` | A static grid, or a row that scrolls in a loop. Exposed as data-variant. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `logo-cloud`, `logo-cloud-container`, `logo-cloud-image`, `logo-cloud-item`, `logo-cloud-link`, `logo-cloud-list`, `logo-cloud-title`, `logo-cloud-wordmark`

State attributes:

- `data-variant` (computed)

### Accessibility

- The title is an h2 and the logos are a list.
- Every logo is named by name: a custom logo is wrapped in role="img", or its link gets the name as aria-label.
- In the marquee the copies that make the loop are hidden and inert. It pauses on hover and while a link has focus, and holds still, scrollable by hand, with prefers-reduced-motion.

### Examples

On the docs page: Grid, Marquee. The usage examples in the source comments (https://corsairui.vercel.app/llms/logo-cloud.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/logo-cloud.md

## Newsletter

A compact sign-up band with an inline email form that reports pending, success and error states.

Docs: https://corsairui.vercel.app/docs/newsletter

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/newsletter
```

Also adds: `utils`, `button`, `input`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title / description` | `ReactNode` | `"Stay in the loop" / example copy` | The header; the title is an h2. |
| `note` | `ReactNode` | `"No spam. Unsubscribe at any time."` | Small print under the form; null hides it. |
| `placeholder` | `string` | `"you@example.com"` | Placeholder of the email field. |
| `labels` | `Partial` |  | Overrides for email (the hidden label), submit, pending, success and error; the rest keep their English defaults. |
| `onSubscribe` | `(email: string) => void \| Promise` |  | Gets the email instead of a native submit. Resolving clears the field and shows success; throwing keeps it and shows the error. |
| `formProps` | `ComponentProps<"form">` |  | Props for the form, such as action and method when there is no onSubscribe. |
| `variant` | `"plain" \| "card"` | `"card"` | In a bordered box, or on the page background. Exposed as data-variant. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `newsletter`, `newsletter-body`, `newsletter-container`, `newsletter-content`, `newsletter-description`, `newsletter-error`, `newsletter-form`, `newsletter-header`, `newsletter-note`, `newsletter-submit`, `newsletter-success`, `newsletter-title`

State attributes:

- `data-status` (computed)
- `data-variant` (computed)

### Accessibility

- The title is an h2. The email field has a visually hidden label, type="email", required and autoComplete="email"; the placeholder is only an example.
- Success goes to a role="status" region and the error to a role="alert" region; both stay mounted so they are announced.
- While onSubscribe runs, the button is disabled, marked aria-busy and shows the pending label.

### Examples

On the docs page: Default, Plain. The usage examples in the source comments (https://corsairui.vercel.app/llms/newsletter.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/newsletter.md

## Not Found

A 404 page section with a large decorative error code, a page heading, a description, an optional search slot and actions.

Docs: https://corsairui.vercel.app/docs/not-found

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/not-found
```

Also adds: `utils`, `button`.
npm: `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `code` | `ReactNode` | `"404"` | The large decorative code above the title; null hides it. |
| `title / description` | `ReactNode` | `"Page not found" / example copy` | The title is an h1. |
| `search` | `ReactNode` |  | Optional slot between the description and the actions, such as a search form. |
| `actions` | `ReactNode` | `"Go home" and "Contact support"` | Buttons or links; null hides them. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The code is not-found-code, large and in the primary colour; the other parts are not-found-title, not-found-description, not-found-search and not-found-actions.

data-slot: `not-found`, `not-found-actions`, `not-found-code`, `not-found-container`, `not-found-description`, `not-found-search`, `not-found-title`

### Accessibility

- The code is hidden with aria-hidden; the h1 is the real heading, so the page is announced by its title.
- The default actions are links styled as buttons, reachable with Tab.
- Nothing animates, and it is a server component with no client code.

### Examples

On the docs page: Default, With search. The usage examples in the source comments (https://corsairui.vercel.app/llms/not-found.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/not-found.md

## Pricing

A pricing section with plan cards, a featured plan and an optional monthly or yearly billing switch.

Docs: https://corsairui.vercel.app/docs/pricing

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/pricing
```

Also adds: `utils`, `badge`, `button`, `card`, `toggle`, `toggle-group`.
npm: `@radix-ui/react-slot`, `@radix-ui/react-toggle`, `@radix-ui/react-toggle-group`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `eyebrow` | `ReactNode` | `"Pricing"` | Small line above the title; null hides it. |
| `title / description` | `ReactNode` | `"Plans that grow with you" / example copy` | The header; the title is an h2. null hides either. |
| `plans` | `PricingPlan[]` | `three examples` | Each has name, price and features, and optionally id, description, period, action ({ label, href }), featured and badge. price and period take { monthly, yearly } to show the switch. |
| `billing / defaultBilling / onBillingChange` | `"monthly" \| "yearly" / "monthly" \| "yearly" / (billing) => void` | `"monthly"` | The period shown, controlled or not. Exposed as data-billing. |
| `monthlyLabel / yearlyLabel` | `ReactNode` | `"Monthly" / "Yearly"` | The switch's two options. |
| `yearlyNote` | `ReactNode` | `"2 months free"` | Shown next to the yearly label; null hides it. |
| `billingLabel` | `string` | `"Billing period"` | Accessible name of the switch. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `pricing`, `pricing-billing`, `pricing-billing-monthly`, `pricing-billing-yearly`, `pricing-container`, `pricing-content`, `pricing-description`, `pricing-eyebrow`, `pricing-feature`, `pricing-features`, `pricing-header`, `pricing-period`, `pricing-plan`, `pricing-plan-action`, `pricing-plan-badge`, `pricing-plan-description`, `pricing-plan-item`, `pricing-plan-name`, `pricing-plans`, `pricing-price`, `pricing-title`, `pricing-yearly-note`

State attributes:

- `data-billing` (computed)
- `data-featured` (computed)

### Accessibility

- The title is an h2 and plan names are h3. Plans and features are lists; the check icons are hidden.
- The switch is a toggle group named by billingLabel that always keeps one period selected: Tab reaches it, the arrow keys move between periods.
- The featured ring is only visual: give that plan a badge so it is said in words too.

### Examples

On the docs page: Default, Single price. The usage examples in the source comments (https://corsairui.vercel.app/llms/pricing.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/pricing.md

## Settings

An account settings page with tabs for a profile form, a password change and delete-account dialog, and notification switches.

Docs: https://corsairui.vercel.app/docs/settings

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/settings
```

Also adds: `utils`, `use-media-query`, `button`, `alert-dialog`, `avatar`, `card`, `label`, `field`, `input`, `input-group`, `password-input`, `switch`, `tabs`, `textarea`.
npm: `@radix-ui/react-alert-dialog`, `@radix-ui/react-avatar`, `@radix-ui/react-label`, `@radix-ui/react-slot`, `@radix-ui/react-switch`, `@radix-ui/react-tabs`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `labels` | `Partial` |  | Overrides for any string; the rest keep their English defaults. |
| `sections / tabsProps` | `SettingsSection[] / Tabs props` | `Profile, Account, Notifications` | The tabs ({ value, label, icon?, content? }); "profile", "account" and "notifications" render the built-in panels. tabsProps sets defaultValue, or value and onValueChange. |
| `profile / onSaveProfile` | `SettingsProfile / (values) => void \| Promise` | `an example profile` | The profile form's starting values (name, email, bio, avatar), and a handler that gets { name, email, bio } instead of a native submit. |
| `minPasswordLength / onChangePassword` | `number / (values) => void \| Promise` | `8` | Shortest new password, and a handler that gets { currentPassword, newPassword }; resolving clears the form. |
| `onDelete` | `() => void \| Promise` |  | Runs when the delete dialog is confirmed; the dialog stays open until it resolves. |
| `notifications / onNotificationsChange` | `SettingsNotification[] / (state) => void` | `four examples` | The switches ({ id, label, description?, defaultChecked? }), and every switch's state by id whenever one changes. |
| `profileFormProps / passwordFormProps` | `ComponentProps<"form">` |  | Props for each form, such as action and method for a native submit. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- settings-profile-form, settings-password-form and the settings-danger card have data-status ("idle", "pending", "success" or "error").
- The parts are settings-header, settings-profile, settings-avatar, settings-password, settings-danger, settings-delete, settings-notifications and settings-notification.

data-slot: `settings`, `settings-avatar`, `settings-container`, `settings-danger`, `settings-delete`, `settings-delete-confirm`, `settings-delete-dialog-error`, `settings-description`, `settings-header`, `settings-notification`, `settings-notification-list`, `settings-notifications`, `settings-password`, `settings-password-form`, `settings-password-submit`, `settings-profile`, `settings-profile-form`, `settings-profile-submit`, `settings-title`

State attributes:

- `data-status` (computed)

### Accessibility

- The tab list is one Tab stop: Up and Down move between tabs while it is a column (from 768px), Left and Right while it is a row, Home and End jump to the ends.
- Each form has its own role="status" and role="alert" regions, always mounted, and uses native validation and autoComplete.
- The delete dialog is an alert dialog that traps focus, starts on Cancel and closes with Escape.
- Each switch is named by its label and described by its description, and applies at once.

### Examples

On the docs page: Default, Notifications first. The usage examples in the source comments (https://corsairui.vercel.app/llms/settings.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/settings.md

## Signup Form

A centred sign-up card with name, email and password fields, a required terms checkbox, optional sign-up providers and pending, success and error states.

Docs: https://corsairui.vercel.app/docs/signup-form

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/signup-form
```

Also adds: `utils`, `button`, `card`, `checkbox`, `label`, `field`, `input`, `input-group`, `password-input`, `separator`.
npm: `@radix-ui/react-checkbox`, `@radix-ui/react-label`, `@radix-ui/react-separator`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title / description` | `ReactNode` | `"Create an account" / example copy` | The card header; the title is an h1. |
| `terms` | `ReactNode` | `links to Terms and Privacy Policy` | Label of the required terms checkbox; it can contain links. |
| `minLength` | `number` | `8` | Shortest password accepted. Change labels.passwordHint to match. |
| `signinHref` | `string \| null` | `"#sign-in"` | Target of the sign-in link in the footer; null hides the line. |
| `providers` | `SignupFormProvider[]` |  | Outline buttons under an or continue with separator. Each has label, and optionally id, icon, href (a link) or onClick (a button). |
| `labels` | `Partial` |  | Overrides for any string; the rest keep their English defaults. |
| `onSubmit` | `(values: SignupFormValues) => void \| Promise` |  | Gets { name, email, password } instead of a native submit. Resolving resets the form and shows success; throwing keeps the values and shows the error. |
| `formProps` | `ComponentProps<"form">` |  | Props for the form, such as action and method when there is no onSubmit. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- The section and signup-form-form have data-status ("idle", "pending", "success" or "error") while onSubmit runs and after it settles.
- The parts are signup-form-card, signup-form-password-hint, signup-form-terms, signup-form-submit, signup-form-providers and signup-form-signin.

data-slot: `signup-form`, `signup-form-card`, `signup-form-container`, `signup-form-content`, `signup-form-description`, `signup-form-error`, `signup-form-footer`, `signup-form-form`, `signup-form-header`, `signup-form-password-hint`, `signup-form-provider`, `signup-form-providers`, `signup-form-separator`, `signup-form-signin`, `signup-form-submit`, `signup-form-success`, `signup-form-terms`, `signup-form-title`

State attributes:

- `data-status` (computed)

### Accessibility

- The section is named by its h1. Every field is required with autoComplete ("name", "email", "new-password"), so the browser blocks a short password or an unchecked terms box before onSubmit runs.
- The password hint is linked to its field with aria-describedby, and a button reveals the password, with aria-pressed for its state.
- Success goes to a role="status" region and the error to a role="alert" region; both stay mounted so they are announced.

### Examples

On the docs page: Default, Longer password. The usage examples in the source comments (https://corsairui.vercel.app/llms/signup-form.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/signup-form.md

## Site Footer

A page footer with a brand, link columns in a navigation landmark, a copyright line and optional social icon links.

Docs: https://corsairui.vercel.app/docs/site-footer

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/site-footer
```

Also adds: `utils`, `button`, `separator`.
npm: `@radix-ui/react-separator`, `@radix-ui/react-slot`, `class-variance-authority`, `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `brand` | `ReactNode` | `"Acme"` | Logo or wordmark at the top left. |
| `description` | `ReactNode` | `example copy` | A sentence under the brand; null hides it. |
| `columns` | `SiteFooterColumn[]` | `Product, Company, Resources` | Each has title and links ({ label, href, id? }[]), and optionally id. |
| `social` | `SiteFooterSocialLink[]` | `[]` | Icon links in the bottom row. Each has label, href and icon, and optionally id. |
| `bottom` | `ReactNode` | `"© Acme Inc. All rights reserved."` | Left of the bottom row. Nothing is computed, so pass the year yourself. |
| `navLabel` | `string` | `"Footer"` | Accessible name of the nav around the columns. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `site-footer`, `site-footer-bottom`, `site-footer-brand`, `site-footer-column`, `site-footer-column-title`, `site-footer-container`, `site-footer-description`, `site-footer-intro`, `site-footer-legal`, `site-footer-link`, `site-footer-links`, `site-footer-main`, `site-footer-nav`, `site-footer-social`, `site-footer-social-link`

### Accessibility

- A footer element: the contentinfo landmark when it is not inside another section.
- The columns share one nav named by navLabel; each column title is an h2 and its links a list.
- Social links are named by their label and their icons are hidden, so every one needs a label.

### Examples

On the docs page: Default, With social links. The usage examples in the source comments (https://corsairui.vercel.app/llms/site-footer.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/site-footer.md

## Stats

A row of key figures that count up when they scroll into view, under a centred section header.

Docs: https://corsairui.vercel.app/docs/stats

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/stats
```

Also adds: `utils`, `stat`, `use-in-view`, `use-entrance`, `number-ticker`.
npm: `clsx`, `lucide-react`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `eyebrow` | `ReactNode` | `"By the numbers"` | Small line above the title; null hides it. |
| `title / description` | `ReactNode` | `example copy` | The header; the title is an h2. null hides either. |
| `stats` | `StatsItem[]` | `four examples` | Each has label and value (a number counts up), and optionally id, prefix, suffix, format (Intl.NumberFormat options), caption and trend. |
| `variant` | `"plain" \| "cards"` | `"plain"` | Plain figures, or each in a bordered box. Exposed as data-variant. |
| `locale` | `string` |  | Locale for numeric values; defaults to NumberTicker's. |
| `trendLabels` | `Partial>` | `"Increase" / "Decrease" / "No change"` | Read before a caption with a trend, since the arrow is only a picture. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `stats`, `stats-container`, `stats-description`, `stats-eyebrow`, `stats-header`, `stats-prefix`, `stats-suffix`, `stats-title`

State attributes:

- `data-variant` (computed)

### Accessibility

- Built on StatGroup, a description list, so each value is read with its label. The title is an h2.
- Screen readers only hear the final number, never the moving digits.
- Trend arrows are hidden and replaced by trendLabels. With prefers-reduced-motion the final value shows at once.

### Examples

On the docs page: Default, Cards. The usage examples in the source comments (https://corsairui.vercel.app/llms/stats.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/stats.md

## Testimonials

A section of customer quotes with names, roles and avatars, in a masonry or grid layout.

Docs: https://corsairui.vercel.app/docs/testimonials

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/testimonials
```

Also adds: `utils`, `avatar`, `card`.
npm: `@radix-ui/react-avatar`, `class-variance-authority`, `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `eyebrow` | `ReactNode` | `"Testimonials"` | Small line above the title; null hides it. |
| `title / description` | `ReactNode` | `"Loved by teams who ship" / example copy` | The header; the title is an h2. null hides either. |
| `testimonials` | `Testimonial[]` | `six examples` | Each has quote and name, and optionally id, role and avatar (an image URL; the initials of name show without one). |
| `variant` | `"grid" \| "masonry"` | `"masonry"` | Cards of different heights in columns, or lined up in rows. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

data-slot: `testimonial`, `testimonial-author`, `testimonial-avatar`, `testimonial-card`, `testimonial-name`, `testimonial-quote`, `testimonial-role`, `testimonials`, `testimonials-container`, `testimonials-description`, `testimonials-eyebrow`, `testimonials-header`, `testimonials-item`, `testimonials-list`, `testimonials-title`

State attributes:

- `data-variant` (computed)

### Accessibility

- The title is an h2 and the quotes are a list; each is a figure with a blockquote and a figcaption.
- The avatar is hidden from screen readers, since the name is right next to it.

### Examples

On the docs page: Masonry, Grid. The usage examples in the source comments (https://corsairui.vercel.app/llms/testimonials.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/testimonials.md

## useEntrance

When a one-shot entrance runs: on first paint, or once in view, without hiding server-rendered content that is already on screen.

Docs: https://corsairui.vercel.app/docs/use-entrance

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/use-entrance
```

Also adds: `use-in-view`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `trigger` | `"load" \| "in-view"` | `"in-view"` | Play on first paint, from CSS alone, or once the element scrolls into view. |
| `once` | `boolean` | `true` | In view: play the first time only; otherwise rearm off screen. |
| `amount` | `number` | `0.3` | In view: fraction of the element that has to be visible. |
| `play` | `boolean` |  | Takes over from trigger: false holds the start state, true plays. |
| `returns` | `[ref, phase, inView]` |  | phase is "static", "armed" or "play"; render the start state for armed. |

### Accessibility

- Server-rendered content already on screen is never hidden while the page hydrates.
- Components render their start state and animation behind motion-safe, so reduced motion shows the content as is.

Source and usage examples: https://corsairui.vercel.app/llms/use-entrance.md

## useInView

Whether an element is in the viewport, from IntersectionObserver, with once, margin and amount options.

Docs: https://corsairui.vercel.app/docs/use-in-view

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/use-in-view
```

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `once` | `boolean` | `false` | Stop after the first entry. |
| `rootMargin` | `string` | `"0px"` | Grows or shrinks the viewport used for the check. |
| `amount` | `number` | `0` | Fraction of the element that has to be visible. |
| `onChange` | `(inView, entry) => void` |  | Called on every change; the first call says whether it started in view. |
| `returns` | `[ref, inView]` |  | A callback ref for the element and whether it is in view. |

### Examples

On the docs page: Live. The usage examples in the source comments (https://corsairui.vercel.app/llms/use-in-view.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/use-in-view.md

## useMediaQuery

Subscribes to a CSS media query. SSR-safe: renders a fallback on the server and hydrates without a mismatch.

Docs: https://corsairui.vercel.app/docs/use-media-query

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/use-media-query
```

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `query` | `string` |  | Any CSS media query. |
| `defaultValue` | `boolean` | `false` | Returned on the server and during hydration. |

### Examples

On the docs page: Live queries. The usage examples in the source comments (https://corsairui.vercel.app/llms/use-media-query.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/use-media-query.md

## useScrollSpy

The id of the section on screen, for a table of contents, from one IntersectionObserver. SSR-safe, keeps the last section between two.

Docs: https://corsairui.vercel.app/docs/use-scroll-spy

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/use-scroll-spy
```

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `ids` | `string[]` |  | The sections to follow, in page order. |
| `rootMargin` | `string` | `"-50% 0px -50% 0px"` | The line that decides the active section: the middle by default. |
| `defaultValue` | `string` |  | The answer on the server and before a section is reached. |
| `root` | `Element \| null` |  | A scroll box to watch instead of the window. |

### Accessibility

- Pair it with aria-current on the nav link of the active section.
- Between two sections it keeps the last one, so the nav never goes blank.

### Examples

On the docs page: Inside a scroll box. The usage examples in the source comments (https://corsairui.vercel.app/llms/use-scroll-spy.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/use-scroll-spy.md

## getSpotifyTrack

Title, artist, cover and preview clip for a Spotify link, read on the server from the public embed page.

Docs: https://corsairui.vercel.app/docs/spotify-track

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/spotify-track
```

npm: `spotify-url-info`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `getSpotifyTrack(url, init?)` | `Promise` |  | Title, artist, image, link and audio (a preview clip, when there is one). Only open.spotify.com links; /intl-xx/ paths are dropped. |

### Accessibility

- Call it on the server: Spotify does not allow it from the browser.

Source and usage examples: https://corsairui.vercel.app/llms/spotify-track.md

## Story Export

Turns stories into images for Instagram, WhatsApp and TikTok: renderStoryPng() draws JSX to a 1080 × 1920 PNG in the browser with Satori, shareStory() opens the share sheet with it, plus downloadStory(), loadFont() and the safe area the apps draw over.

Docs: https://corsairui.vercel.app/docs/story-export

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/story-export
```

npm: `satori`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `renderStoryPng(story, options)` | `Promise` |  | Draws an element to a PNG in the browser with Satori. options: fonts (required), width and height (1080 × 1920). |
| `renderStorySvg(story, options) / svgToPng(svg, width, height)` | `Promise / Promise` |  | The two steps apart, to skip the PNG when the SVG has not changed. |
| `shareStory({ file, title })` | `"shared" \| "cancelled" \| "unsupported"` |  | Shares a PNG File with the Web Share API, file only: apps drop text and links. Call it straight from a tap with the file ready; browsers refuse it after a slow await. |
| `canShareStory(file) / downloadStory(blob, fileName?)` | `boolean / void` |  | Whether this browser can share files (phones, mostly), and the fallback that saves it. |
| `loadFont(url, init?)` | `Promise` |  | Fetches a TTF, OTF or WOFF for the fonts option. WOFF2 is refused with a clear error. |
| `setStoryWasm(source)` | `string \| URL \| ArrayBuffer` |  | Where Satori's layout engine comes from. Next.js and webpack find it; with Vite, pass import wasm from "satori/layout.wasm?url". |
| `STORY_SIZE / STORY_SAFE_AREA` | `{ width, height } / { top, bottom, side }` |  | 1080 × 1920, and the bands the apps draw over: 250 px at the top, 340 px at the bottom, 64 px at the sides. |

### Styling

Restyle it in the project that installs it: `className` on each part, the theme variables, and these attributes with arbitrary variants (`[&_[data-slot=…]]:…`, `data-[state=…]:…`).

- Stories are drawn by Satori, so they take inline styles and flexbox only: every element with more than one child needs display: flex, and there are no classes.
- Text is drawn from the fonts you pass, so it looks the same on every phone. Pass every weight the story uses.

### Accessibility

- A story is an image: give its  or the frame an alt that says what it shows (storyAlt() reads a Story for it).

### Examples

On the docs page: Draw a PNG. The usage examples in the source comments (https://corsairui.vercel.app/llms/story-export.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/story-export.md

## Utils

The cn() helper: merges class names and resolves conflicting Tailwind utilities.

Docs: https://corsairui.vercel.app/docs/utils

### Install

```bash
pnpm dlx shadcn@latest add @corsair-ui/utils
```

npm: `clsx`, `tailwind-merge`.

### API

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `cn(...inputs)` | `ClassValue[]` |  | Joins classes with clsx and resolves Tailwind conflicts: the last one wins. |

### Examples

On the docs page: cn. The usage examples in the source comments (https://corsairui.vercel.app/llms/utils.md) are the reference.

Source and usage examples: https://corsairui.vercel.app/llms/utils.md