)
}
```
### Value Format
```tsx
import { Meter, MeterIndicator, MeterLabel, MeterTrack, MeterValue } from "@/components/ui/meter"
export default function MeterValueFormat() {
return (
Currency
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the meter:
| Slot name | Element |
| ----------------- | ------------------------------------ |
| `meter` | Root wrapper |
| `meter-track` | Background track bar |
| `meter-indicator` | Filled indicator bar |
| `meter-label` | Label text |
| `meter-value` | Value text |
### Customization Examples
```css
/* Make the meter track taller */
[data-slot="meter-track"] {
@apply h-4 rounded-lg;
}
/* Custom indicator color */
[data-slot="meter-indicator"] {
@apply bg-gradient-to-r from-blue-500 to-purple-500;
}
```
```tsx
{/* Override track color via className */}
```
## API Reference
### Meter
Root component that provides meter value context.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number` | - | Current value of the meter |
| `min` | `number` | `0` | Minimum value |
| `max` | `number` | `100` | Maximum value |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Meter content |
All [Base UI Meter.Root props](https://base-ui.com/react/components/meter) are forwarded via `...props`.
### MeterTrack
Background track that contains the indicator.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `"default" \| "success" \| "warning" \| "destructive"` | `"default"` | Color variant for the track |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Track content (typically MeterIndicator) |
All [Base UI Meter.Track props](https://base-ui.com/react/components/meter) are forwarded via `...props`.
### MeterIndicator
The filled portion of the track representing the current value.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `"default" \| "success" \| "warning" \| "destructive"` | `"default"` | Color variant for the indicator |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Meter.Indicator props](https://base-ui.com/react/components/meter) are forwarded via `...props`.
### MeterLabel
Accessible label for the meter.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Label text |
All [Base UI Meter.Label props](https://base-ui.com/react/components/meter) are forwarded via `...props`.
### MeterValue
Displays the current meter value as formatted text.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Custom value rendering |
All [Base UI Meter.Value props](https://base-ui.com/react/components/meter) are forwarded via `...props`.
### meterTrackVariants
A `cva` helper exported for applying meter track styles outside of the `` component.
```tsx
import { meterTrackVariants } from "@/components/ui/meter"
Custom track
```
### meterIndicatorVariants
A `cva` helper exported for applying meter indicator styles outside of the `` component.
```tsx
import { meterIndicatorVariants } from "@/components/ui/meter"
Custom indicator
```
## Accessibility
### Keyboard Interactions
The meter is a non-interactive display element and does not have keyboard interactions. Focus management is handled by surrounding interactive elements.
### ARIA Attributes
- The meter renders with `role="meter"` via the Base UI primitive.
- `aria-valuenow` reflects the current value.
- `aria-valuemin` and `aria-valuemax` define the range.
- `aria-labelledby` is automatically linked to `MeterLabel` when present.
- Screen readers announce the meter value and its label.
# NumberField
> A number input with increment/decrement controls built on Base UI
URL: https://prototyper-ui.com/docs/components/numberfield
Base UI reference: https://base-ui.com/react/components/number-field
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldDemo() {
return (
Width
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/numberfield.json
```
This will add the following files to your project:
- `components/ui/numberfield.tsx`
> **Note:** This component depends on [Field](/docs/components/field). It will be installed automatically.
## Usage
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
```
## Anatomy
```tsx
```
Or with individual step buttons:
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| -------------------------- | ------------------------------ | ------------------------------------------- | -------- |
| `NumberField` | `number-field` | Root provider, manages value state | Yes |
| `NumberFieldGroup` | `number-field-group` | Visual container for input and steppers | Yes |
| `NumberFieldInput` | `number-field-input` | The numeric input element | Yes |
| `NumberFieldIncrement` | `number-field-increment` | Button to increase the value | No |
| `NumberFieldDecrement` | `number-field-decrement` | Button to decrease the value | No |
| `NumberFieldSteppers` | `number-field-steppers` | Convenience wrapper with increment/decrement | No |
| `NumberFieldScrubArea` | `number-field-scrub-area` | Drag-to-scrub area for value adjustment | No |
| `NumberFieldScrubAreaCursor` | `number-field-scrub-area-cursor` | Custom cursor for the scrub area | No |
## Examples
### Currency
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldCurrency() {
return (
Transaction amount
)
}
```
### Description
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldDescription() {
return (
Width
Enter a width in centimeters.
)
}
```
### Disabled
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldDisabled() {
return (
Disabled
)
}
```
### Formatting
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldFormatting() {
return (
Adjust exposure
)
}
```
### Percentages
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldPercentages() {
return (
Sales tax
)
}
```
### Read Only
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldReadonly() {
return (
Read only
)
}
```
### Reusable
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldDescription, FieldLabel } from "@/components/ui/field"
export default function NumberfieldReusable() {
return (
CookiesPlease enter a number
)
}
```
### Step Values
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldStepValues() {
return (
StepStep + minValueStep + minValue + maxValue
)
}
```
### Units
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldUnits() {
return (
Package width
)
}
```
### Validation
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { FieldLabel } from "@/components/ui/field"
export default function NumberFieldValidation() {
return (
Enter your age
)
}
```
### Validation Error
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/numberfield"
import { Button } from "@/components/ui/button"
import { FieldError, FieldLabel } from "@/components/ui/field"
export default function NumberFieldValidationError() {
return (
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the number field:
| Slot name | Element |
| ------------------------------ | ----------------------------------------- |
| `number-field` | Root wrapper |
| `number-field-group` | Visual container for input and controls |
| `number-field-input` | The `` element |
| `number-field-increment` | Increment button |
| `number-field-decrement` | Decrement button |
| `number-field-steppers` | Steppers container (increment + decrement)|
| `number-field-scrub-area` | Drag-to-scrub interaction area |
| `number-field-scrub-area-cursor` | Custom cursor for scrub area |
### Customization Examples
```css
/* Make the number field group larger */
[data-slot="number-field-group"] {
@apply h-12;
}
/* Style the stepper buttons */
[data-slot="number-field-increment"],
[data-slot="number-field-decrement"] {
@apply px-2 text-primary;
}
```
```tsx
{/* Override size via className on the group */}
```
## API Reference
### NumberField
Root component that manages the number field state. Built on Base UI `NumberField.Root`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI NumberField props](https://base-ui.com/react/components/number-field) are forwarded via `...props`.
### NumberFieldGroup
Visual container that wraps the input and stepper controls.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Height of the group container |
| `className` | `string` | - | Additional CSS classes |
All [Base UI NumberField.Group props](https://base-ui.com/react/components/number-field) are forwarded via `...props`.
### NumberFieldInput
The numeric input element.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI NumberField.Input props](https://base-ui.com/react/components/number-field) are forwarded via `...props`.
### NumberFieldIncrement
Button that increments the value. Renders a chevron-up icon by default.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Custom icon (replaces default chevron-up) |
All [Base UI NumberField.Increment props](https://base-ui.com/react/components/number-field) are forwarded via `...props`.
### NumberFieldDecrement
Button that decrements the value. Renders a chevron-down icon by default.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Custom icon (replaces default chevron-down) |
All [Base UI NumberField.Decrement props](https://base-ui.com/react/components/number-field) are forwarded via `...props`.
### NumberFieldSteppers
Convenience wrapper that renders increment and decrement buttons in a vertical stack.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All standard `div` props are forwarded via `...props`.
### NumberFieldScrubArea
A drag-to-scrub interaction area for adjusting the value by dragging.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI NumberField.ScrubArea props](https://base-ui.com/react/components/number-field) are forwarded via `...props`.
### NumberFieldScrubAreaCursor
Custom cursor element rendered within the scrub area.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI NumberField.ScrubAreaCursor props](https://base-ui.com/react/components/number-field) are forwarded via `...props`.
### numberFieldGroupVariants
A `cva` helper exported for applying number field group styles outside the component.
```tsx
import { numberFieldGroupVariants } from "@/components/ui/numberfield"
...
```
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | ---------------------------------------------------- |
| `ArrowUp` | Increments the value by one step |
| `ArrowDown` | Decrements the value by one step |
| `Home` | Sets the value to the minimum (if `min` is set) |
| `End` | Sets the value to the maximum (if `max` is set) |
| `Tab` | Moves focus into or out of the number field |
### ARIA Attributes
- The input renders with `role="spinbutton"` via Base UI.
- `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` are set automatically based on the current value and constraints.
- `aria-invalid` is set when validation fails.
- `data-disabled` is set on the group and controls when the field is disabled.
- Increment and decrement buttons are labeled for screen readers.
# Popover
> A popup anchored to a trigger element built on Base UI
URL: https://prototyper-ui.com/docs/components/popover
Base UI reference: https://base-ui.com/react/components/popover
```tsx
import { Button } from "@/components/ui/button"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
import { Switch } from "@/components/ui/switch"
export default function PopoverDemo() {
return (
}>
Settings
Wi-FiBluetoothMute
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/popover.json
```
This will add the following files to your project:
- `components/ui/popover.tsx`
## Usage
```tsx
import {
Popover,
PopoverTrigger,
PopoverContent,
} from "@/components/ui/popover"
Open
Popover content here.
```
## Anatomy
```tsx
{/* your content */}
```
| Sub-component | `data-slot` | Purpose | Required |
| -------------------- | ---------------------- | --------------------------------------------- | -------- |
| `Popover` | `popover` | Root provider, manages open/close state | Yes |
| `PopoverTrigger` | `popover-trigger` | Element that toggles the popover | Yes |
| `PopoverContent` | `popover-content` | The popup panel anchored to the trigger | Yes |
| `PopoverHeader` | `popover-header` | Flex container for title and description | No |
| `PopoverTitle` | `popover-title` | Accessible title for the popover | No |
| `PopoverDescription` | `popover-description` | Accessible description for the popover | No |
## Examples
### Container Padding
```tsx
import { Button } from "@/components/ui/button"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
export default function PopoverContainerPadding() {
return (
}>
Container Padding
)
}
```
### Flipping
```tsx
import { Button } from "@/components/ui/button"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
export default function PopoverFlipping() {
return (
}>
Default
This is a popover that will flip if it can't fully render below
the button.
}>
shouldFlip=false
This is a popover that won't flip if it can't fully render below
the button.
)
}
```
### Offset
```tsx
import { Button } from "@/components/ui/button"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
export default function PopoverOffset() {
return (
}>
Offset
Offset by an additional 50px.
)
}
```
### Position
```tsx
import { Button } from "@/components/ui/button"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
export default function PopoverPosition() {
return (
}>
⬅️
In left-to-right, this is on the left. In right-to-left, this is
on the right.
}>
⬆️
This popover is above the button.
}>
⬇️
This popover is below the button.
}>
➡️
In left-to-right, this is on the right. In right-to-left, this is
on the left.
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the popover:
| Slot name | Element |
| ---------------------- | -------------------------------- |
| `popover` | Root provider (no DOM rendered) |
| `popover-trigger` | The trigger element |
| `popover-content` | The popup panel |
| `popover-header` | Header container |
| `popover-title` | Title heading |
| `popover-description` | Description text |
### Customization Examples
```css
/* Make popover wider */
[data-slot="popover-content"] {
@apply min-w-[16rem];
}
/* Style the popover title */
[data-slot="popover-title"] {
@apply text-base font-bold;
}
```
```tsx
{/* Override styles via className */}
Settings
{/* ... */}
```
## API Reference
### Popover
Root component that manages open/close state.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | - | Controlled open state |
| `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
| `defaultOpen` | `boolean` | `false` | Initial open state for uncontrolled usage |
All [Base UI Popover.Root props](https://base-ui.com/react/components/popover) are forwarded via `...props`.
### PopoverTrigger
Element that toggles the popover when clicked.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Popover.Trigger props](https://base-ui.com/react/components/popover) are forwarded via `...props`.
### PopoverContent
The popup panel rendered inside a portal with a positioner.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment relative to the trigger |
| `alignOffset` | `number` | `0` | Offset from the alignment edge |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Preferred side relative to trigger |
| `sideOffset` | `number` | `4` | Gap between trigger and popup |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Popover content |
All [Base UI Popover.Popup props](https://base-ui.com/react/components/popover) are forwarded via `...props`.
### PopoverHeader
Flex column container for title and description.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Header content |
Standard div props are forwarded via `...props`.
### PopoverTitle
Accessible title for the popover.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Popover.Title props](https://base-ui.com/react/components/popover) are forwarded via `...props`.
### PopoverDescription
Accessible description for the popover.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Popover.Description props](https://base-ui.com/react/components/popover) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ----------- | ----------------------------------------------------------------- |
| `Escape` | Closes the popover |
| `Tab` | Moves focus to the next focusable element within the popover |
| `Shift+Tab` | Moves focus to the previous focusable element within the popover |
### ARIA Attributes
- `PopoverContent` receives `role="dialog"` by default via Base UI.
- `aria-labelledby` is automatically linked to `PopoverTitle` when present.
- `aria-describedby` is automatically linked to `PopoverDescription` when present.
- Focus moves into the popover when it opens.
- Focus returns to the trigger element when the popover closes.
# Progress
> A progress bar showing completion status
URL: https://prototyper-ui.com/docs/components/progress
Base UI reference: https://base-ui.com/react/components/progress
```tsx
"use client"
import React from "react"
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
export default function ProgressDemo() {
const [progress, setProgress] = React.useState(13)
React.useEffect(() => {
const timer = setTimeout(() => setProgress(80), 500)
return () => clearTimeout(timer)
}, [])
return (
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/progress.json
```
This will add the following files to your project:
- `components/ui/progress.tsx`
## Usage
```tsx
import { Progress, ProgressLabel } from "@/components/ui/progress"
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ------------------- | -------------------- | ------------------------------------------ | -------- |
| `Progress` | `progress` | Root provider with built-in track/indicator| Yes |
| `ProgressTrack` | `progress-track` | Background track for the indicator | Yes |
| `ProgressIndicator` | `progress-indicator` | Filled portion representing progress | Yes |
| `ProgressLabel` | `progress-label` | Accessible label for the progress bar | No |
| `ProgressValue` | `progress-value` | Displays the current value as text | No |
> **Note:** `ProgressTrack` and `ProgressIndicator` are rendered automatically inside the `Progress` component. You do not need to include them manually.
## Examples
### Custom Format
```tsx
import {
Progress,
ProgressLabel,
} from "@/components/ui/progress"
export default function ProgressCustomFormat() {
return (
)
}
```
### Reusable
```tsx
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
export default function ProgressReusable() {
return (
)
}
```
### Value Format
```tsx
import {
Progress,
ProgressLabel,
ProgressValue,
} from "@/components/ui/progress"
export default function ProgressValueFormat() {
return (
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the progress bar:
| Slot name | Element |
| -------------------- | ------------------------------------ |
| `progress` | Root wrapper |
| `progress-track` | Background track bar |
| `progress-indicator` | Filled indicator bar |
| `progress-label` | Label text |
| `progress-value` | Value text |
### Customization Examples
```css
/* Make the progress track taller */
[data-slot="progress-track"] {
@apply h-4 rounded-lg;
}
/* Custom indicator with gradient */
[data-slot="progress-indicator"] {
@apply bg-gradient-to-r from-green-500 to-emerald-500;
}
```
```tsx
{/* Override color via the color prop */}
```
## API Reference
### Progress
Root component that provides progress value context and renders the track and indicator internally.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number \| null` | - | Current progress value, null for indeterminate |
| `min` | `number` | `0` | Minimum value |
| `max` | `number` | `100` | Maximum value |
| `color` | `"default" \| "success" \| "warning" \| "destructive"` | `"default"` | Color variant for the track and indicator |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Progress content (label, value) |
All [Base UI Progress.Root props](https://base-ui.com/react/components/progress) are forwarded via `...props`.
### ProgressTrack
Background track that contains the indicator. Rendered automatically by `Progress`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `"default" \| "success" \| "warning" \| "destructive"` | `"default"` | Color variant for the track |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Track content (typically ProgressIndicator) |
All [Base UI Progress.Track props](https://base-ui.com/react/components/progress) are forwarded via `...props`.
### ProgressIndicator
The filled portion of the track representing current progress. Rendered automatically by `Progress`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `"default" \| "success" \| "warning" \| "destructive"` | `"default"` | Color variant for the indicator |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Progress.Indicator props](https://base-ui.com/react/components/progress) are forwarded via `...props`.
### ProgressLabel
Accessible label for the progress bar.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Label text |
All [Base UI Progress.Label props](https://base-ui.com/react/components/progress) are forwarded via `...props`.
### ProgressValue
Displays the current progress value as formatted text.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Custom value rendering |
All [Base UI Progress.Value props](https://base-ui.com/react/components/progress) are forwarded via `...props`.
### progressTrackVariants
A `cva` helper exported for applying progress track styles outside of the `` component.
```tsx
import { progressTrackVariants } from "@/components/ui/progress"
Custom track
```
### progressIndicatorVariants
A `cva` helper exported for applying progress indicator styles outside of the `` component.
```tsx
import { progressIndicatorVariants } from "@/components/ui/progress"
Custom indicator
```
## Accessibility
### Keyboard Interactions
The progress bar is a non-interactive display element and does not have keyboard interactions. Focus management is handled by surrounding interactive elements.
### ARIA Attributes
- The progress bar renders with `role="progressbar"` via the Base UI primitive.
- `aria-valuenow` reflects the current value.
- `aria-valuemin` and `aria-valuemax` define the range.
- When `value` is `null`, the progress bar enters indeterminate mode and `aria-valuenow` is removed.
- `aria-labelledby` is automatically linked to `ProgressLabel` when present.
- The `data-indeterminate` attribute is set when value is `null`, and `data-complete` is set when value equals max.
- Screen readers announce the progress value and its label.
# Radio Group
> A group of radio buttons built on Base UI
URL: https://prototyper-ui.com/docs/components/radio-group
Base UI reference: https://base-ui.com/react/components/radio
```tsx
import { FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export default function RadioGroupDemo() {
return (
Favorite pet
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/radio-group.json
```
This will add the following files to your project:
- `components/ui/radio-group.tsx`
## Usage
```tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
Option 1Option 2Option 3
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ---------------- | ---------------------- | ------------------------------------------- | -------- |
| `RadioGroup` | `radio-group` | Root container, manages selection state | Yes |
| `RadioGroupItem` | `radio-group-item` | Individual radio button with built-in indicator | Yes |
| (indicator) | `radio-group-indicator`| Selection dot (rendered internally by RadioGroupItem) | Yes |
## Examples
### Description
```tsx
import { FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export default function RadioGroupDescription() {
return (
Favorite avatar
Please select an avatar.
)
}
```
### Disabled
```tsx
import { FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export default function RadioGroupDisabled() {
return (
Favorite sport
)
}
```
### Disabled Individual
```tsx
import { FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export default function RadioGroupDisabledIndividual() {
return (
Favorite sport
)
}
```
### Orientation
```tsx
import { FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export default function RadioGroupOrientation() {
return (
Favorite avatar
)
}
```
### Read Only
```tsx
import { FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export default function RadioGroupReadonly() {
return (
Favorite avatar
)
}
```
### Reusable
```tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { FieldLabel } from "@/components/ui/field"
export default function RadioGroupReusable() {
return (
Favorite sport *
Select a favorite sport
)
}
```
### Validation
```tsx
import { Button } from "@/components/ui/button"
import { FieldError, FieldLabel } from "@/components/ui/field"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
export default function RadioGroupValidation() {
return (
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the radio group:
| Slot name | Element |
| ---------------------- | -------------------------------------- |
| `radio-group` | Root container for all radio items |
| `radio-group-item` | Individual radio button circle |
| `radio-group-indicator`| The selection dot inside the circle |
### Customization Examples
```css
/* Make radio items larger */
[data-slot="radio-group-item"] {
@apply size-5;
}
/* Custom checked color */
[data-slot="radio-group-item"] {
@apply data-checked:bg-green-600 data-checked:border-green-600;
}
```
```tsx
{/* Override layout via className */}
AB
```
## API Reference
### RadioGroup
Root container that manages which radio item is selected.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | - | Controlled selected value |
| `defaultValue` | `string` | - | Initial selected value for uncontrolled usage |
| `onValueChange` | `(value: string, event: Event) => void` | - | Callback when selection changes |
| `disabled` | `boolean` | `false` | Whether all radio items are disabled |
| `readOnly` | `boolean` | `false` | Whether the group is read-only |
| `required` | `boolean` | `false` | Whether a selection is required |
| `name` | `string` | - | Name attribute for form submission |
| `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Orientation of the radio group |
| `className` | `string` | - | Additional CSS classes |
All [Base UI RadioGroup props](https://base-ui.com/react/components/radio) are forwarded via `...props`.
### RadioGroupItem
An individual radio button with a built-in selection indicator.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | - | Unique value for this radio item (required) |
| `disabled` | `boolean` | `false` | Whether this individual item is disabled |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Radio props](https://base-ui.com/react/components/radio) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | --------------------------------------------------- |
| `ArrowDown` | Moves focus and selection to the next radio item |
| `ArrowRight` | Moves focus and selection to the next radio item |
| `ArrowUp` | Moves focus and selection to the previous radio item|
| `ArrowLeft` | Moves focus and selection to the previous radio item|
| `Tab` | Moves focus into / out of the radio group |
### ARIA Attributes
- `RadioGroup` renders with `role="radiogroup"` via Base UI.
- Each `RadioGroupItem` renders with `role="radio"`.
- `aria-checked` is set to `true` on the selected item and `false` on others.
- `aria-disabled` is set when the group or an individual item is disabled.
- `aria-readonly` is set when the group is read-only.
- `aria-required` is set when the group is required.
- Screen readers announce each radio item label and its selected/unselected state.
# Select
> A dropdown select input built on Base UI
URL: https://prototyper-ui.com/docs/components/select
Base UI reference: https://base-ui.com/react/components/select
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export default function SelectDemo() {
return (
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/select.json
```
This will add the following files to your project:
- `components/ui/select.tsx`
## Usage
```tsx
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select"
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ---------------------- | ---------------------------- | ---------------------------------------------- | -------- |
| `Select` | `select` | Root provider, manages selection state | Yes |
| `SelectTrigger` | `select-trigger` | Button that opens the dropdown | Yes |
| `SelectValue` | `select-value` | Displays the currently selected value | Yes |
| `SelectContent` | `select-content` | Popup container for items | Yes |
| `SelectItem` | `select-item` | An individual selectable option | Yes |
| `SelectGroup` | `select-group` | Groups related items together | No |
| `SelectLabel` | `select-label` | Label for a group of items | No |
| `SelectSeparator` | `select-separator` | Visual divider between items or groups | No |
| `SelectScrollUpButton` | `select-scroll-up-button` | Scroll indicator at the top of the list | No |
| `SelectScrollDownButton` | `select-scroll-down-button` | Scroll indicator at the bottom of the list | No |
## Examples
### Content
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export default function SelectContentDemo() {
const options = [
{ id: 1, name: "Aerospace" },
{ id: 2, name: "Mechanical" },
{ id: 3, name: "Civil" },
{ id: 4, name: "Biomedical" },
{ id: 5, name: "Nuclear" },
{ id: 6, name: "Industrial" },
{ id: 7, name: "Chemical" },
{ id: 8, name: "Agricultural" },
{ id: 9, name: "Electrical" },
]
return (
)
}
```
### Description
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export default function SelectDescription() {
return (
)
}
```
### With Icon
```tsx
import { Check } from "lucide-react"
import { Switch, SwitchTrack, SwitchThumb, SwitchIcon } from "@/components/ui/switch"
export default function SwitchWithIcon() {
return (
Notifications
)
}
```
### Label Left
```tsx
import { Switch, SwitchTrack, SwitchThumb } from "@/components/ui/switch"
export default function SwitchLabelLeft() {
return (
Notifications
)
}
```
### Disabled
```tsx
import { Switch, SwitchTrack, SwitchThumb } from "@/components/ui/switch"
export default function SwitchDisabled() {
return (
Airplane Mode
)
}
```
### Read Only
```tsx
import { Switch, SwitchTrack, SwitchThumb } from "@/components/ui/switch"
export default function SwitchReadonly() {
return (
Bluetooth
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the switch:
| Slot name | Element |
| -------------- | ------------------------------ |
| `switch` | Root wrapper (label + track) |
| `switch-track` | The sliding track background |
| `switch-thumb` | The circular thumb indicator |
### Customization Examples
```css
/* Make the switch track wider */
[data-slot="switch-track"] {
@apply w-12 h-6;
}
/* Custom checked track color */
[data-slot="switch"] [data-slot="switch-track"] {
@apply group-data-checked:bg-green-600;
}
```
```tsx
{/* Override styles via className */}
Wide gap label
```
## API Reference
### Switch
Root component that manages on/off state and renders a track with thumb.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | - | Controlled checked state |
| `defaultChecked` | `boolean` | `false` | Initial checked state for uncontrolled usage |
| `onCheckedChange` | `(checked: boolean, event: Event) => void` | - | Callback when checked state changes |
| `disabled` | `boolean` | `false` | Whether the switch is disabled |
| `readOnly` | `boolean` | `false` | Whether the switch is read-only |
| `required` | `boolean` | `false` | Whether the switch is required |
| `name` | `string` | - | Name attribute for form submission |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Label content rendered next to the switch |
All [Base UI Switch.Root props](https://base-ui.com/react/components/switch) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------- | ------------------------------------ |
| `Space` | Toggles the switch on/off |
| `Tab` | Moves focus to / away from the switch |
### ARIA Attributes
- Renders with `role="switch"` via Base UI.
- `aria-checked` is set to `true` or `false` based on the switch state.
- `aria-disabled` is set when the switch is disabled.
- `aria-readonly` is set when the switch is read-only.
- Screen readers announce the label and the on/off state.
# Tabs
> Tabbed content panels built on Base UI
URL: https://prototyper-ui.com/docs/components/tabs
Base UI reference: https://base-ui.com/react/components/tabs
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export default function TabsDemo() {
return (
AccountNotificationsBilling
Update your name, email, and profile photo.
Choose which notifications you receive and how.
Manage your subscription plan and payment method.
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/tabs.json
```
This will add the following files to your project:
- `components/ui/tabs.tsx`
## Usage
```tsx
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "@/components/ui/tabs"
Tab 1Tab 2Content 1Content 2
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| -------------- | -------------- | ---------------------------------------- | -------- |
| `Tabs` | `tabs` | Root provider, manages active tab state | Yes |
| `TabsList` | `tabs-list` | Container for tab triggers | Yes |
| `TabsTrigger` | `tabs-trigger` | Button that activates a tab panel | Yes |
| `TabsContent` | `tabs-content` | Content panel associated with a trigger | Yes |
## Examples
### Disabled
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export default function TabsDisabled() {
return (
Mouse SettingsKeyboard SettingsGamepad SettingsMouse SettingsKeyboard SettingsGamepad Settings
)
}
```
### Disabled Dynamic
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export default function TabsDisabledDynamic() {
let tabs = [
{ id: 1, title: "Mouse settings" },
{ id: 2, title: "Keyboard settings" },
{ id: 3, title: "Gamepad settings" },
]
return (
{tabs.map((item) => (
{item.title}
))}
{tabs.map((item) => (
{item.title}
))}
)
}
```
### Disabled Items
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export default function TabsDisabledItems() {
return (
Mouse SettingsKeyboard Settings
Gamepad Settings
Mouse SettingsKeyboard SettingsGamepad Settings
)
}
```
### Dynamic
```tsx
"use client"
import React from "react"
import { Button } from "@/components/ui/button"
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export default function TabsDynamic() {
let [tabs, setTabs] = React.useState([
{ id: 1, title: "Tab 1", content: "Tab body 1" },
{ id: 2, title: "Tab 2", content: "Tab body 2" },
{ id: 3, title: "Tab 3", content: "Tab body 3" },
])
let addTab = () => {
setTabs((tabs) => [
...tabs,
{
id: tabs.length + 1,
title: `Tab ${tabs.length + 1}`,
content: `Tab body ${tabs.length + 1}`,
},
])
}
let removeTab = () => {
if (tabs.length > 1) {
setTabs((tabs) => tabs.slice(0, -1))
}
}
return (
{tabs.map((item) => (
{item.title}
))}
Add tab
Remove tab
{tabs.map((item) => (
{item.content}
))}
)
}
```
### Focus
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { Input } from "@/components/ui/textfield"
export default function TabsFocus() {
return (
Jane DoeJohn DoeJoe BloggsSenatus Populusque Romanus.Alea jacta est.
)
}
```
### Vertical
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
export default function TabsVertical() {
return (
John DoeJane DoeJoe BloggsThere is no prior chat history with John Doe.There is no prior chat history with Jane Doe.
There is no prior chat history with Joe Bloggs.
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the tabs:
| Slot name | Element |
| -------------- | ------------------------------------ |
| `tabs` | Root wrapper |
| `tabs-list` | Container for tab triggers |
| `tabs-trigger` | Individual tab button |
| `tabs-content` | Tab panel content area |
### Customization Examples
```css
/* Make the tab list full-width */
[data-slot="tabs-list"] {
@apply w-full;
}
/* Custom active tab style */
[data-slot="tabs-trigger"][data-active] {
@apply bg-primary text-primary-foreground;
}
```
```tsx
{/* Override list variant via className */}
Tab 1Tab 2
```
## API Reference
### Tabs
Root component that manages active tab state and context.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `any` | - | Initial active tab for uncontrolled usage |
| `value` | `any` | - | Controlled active tab value |
| `onValueChange` | `(value: any) => void` | - | Callback when the active tab changes |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction of the tabs |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Tabs content |
All [Base UI Tabs.Root props](https://base-ui.com/react/components/tabs) are forwarded via `...props`.
### TabsList
Container for tab triggers with variant styling.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "line"` | `"default"` | Visual style variant for the tab list |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Tab triggers |
All [Base UI Tabs.List props](https://base-ui.com/react/components/tabs) are forwarded via `...props`.
### TabsTrigger
Button that activates a tab panel when clicked.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `any` | - | Value linking this trigger to a panel |
| `disabled` | `boolean` | `false` | Whether the trigger is disabled |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Tab label content |
All [Base UI Tabs.Tab props](https://base-ui.com/react/components/tabs) are forwarded via `...props`.
### TabsContent
Content panel that is shown when its associated trigger is active.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `any` | - | Value linking this panel to a trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Panel content |
All [Base UI Tabs.Panel props](https://base-ui.com/react/components/tabs) are forwarded via `...props`.
### tabsListVariants
A `cva` helper exported for use outside of the `` component (e.g., applying tab list styles to custom elements).
```tsx
import { tabsListVariants } from "@/components/ui/tabs"
Custom tab list
```
## Accessibility
### Keyboard Interactions
| Key | Action |
| ---------------- | ------------------------------------------------------------------- |
| `ArrowRight` | Moves focus to the next tab trigger (horizontal orientation) |
| `ArrowLeft` | Moves focus to the previous tab trigger (horizontal orientation) |
| `ArrowDown` | Moves focus to the next tab trigger (vertical orientation) |
| `ArrowUp` | Moves focus to the previous tab trigger (vertical orientation) |
| `Home` | Moves focus to the first tab trigger |
| `End` | Moves focus to the last tab trigger |
| `Space` | Activates the focused tab trigger |
| `Enter` | Activates the focused tab trigger |
| `Tab` | Moves focus into the active tab panel, then to the next element |
### ARIA Attributes
- The tab list renders with `role="tablist"`.
- Each trigger renders with `role="tab"` and `aria-selected` indicating whether it is active.
- Each content panel renders with `role="tabpanel"`.
- `aria-controls` on each trigger links to its corresponding panel.
- `aria-labelledby` on each panel links back to its corresponding trigger.
- `aria-orientation` is set on the tab list based on the `orientation` prop.
- `aria-disabled` is set on disabled tab triggers.
# TextField
> A text input with label and validation built on Base UI
URL: https://prototyper-ui.com/docs/components/textfield
Base UI reference: https://base-ui.com/react/components/field
```tsx
import { FieldLabel } from "@/components/ui/field"
import { Input, TextField } from "@/components/ui/textfield"
export default function TextFieldDemo() {
return (
First name
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/textfield.json
```
This will add the following files to your project:
- `components/ui/textfield.tsx`
> **Note:** This component depends on [Field](/docs/components/field). It will be installed automatically.
## Usage
```tsx
import { TextField, Input } from "@/components/ui/textfield"
import { FieldLabel } from "@/components/ui/field"
Name
```
## Anatomy
```tsx
```
Or with `TextArea`:
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ----------------- | -------------------- | -------------------------------------------- | -------- |
| `TextField` | `text-field` | Root field wrapper, provides form context | Yes |
| `Input` | `input` | Single-line text input | Yes* |
| `TextArea` | `textarea` | Multi-line text input | Yes* |
| `FieldLabel` | `field-label` | Label for the text field | No |
| `FieldDescription`| `field-description` | Descriptive helper text | No |
| `FieldError` | `field-error` | Validation error message | No |
\* Use either `Input` or `TextArea`, not both.
## Examples
### Description
```tsx
import { FieldLabel } from "@/components/ui/field"
import { Input, TextField } from "@/components/ui/textfield"
export default function TextFieldDescription() {
return (
Email
Enter an email for us to contact you about your order.
)
}
```
### Disabled
```tsx
import { FieldLabel } from "@/components/ui/field"
import { Input, TextField } from "@/components/ui/textfield"
export default function TextFieldDisabled() {
return (
Email
)
}
```
### Multiline
```tsx
import { FieldLabel } from "@/components/ui/field"
import { TextArea, TextField } from "@/components/ui/textfield"
export default function TextFieldMultiline() {
return (
Comment
)
}
```
### Read Only
```tsx
import { FieldLabel } from "@/components/ui/field"
import { Input, TextField } from "@/components/ui/textfield"
export default function TextFieldReadonly() {
return (
Email
)
}
```
### Reusable
```tsx
import { ProtoTextField } from "@/components/ui/textfield"
export default function TextfieldReusable() {
return (
)
}
```
### Validation
```tsx
import { Button } from "@/components/ui/button"
import { FieldError, FieldLabel } from "@/components/ui/field"
import { Input, TextField } from "@/components/ui/textfield"
export default function TextFieldValidation() {
return (
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the text field:
| Slot name | Element |
| ------------------ | ------------------------------ |
| `text-field` | Root wrapper (`Field.Root`) |
| `input` | The `` element |
| `textarea` | The `
# Toggle
> A two-state toggle button built on Base UI
URL: https://prototyper-ui.com/docs/components/toggle
Base UI reference: https://base-ui.com/react/components/toggle
```tsx
import { FontBoldIcon } from "@radix-ui/react-icons"
import { Toggle } from "@/components/ui/toggle"
export default function ToggleDemo() {
return (
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/toggle.json
```
This will add the following files to your project:
- `components/ui/toggle.tsx`
## Usage
```tsx
import { Toggle } from "@/components/ui/toggle"
Bold
```
## Examples
### Outline
```tsx
import { FontItalicIcon } from "@radix-ui/react-icons"
import { Toggle } from "@/components/ui/toggle"
export default function ToggleOutline() {
return (
)
}
```
### Small
```tsx
import { FontItalicIcon } from "@radix-ui/react-icons"
import { Toggle } from "@/components/ui/toggle"
export default function ToggleSm() {
return (
)
}
```
### Large
```tsx
import { FontItalicIcon } from "@radix-ui/react-icons"
import { Toggle } from "@/components/ui/toggle"
export default function ToggleLg() {
return (
)
}
```
### Disabled
```tsx
import { UnderlineIcon } from "@radix-ui/react-icons"
import { Toggle } from "@/components/ui/toggle"
export default function ToggleDisabled() {
return (
)
}
```
### With Text
```tsx
import { FontItalicIcon } from "@radix-ui/react-icons"
import { Toggle } from "@/components/ui/toggle"
export default function ToggleWithText() {
return (
Italic
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target the toggle in CSS:
| Slot name | Element |
| --------- | ------------------- |
| `toggle` | The `` root |
### Customization Examples
```css
/* Style all pressed toggles */
[data-slot="toggle"][aria-pressed="true"] {
@apply bg-primary text-primary-foreground;
}
```
```tsx
{/* Use className for one-off overrides */}
Pill Toggle
```
## API Reference
### Toggle
A two-state toggle button that can be pressed or unpressed.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "outline"` | `"default"` | Visual style variant |
| `size` | `"default" \| "sm" \| "lg"` | `"default"` | Size of the toggle |
| `pressed` | `boolean` | - | Controlled pressed state |
| `onPressedChange` | `(pressed: boolean) => void` | - | Callback when pressed state changes |
| `defaultPressed` | `boolean` | `false` | Initial pressed state for uncontrolled usage |
| `disabled` | `boolean` | `false` | Whether the toggle is disabled |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Toggle content |
All [Base UI Toggle props](https://base-ui.com/react/components/toggle) are forwarded via `...props`.
### toggleVariants
A `cva` helper exported for use outside of the `` component.
```tsx
import { toggleVariants } from "@/components/ui/toggle"
Custom Toggle
```
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------- | -------------------------------------- |
| `Space` | Toggles the pressed state |
| `Enter` | Toggles the pressed state |
| `Tab` | Moves focus to / away from the toggle |
### ARIA Attributes
- Renders as a `` element with `aria-pressed` attribute.
- `aria-pressed="true"` when the toggle is in the pressed state, `aria-pressed="false"` when unpressed.
- Screen readers announce the toggle as a toggle button with its current pressed/unpressed state.
- `disabled` attribute is set when the toggle is disabled.
# Toolbar
> A toolbar container with keyboard navigation built on Base UI
URL: https://prototyper-ui.com/docs/components/toolbar
Base UI reference: https://base-ui.com/react/components/toolbar
```tsx
"use client"
import {
FontBoldIcon,
FontItalicIcon,
UnderlineIcon,
} from "@radix-ui/react-icons"
import { Button } from "@/components/ui/button"
import {
Checkbox,
CheckboxControl,
CheckboxIndicator,
} from "@/components/ui/checkbox"
import { Toggle } from "@/components/ui/toggle"
import { Toolbar, ToolbarSeparator } from "@/components/ui/toolbar"
export default function ToolbarDemo() {
return (
CopyPasteCut
Night Mode
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/toolbar.json
```
This will add the following files to your project:
- `components/ui/toolbar.tsx`
## Usage
```tsx
import {
Toolbar,
ToolbarButton,
ToolbarSeparator,
} from "@/components/ui/toolbar"
BoldItalicLink
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ------------------ | -------------------- | -------------------------------------------- | -------- |
| `Toolbar` | `toolbar` | Root container with keyboard navigation | Yes |
| `ToolbarButton` | `toolbar-button` | A button within the toolbar | No |
| `ToolbarLink` | `toolbar-link` | A link within the toolbar | No |
| `ToolbarGroup` | `toolbar-group` | Groups related toolbar items together | No |
| `ToolbarSeparator` | `toolbar-separator` | Visual separator between toolbar items | No |
| `ToolbarInput` | `toolbar-input` | A text input within the toolbar | No |
## Examples
### Vertical
```tsx
"use client"
import {
CursorArrowIcon,
MagicWandIcon,
MoveIcon,
Pencil1Icon,
Pencil2Icon,
} from "@radix-ui/react-icons"
import { Button } from "@/components/ui/button"
import { Toolbar, ToolbarSeparator } from "@/components/ui/toolbar"
export default function ToolbarVerticalDemo() {
return (
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the toolbar:
| Slot name | Element |
| ------------------- | ------------------------------------- |
| `toolbar` | Root toolbar container |
| `toolbar-button` | Button element within the toolbar |
| `toolbar-link` | Link element within the toolbar |
| `toolbar-group` | Group wrapper for related items |
| `toolbar-separator` | Separator line between items |
| `toolbar-input` | Text input within the toolbar |
### Customization Examples
```css
/* Add a border around the toolbar */
[data-slot="toolbar"] {
@apply rounded-lg border bg-background p-1;
}
/* Style toolbar buttons with more padding */
[data-slot="toolbar-button"] {
@apply px-4;
}
```
```tsx
{/* Override styles via className */}
Bold
```
## API Reference
### Toolbar
Root container that provides keyboard navigation between toolbar items.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction of the toolbar |
| `disabled` | `boolean` | `false` | Disables all items in the toolbar |
| `loop` | `boolean` | `true` | Whether keyboard navigation loops around |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Toolbar content |
All [Base UI Toolbar.Root props](https://base-ui.com/react/components/toolbar) are forwarded via `...props`.
### ToolbarButton
A button within the toolbar that participates in keyboard navigation.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | Whether the button is disabled |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Button content |
All [Base UI Toolbar.Button props](https://base-ui.com/react/components/toolbar) are forwarded via `...props`.
### ToolbarLink
A link within the toolbar that participates in keyboard navigation.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | - | The URL the link points to |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Link content |
All [Base UI Toolbar.Link props](https://base-ui.com/react/components/toolbar) are forwarded via `...props`.
### ToolbarGroup
Groups related toolbar items together visually and semantically.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | Disables all items in the group |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Group content |
All [Base UI Toolbar.Group props](https://base-ui.com/react/components/toolbar) are forwarded via `...props`.
### ToolbarSeparator
A visual separator between toolbar items or groups.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | `"vertical"` | Direction of the separator line |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Toolbar.Separator props](https://base-ui.com/react/components/toolbar) are forwarded via `...props`.
### ToolbarInput
A text input within the toolbar that participates in keyboard navigation.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Toolbar.Input props](https://base-ui.com/react/components/toolbar) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ---------------- | ----------------------------------------------------------- |
| `Tab` | Moves focus into the toolbar (focuses the first/last active item) |
| `ArrowRight` | Moves focus to the next toolbar item (horizontal orientation) |
| `ArrowLeft` | Moves focus to the previous toolbar item (horizontal orientation) |
| `ArrowDown` | Moves focus to the next toolbar item (vertical orientation) |
| `ArrowUp` | Moves focus to the previous toolbar item (vertical orientation) |
| `Home` | Moves focus to the first toolbar item |
| `End` | Moves focus to the last toolbar item |
| `Space` / `Enter`| Activates the focused toolbar button or link |
### ARIA Attributes
- `Toolbar` renders with `role="toolbar"` by default via Base UI.
- `aria-orientation` is set to match the `orientation` prop.
- `aria-disabled` is set on the toolbar when `disabled` is `true`.
- Toolbar items are part of a single tab stop; arrow keys navigate between items within the toolbar.
- Focus management follows the roving tabindex pattern so only one item is tabbable at a time.
# Tooltip
> A tooltip that appears on hover built on Base UI
URL: https://prototyper-ui.com/docs/components/tooltip
Base UI reference: https://base-ui.com/react/components/tooltip
```tsx
import { Pencil1Icon } from "@radix-ui/react-icons"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
export default function TooltipDemo() {
return (
}>
Edit
)
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/tooltip.json
```
This will add the following files to your project:
- `components/ui/tooltip.tsx`
## Usage
```tsx
import {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipProvider,
} from "@/components/ui/tooltip"
Hover meTooltip text
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ------------------ | -------------------- | ---------------------------------------------- | -------- |
| `TooltipProvider` | `tooltip-provider` | Shared delay and configuration for tooltips | Yes |
| `Tooltip` | `tooltip` | Root provider, manages open/close state | Yes |
| `TooltipTrigger` | `tooltip-trigger` | Element that triggers the tooltip on hover | Yes |
| `TooltipContent` | `tooltip-content` | The popup displaying tooltip text | Yes |
## Examples
### Cross Offset
```tsx
import { ArrowRightIcon } from "@radix-ui/react-icons"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
export default function TooltipOffset() {
return (
}>
This will shift over to the right.
)
}
```
### Disabled
```tsx
import { Pencil1Icon } from "@radix-ui/react-icons"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
export default function TooltipDisabled() {
return (
}>
Edit
)
}
```
### Offset
```tsx
import { ArrowUpIcon } from "@radix-ui/react-icons"
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
export default function TooltipOffset() {
return (
}>
This will shift up.
)
}
```
### Position
```tsx
import { Button } from "@/components/ui/button"
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
export default function TooltipPosition() {
return (
}>
Left
Add to library
}>
Up
Add to library
}>
Down
Add to library
}>
Right
Add to library
)
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the tooltip:
| Slot name | Element |
| -------------------- | -------------------------------- |
| `tooltip-provider` | Shared provider (no DOM rendered)|
| `tooltip` | Root provider (no DOM rendered) |
| `tooltip-trigger` | The trigger element |
| `tooltip-content` | The popup panel |
| `tooltip-arrow` | Arrow element pointing to trigger|
### Customization Examples
```css
/* Change tooltip background */
[data-slot="tooltip-content"] {
@apply bg-primary text-primary-foreground;
}
/* Style the tooltip arrow */
[data-slot="tooltip-arrow"] {
@apply bg-primary fill-primary;
}
```
```tsx
{/* Override styles via className */}
This action is destructive
```
## API Reference
### TooltipProvider
Shared provider that configures delay and grouping behavior for all child tooltips.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `delay` | `number` | `0` | Delay in ms before tooltips appear |
All [Base UI Tooltip.Provider props](https://base-ui.com/react/components/tooltip) are forwarded via `...props`.
### Tooltip
Root component that manages open/close state for a single tooltip.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | - | Controlled open state |
| `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
| `defaultOpen` | `boolean` | `false` | Initial open state for uncontrolled usage |
All [Base UI Tooltip.Root props](https://base-ui.com/react/components/tooltip) are forwarded via `...props`.
### TooltipTrigger
Element that triggers the tooltip on hover and focus.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Tooltip.Trigger props](https://base-ui.com/react/components/tooltip) are forwarded via `...props`.
### TooltipContent
The popup panel displaying the tooltip text, rendered inside a portal with a positioner.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Preferred side relative to trigger |
| `sideOffset` | `number` | `8` | Gap between trigger and popup |
| `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment relative to the trigger |
| `alignOffset` | `number` | `0` | Offset from the alignment edge |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Tooltip content |
All [Base UI Tooltip.Popup props](https://base-ui.com/react/components/tooltip) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| --------- | ----------------------------------------------- |
| `Tab` | Moves focus to the trigger, showing the tooltip |
| `Escape` | Closes the tooltip |
### ARIA Attributes
- `TooltipContent` receives `role="tooltip"` via Base UI.
- The trigger element receives `aria-describedby` pointing to the tooltip content.
- Tooltips appear on both hover and focus, ensuring keyboard accessibility.
- The tooltip includes an arrow element for visual connection to the trigger.
- Screen readers announce tooltip content when the trigger receives focus.
# Design Principles
> Core principles that guide Prototyper UI's design and development.
URL: https://prototyper-ui.com/docs/design-principles
Prototyper UI follows a set of core principles that prioritize ownership, beauty, consistency, and developer experience. These principles shape every decision — from token naming to component API design.
## 1. You Own The Code
Components are files you copy into your project, read, edit, and extend. There is no hidden abstraction layer, no runtime CSS-in-JS, no provider you must wrap your app in. You install a component, it lands in your codebase, and it's yours.
This is the [shadcn philosophy](https://ui.shadcn.com/docs): the component library is a starting point, not a dependency.
```bash
# Install a component — it copies source into your project
npx shadcn@latest add https://prototyper-ui.com/r/button.json
```
```tsx
// You get a real file you can edit
// registry/ui/button.tsx
function Button({ className, variant, size, ...props }) {
return (
)
}
```
**Why this matters:** No version lock-in. No waiting for upstream fixes. No fighting abstraction layers to customize behavior. You change the file, you change the component.
## 2. Base UI Native
Every interactive component is built on [Base UI](https://base-ui.com) — the unstyled primitive library from the Material UI team. Base UI handles the hard problems (focus management, keyboard navigation, ARIA attributes, scroll locking, portal rendering) so we focus entirely on design.
```tsx
// Base UI provides the behavior, we provide the style
import { Select as SelectPrimitive } from "@base-ui/react/select"
function SelectTrigger({ className, children, ...props }) {
return (
{children}
} />
)
}
```
**What Base UI gives us for free:**
- WCAG 2.1 AA compliance across all components
- Keyboard navigation (arrow keys, type-ahead, focus trapping)
- Screen reader announcements via ARIA attributes
- Data attributes (`data-open`, `data-disabled`, `data-highlighted`) as a public styling contract
- Portal rendering for overlays
- Scroll lock for modals
## 3. Beautiful by Default
Components should look exceptional without any customization. The design system uses:
- **OKLCH color space** — perceptually uniform, `color-mix()` works predictably for derived states
- **Multi-layer shadows** — three stacked shadow layers simulate realistic light (subtle edge shadow is critical for dark mode)
- **Role-based surfaces** — surfaces, overlays, and fields each have dedicated tokens and shadow tiers
- **Fluid easing** — `cubic-bezier(0.32, 0.72, 0, 1)` as the signature curve (Apple-style deceleration)
- **Gradient buttons** — the default button uses a three-layer gradient system with primary color ramps
Beauty is not decoration — it's the result of precise tokens, consistent spacing, and careful shadow/color relationships.
## 4. Role-Based Surfaces
Inspired by [HeroUI v3's surface system](https://v3.heroui.com), surfaces are categorized by their **role in the UI**, not by arbitrary elevation numbers:
| Role | Token | Shadow | Components |
|------|-------|--------|-----------|
| **Surface** | `bg-surface` | `shadow-surface` | Cards, panels, tabs |
| **Overlay** | `bg-overlay` | `shadow-overlay` | Dialogs, popovers, menus, dropdowns |
| **Field** | `bg-field-background` | `shadow-field` | Inputs, selects, comboboxes |
**Light mode** uses shadows for depth. **Dark mode** uses background lightness stepping — shadows are reduced or zeroed because dark surfaces already show depth through tonal difference.
Three surface tiers handle nesting (card-in-card):
- `bg-surface` — primary surface
- `bg-surface-secondary` — nested containers (derived via `color-mix`)
- `bg-surface-tertiary` — deeper nesting (derived via `color-mix`)
## 5. CSS Utilities for Consistency
Every component references shared CSS utilities instead of reimplementing focus, disabled, and invalid patterns. This guarantees identical behavior everywhere:
```css
/* Focus ring — buttons, links, interactive elements */
@utility focus-ring {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
}
/* Focus ring — form fields (sits on border, no offset) */
@utility focus-field-ring {
outline: 2px solid var(--color-ring);
outline-offset: -1px;
}
/* Disabled state — universal across all components */
@utility status-disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
```
```tsx
// Every component uses the same utilities
```
**Why not per-component styles?** Because 5 different focus ring implementations (different widths, colors, offsets) is how inconsistency creeps in. One utility, one look, everywhere.
## 6. Flat Exports, shadcn-Compatible
Components use flat named exports — no `Object.assign` compound patterns, no dot notation. This is what shadcn users know and what LLMs generate best.
```tsx
// Named exports — clear, greppable, tree-shakeable
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue,
} from "@/components/ui/select"
```
Every component root and sub-component has a `data-slot` attribute for external targeting:
```css
/* Target any component part from outside */
[data-slot="select-trigger"] { /* ... */ }
[data-slot="select-content"] { /* ... */ }
```
## 7. Progressive Disclosure
Components work with minimal props and scale up as requirements grow. The simplest usage should be a single line; advanced usage reveals more knobs.
```tsx
// Level 1: Minimal — just works
// Level 2: Control what's visible
// Level 3: Full composition
```
## 8. CSS-First Animation
No JavaScript animation libraries. All transitions and animations use CSS, data attributes for state, and `prefers-reduced-motion` support.
```css
/* Overlay enter/exit via Tailwind animate utilities */
data-open:animate-in data-closed:animate-out
data-closed:fade-out-0 data-open:fade-in-0
data-closed:zoom-out-95 data-open:zoom-in-95
/* Interactive elements use transition-colors */
transition-colors duration-200
/* Easing tokens from the design system */
--ease-out-fluid: cubic-bezier(0.32, 0.72, 0, 1);
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
```
**Reduced motion is respected automatically.** All components use Tailwind's `motion-reduce:` variant which maps to both `prefers-reduced-motion` media query and `data-reduce-motion` attribute.
## 9. Type Safety
Full TypeScript with strict mode. Component props are derived from Base UI's type definitions, extended where needed:
```tsx
// Props extend Base UI types — full IntelliSense
function SelectTrigger({
className,
size = "default",
children,
...props
}: SelectPrimitive.Trigger.Props & {
size?: "sm" | "default" | "lg"
}) {
// ...
}
```
Variant types are generated by `class-variance-authority`:
```tsx
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva("...", {
variants: {
variant: { default: "...", destructive: "...", outline: "..." },
size: { default: "h-9", sm: "h-8", lg: "h-10" },
},
})
// VariantProps gives you { variant?: "default" | ... }
```
## 10. LLM-Friendly
Prototyper UI is designed to be generated correctly by AI. Every component follows the same patterns:
- **Consistent file structure:** `"use client"`, imports, component functions, exports
- **Consistent naming:** `ComponentName` + `ComponentNameSubPart` (e.g., `Select`, `SelectTrigger`, `SelectContent`)
- **Consistent props:** `className` + `...props` spread on every component
- **Consistent styling:** Tailwind utilities, `cn()` for merging, `data-slot` for identification
- **Rich examples:** Every component has multiple examples showing common patterns
- **Structured docs:** MDX pages with live previews, installation commands, and usage snippets
When an LLM reads one component, it understands all of them.
---
## Design Token Architecture
### Color Space: OKLCH
All colors use the OKLCH color space with `color-mix(in oklab)` for derived states:
```css
/* Base token (manually defined per theme) */
--primary: 39.11% 0.084 240.8;
/* Derived hover state (90% base + 10% foreground) */
--primary-hover: color-mix(in oklab, oklch(var(--primary)) 90%, oklch(var(--primary-foreground)) 10%);
/* Derived soft variant (15% opacity) */
--primary-soft: color-mix(in oklab, oklch(var(--primary)) 15%, transparent);
```
**Why OKLCH?** Perceptually uniform — a 10% mix shift looks like 10% regardless of the base color. HSL-based mixing produces unpredictable results across hues.
### Shadow System
Three semantic shadow tiers, mode-adaptive:
| Tier | Light Mode | Dark Mode | Used For |
|------|-----------|-----------|----------|
| `shadow-surface` | Multi-layer subtle shadow | None (tonal contrast instead) | Cards, panels |
| `shadow-field` | Subtle shadow + 1px edge | None | Form inputs |
| `shadow-overlay` | Heavy multi-layer shadow | Subtle 1px inset white glow | Popovers, menus, dialogs |
### What We Derive vs. Define Manually
| Manually defined | Derived via `color-mix()` |
|---|---|
| Base colors (`--primary`, `--destructive`, etc.) | Hover states (90% base + 10% foreground) |
| Foreground colors (`--primary-foreground`, etc.) | Soft variants (15% base + transparent) |
| Color ramps (`--primary-light/middle/dark`) | Surface tiers (secondary, tertiary) |
| Surface/overlay/field backgrounds | Border/separator progressions |
**Why not derive everything?** Tested with 5 brand colors — ramp derivation collapses for light colors, washes chroma for dark colors. Foreground can't be auto-derived (green needs dark text in both modes). Manual definition where it matters, derivation where it's safe.
---
## Comparison with Other Libraries
| Aspect | shadcn/ui | HeroUI v3 | Prototyper UI |
|--------|-----------|-----------|---------------|
| **Primitive library** | Radix UI | React Aria | Base UI |
| **Ownership model** | Copy-paste | Package dependency | Copy-paste |
| **Color space** | HSL | OKLCH | OKLCH |
| **Surface system** | Ad-hoc (3 tokens) | Role-based (surface/overlay/field) | Role-based (surface/overlay/field) |
| **Shadow system** | Size-based (sm/md/lg) | Semantic (surface/overlay) | Semantic (surface/field/overlay) |
| **Animation** | CSS + Tailwind | CSS + GPU accelerated | CSS + Tailwind |
| **Component API** | Flat exports | Compound (dot notation) | Flat exports |
| **Styling** | Tailwind-in-component | BEM + CSS layers | Tailwind-in-component |
| **Dark mode shadows** | Same as light | Zeroed / inset glow | Zeroed / inset glow |
# LLMs.txt
> Machine-readable documentation endpoints for AI assistants
URL: https://prototyper-ui.com/docs/for-agents/llms-txt
Prototyper UI exposes several plain-text endpoints optimized for LLM consumption, following the [llms.txt convention](https://llmstxt.org).
## Endpoints
| URL | Purpose |
|-----|---------|
| [`/llms.txt`](/llms.txt) | Index of all docs pages and components |
| [`/llms-full.txt`](/llms-full.txt) | Complete documentation — all pages in one file |
| [`/llms-components.txt`](/llms-components.txt) | All components with full TypeScript source and examples |
| `/llms/{slug}` | Individual page LLM text (e.g. [`/llms/button`](/llms/button)) |
| `/llms/components/{name}` | Component docs + source via full path (e.g. [`/llms/components/button`](/llms/components/button)) |
| [`/prototyper-tokens.css`](/prototyper-tokens.css) | Complete OKLCH design tokens as a downloadable CSS file |
The `/llms/{slug}` endpoint is a catch-all: any docs page slug works, not just component names.
## Usage
**Claude Code — reference in conversation:**
```
@https://prototyper-ui.com/llms.txt
```
**Claude Code — add to CLAUDE.md:**
```markdown
## UI Library
See @https://prototyper-ui.com/llms.txt for available components.
```
**Cursor — add to `.cursorrules`:**
```
@Docs https://prototyper-ui.com/llms-full.txt
```
**Windsurf — add to rules file:**
```
#docs https://prototyper-ui.com/llms-full.txt
```
**Direct fetch:**
```bash
curl https://prototyper-ui.com/llms/button
curl https://prototyper-ui.com/llms-components.txt
```
## Response Format
All endpoints return `text/plain; charset=utf-8`. Component endpoints include:
- Frontmatter (title, description, docs URL, Base UI reference link)
- Full MDX documentation converted to plain markdown
- All `` examples inlined as `tsx` code blocks
- Full TypeScript component source
- Additional examples not already shown in docs
# MCP Server
> Give your AI assistant structured access to Prototyper UI components via the Model Context Protocol
URL: https://prototyper-ui.com/docs/for-agents/mcp-server
The `@prototyperai/mcp-server` package provides structured access to components, docs, and design tokens via the [Model Context Protocol](https://modelcontextprotocol.io).
## Setup
### Claude Code
```bash
claude mcp add prototyper-ui -- npx -y @prototyperai/mcp-server@latest
```
Or add to `.mcp.json` in your project:
```json
{
"mcpServers": {
"prototyper-ui": {
"command": "npx",
"args": ["-y", "@prototyperai/mcp-server@latest"]
}
}
}
```
### Cursor
Add to `.cursor/mcp.json`:
```json
{
"mcpServers": {
"prototyper-ui": {
"command": "npx",
"args": ["-y", "@prototyperai/mcp-server@latest"]
}
}
}
```
### Windsurf
Add to `.windsurf/mcp.json`:
```json
{
"mcpServers": {
"prototyper-ui": {
"command": "npx",
"args": ["-y", "@prototyperai/mcp-server@latest"]
}
}
}
```
### VS Code
Add to `.vscode/mcp.json`:
```json
{
"servers": {
"prototyper-ui": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@prototyperai/mcp-server@latest"]
}
}
}
```
### Zed
Add to `.zed/settings.json`:
```json
{
"context_servers": {
"prototyper-ui": {
"command": {
"path": "npx",
"args": ["-y", "@prototyperai/mcp-server@latest"]
}
}
}
}
```
## Available Tools
| Tool | Description |
|------|-------------|
| `list_components` | List all available components with descriptions |
| `get_component_docs` | Full documentation, API reference, and usage examples |
| `get_component_source` | Full TypeScript source code for a component |
| `get_theme` | Complete OKLCH design tokens CSS |
| `search_docs` | Full-text search across all documentation |
## Resources
The server also exposes MCP resources for direct access:
| Resource URI | Content |
|-------------|---------|
| `prototyper://tokens/css` | Full design tokens CSS |
| `prototyper://docs/components/{name}` | Component documentation |
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `PROTOTYPER_UI_BASE_URL` | `https://prototyper-ui.com` | Override the base URL (useful for local development) |
## Local Development
Point the MCP server at your local docs instance:
```bash
PROTOTYPER_UI_BASE_URL=http://localhost:3333 npx @prototyperai/mcp-server@latest
```
# Agent Skills
> Install a Prototyper UI skill into Claude Code, Cursor, OpenCode, and more
URL: https://prototyper-ui.com/docs/for-agents/skills
Agent skills give AI assistants inline access to Prototyper UI patterns, component APIs, and examples — without needing to fetch from the web on every request.
## Install (all detected tools)
```bash
curl -fsSL https://prototyper-ui.com/install.sh | bash
```
This detects and installs to: **Claude Code**, **Cursor**, **OpenCode**, **Codex CLI**, **Gemini CLI**.
## Manual install — Claude Code
```bash
claude skill add https://prototyper-ui.com/install.sh
```
Or using the install script with a custom name:
```bash
curl -fsSL https://prototyper-ui.com/install.sh | bash -s -- my-prototyper-ui
```
## What's included
The skill bundle contains:
| File | Purpose |
|------|---------|
| `SKILL.md` | Main skill definition: component list, patterns, theming, and pitfalls |
| `scripts/list.mjs` | Fetch and display all available components |
| `scripts/docs.mjs` | Fetch full docs + source for one or more components |
| `scripts/source.mjs` | Fetch component source code only |
| `scripts/theme.mjs` | Fetch complete OKLCH design tokens |
## Usage after install
Once installed, run scripts from within the skill directory:
```bash
# List all components
node ~/.claude/skills/prototyper-ui/scripts/list.mjs
# Get button docs and source
node ~/.claude/skills/prototyper-ui/scripts/docs.mjs button
# Get multiple component docs at once
node ~/.claude/skills/prototyper-ui/scripts/docs.mjs button dialog select
# Get component source only
node ~/.claude/skills/prototyper-ui/scripts/source.mjs button
# Get design tokens
node ~/.claude/skills/prototyper-ui/scripts/theme.mjs
```
## Skill content
The `SKILL.md` covers:
- Why Prototyper UI uses `@base-ui/react` instead of Radix UI
- All 19 available components with descriptions
- Component patterns: `"use client"`, `data-slot`, CVA, `cn()`, compound exports
- OKLCH color system and design token categories
- CSS utility classes (`focus-ring`, `status-disabled`, etc.)
- Animation guidelines and easing values
- Common pitfalls and how to avoid them
- Installation and project setup
## Environment variable
Override the base URL to use a local docs server:
```bash
PROTOTYPER_UI_BASE_URL=http://localhost:3333 node scripts/docs.mjs button
```
# Introduction
> Beautiful components built on Base UI. Copy and paste into your apps.
URL: https://prototyper-ui.com/docs/index
## Prototyper UI
Prototyper UI is a collection of beautifully designed components built on [Base UI](https://base-ui.com). Components are styled with Tailwind CSS and can be installed via the shadcn CLI.
### Features
- Built on Base UI for accessible, unstyled primitives
- Styled with Tailwind CSS v4
- Dark mode support
- Themeable with CSS variables
- shadcn-compatible registry — install with `npx shadcn@latest add`
### Installation
Install components individually:
```bash
npx shadcn@latest add https://prototyper-ui.com/r/button.json
```
### Dependencies
Components are built with:
- [Base UI](https://base-ui.com) — Unstyled UI primitives
- [Tailwind CSS v4](https://tailwindcss.com) — Utility-first CSS
- [class-variance-authority](https://cva.style) — Variant management
- [tailwind-merge](https://github.com/dcastil/tailwind-merge) — Class merging
# Installation
> How to set up Prototyper UI in your Next.js project with Tailwind CSS v4 and Base UI.
URL: https://prototyper-ui.com/docs/installation
## Requirements
Prototyper UI components require the following:
- [React](https://react.dev) 19+
- [Next.js](https://nextjs.org) 15+
- [Tailwind CSS](https://tailwindcss.com) v4
- [Base UI](https://base-ui.com) (`@base-ui/react`)
## Quick Start
### Option A: Prototyper UI CLI (Recommended)
Set up your project and add components with the Prototyper UI CLI:
```bash
npx @prototyperai/cli init # tokens, utils, base styles
npx @prototyperai/cli add button # add components
```
Run `add` without arguments for an interactive component picker, or install everything at once:
```bash
npx @prototyperai/cli add --all
```
### Option B: shadcn CLI
If you already use shadcn, install any component directly from the registry:
```bash
npx shadcn@latest add https://prototyper-ui.com/r/button.json
```
This copies the component source into your project (typically `components/ui/button.tsx`) along with any required dependencies.
```bash
npx shadcn@latest add https://prototyper-ui.com/r/button.json https://prototyper-ui.com/r/dialog.json https://prototyper-ui.com/r/select.json
```
## Keeping Up to Date
```bash
npx @prototyperai/cli update # check for component updates
npx @prototyperai/cli doctor # verify project setup
```
## Manual Setup
If you prefer to set up manually or need to understand each step, follow this guide.
### 1. Install Dependencies
```bash
npm install @base-ui/react class-variance-authority clsx tailwind-merge
```
### 2. Add the `cn` Utility
Create `lib/utils.ts` in your project:
```ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
```
### 3. Set Up CSS Tokens
Add the design tokens to your `globals.css`. The token system has four layers:
**Theme registration** — maps CSS custom properties to Tailwind utilities:
```css
@theme {
/* Radius scale */
--radius-xl: calc(var(--radius) + 4px);
--radius-lg: var(--radius);
--radius-md: calc(var(--radius) - 2px);
--radius-sm: calc(var(--radius) - 4px);
/* Colors — each generates bg-*, text-*, border-* utilities */
--color-background: oklch(var(--background));
--color-foreground: oklch(var(--foreground));
--color-primary: oklch(var(--primary));
--color-primary-foreground: oklch(var(--primary-foreground));
/* ... see full token list in the Theming guide */
/* Shadows — three semantic tiers */
--shadow-surface: 0 2px 4px 0 oklch(0% 0 0 / 0.04), 0 1px 2px 0 oklch(0% 0 0 / 0.06), 0 0 0 1px oklch(0% 0 0 / 0.04);
--shadow-field: 0 1px 2px 0 oklch(0% 0 0 / 0.05), 0 0 0 1px oklch(0% 0 0 / 0.04);
--shadow-overlay: 0 8px 30px oklch(0% 0 0 / 0.12), 0 2px 8px oklch(0% 0 0 / 0.06), 0 0 0 1px oklch(0% 0 0 / 0.06);
/* Easing curves */
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
--ease-out-fluid: cubic-bezier(0.32, 0.72, 0, 1);
/* ... */
}
```
**Light mode tokens** (`:root`) — base OKLCH values for all colors, surfaces, and borders.
**Dark mode tokens** (`.dark`) — overrides for dark mode, including zeroed shadows where tonal contrast provides depth.
**CSS utilities** — shared focus, disabled, and invalid patterns used by every component:
```css
@utility focus-ring {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
}
@utility focus-field-ring {
outline: 2px solid var(--color-ring);
outline-offset: -1px;
}
@utility status-disabled {
opacity: 0.5;
pointer-events: none;
cursor: not-allowed;
}
```
For the complete `globals.css` file, see the [Theming](/docs/theming) guide.
### 4. Copy Component Files
Copy the component source from the registry into your `components/ui/` directory. Each component is a self-contained file that imports from `@base-ui/react` and `@/lib/utils`.
```tsx
// components/ui/button.tsx
"use client"
import { Button as ButtonPrimitive } from "@base-ui/react/button"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
// ... component implementation
```
## Token Overview
The design system organizes tokens into clear categories:
| Category | Examples | Purpose |
|----------|----------|---------|
| **Colors** | `--primary`, `--destructive`, `--success` | Brand and semantic colors (OKLCH values) |
| **Surfaces** | `--surface`, `--overlay`, `--field-background` | Role-based surface backgrounds |
| **Borders** | `--border`, `--border-light`, `--field-border` | Edge treatments at multiple weights |
| **Shadows** | `--shadow-surface`, `--shadow-field`, `--shadow-overlay` | Three semantic tiers, mode-adaptive |
| **Easings** | `--ease-smooth`, `--ease-out-fluid` | Animation timing functions |
| **Radius** | `--radius`, `--radius-sm`, `--radius-lg` | Border radius scale |
| **Derived** | `--primary-hover`, `--primary-soft` | Auto-computed via `color-mix()` |
## Dark Mode
Prototyper UI supports dark mode through the `.dark` class on the `` element. The recommended approach uses [next-themes](https://github.com/paisan-s/next-themes):
### Using next-themes
```bash
npm install next-themes
```
```tsx
// app/layout.tsx
import { ThemeProvider } from "next-themes"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
```
### How Dark Mode Works
The `.dark` class toggles all color tokens to their dark variants and adjusts the shadow system:
- **Colors** shift to darker backgrounds and lighter foregrounds
- **Shadows** are zeroed for `surface` and `field` (tonal contrast provides depth instead)
- **Overlay shadows** switch to a subtle white inset glow + deeper dark shadow
- **Derived colors** (`--primary-hover`, `--primary-soft`, etc.) auto-adapt via `color-mix()`
### Alternative: data-theme Attribute
You can also toggle dark mode via a `data-theme` attribute if you prefer:
```css
[data-theme="dark"] {
/* Same dark mode token overrides as .dark */
}
```
### System Preference
To respect the user's OS setting without JavaScript:
```css
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode token overrides */
}
}
```
The `next-themes` approach with `enableSystem` handles this automatically and avoids flash of wrong theme on load.
# Introduction
> A composable UI library built on Base UI with Tailwind CSS. Own every line of code.
URL: https://prototyper-ui.com/docs/introduction
## What is Prototyper UI?
Prototyper UI is a collection of beautifully designed, accessible components built on [Base UI](https://base-ui.com) and styled with [Tailwind CSS v4](https://tailwindcss.com). Components are copied into your project as source files — you own and control every line of code.
Install any component with a single command:
```bash
npx shadcn@latest add https://prototyper-ui.com/r/button.json
```
The component lands in your codebase as a real file you can read, edit, and extend. No runtime dependency, no version lock-in, no abstraction layer to fight.
## Design Principles
### Composable — Own the Code
Components follow the [shadcn model](https://ui.shadcn.com/docs): the library is a starting point, not a dependency. You install a component, it becomes your file. Need to change how a select renders its trigger? Open the file and change it.
### Beautiful by Default
The design system uses OKLCH color tokens, multi-layer shadows, role-based surfaces, and fluid easing curves. Components look exceptional without any customization. Beauty comes from precise tokens and consistent spacing — not decoration.
### Base UI Native
Every interactive component is built on [Base UI](https://base-ui.com), the unstyled primitive library from the Material UI team. Base UI handles focus management, keyboard navigation, ARIA attributes, scroll locking, and portal rendering. Prototyper UI handles design.
### LLM-Friendly
Every component follows identical patterns: `"use client"`, imports, component functions, exports. Consistent naming (`Select`, `SelectTrigger`, `SelectContent`), consistent props (`className` + `...props` spread), consistent styling (`cn()` + Tailwind + `data-slot`). When an LLM reads one component, it understands all of them.
## Comparison with Alternatives
| Feature | Prototyper UI | shadcn/ui | HeroUI | Radix Themes |
|---------|---------------|-----------|--------|--------------|
| **Primitive library** | Base UI | Radix UI | React Aria | Radix UI |
| **Styling** | Tailwind-in-component | Tailwind-in-component | Slots + Tailwind | CSS-in-JS |
| **Owns code** | Yes (copy-paste) | Yes (copy-paste) | No (npm) | No (npm) |
| **Color space** | OKLCH | HSL | HSL | Custom |
| **Surface system** | Role-based (surface/overlay/field) | Ad-hoc | Role-based | Ad-hoc |
| **Shadow system** | Semantic (surface/field/overlay) | Size-based (sm/md/lg) | Semantic | Size-based |
| **Component API** | Flat named exports | Flat named exports | Compound (dot notation) | Compound (dot notation) |
| **Dark mode shadows** | Zeroed / inset glow | Same as light | Zeroed / inset glow | Same as light |
## Architecture
Prototyper UI uses a **registry-based architecture**, compatible with the shadcn CLI:
```
Registry (prototyper-ui.com/r/*.json)
→ Component source files (registry/ui/*.tsx)
→ Your project (components/ui/*.tsx)
```
1. The **registry** hosts JSON manifests that describe each component, its source code, and its dependencies.
2. The **CLI** (`npx shadcn@latest add`) reads the manifest, resolves dependencies, and copies the component source into your project.
3. **Your project** owns the resulting files. Components import from `@/components/ui/*` and `@/lib/utils` — standard paths that work in any Next.js project.
### Key Dependencies
Every component builds on the same foundation:
- [**Base UI**](https://base-ui.com) (`@base-ui/react`) — Unstyled, accessible primitives
- [**Tailwind CSS v4**](https://tailwindcss.com) (`tailwindcss`) — Utility-first CSS framework
- [**class-variance-authority**](https://cva.style) (`class-variance-authority`) — Variant management for components
- [**tailwind-merge**](https://github.com/dcastil/tailwind-merge) (`tailwind-merge`) — Intelligent class merging
- [**clsx**](https://github.com/lukeed/clsx) (`clsx`) — Conditional class construction
### Utility Function
All components use a shared `cn()` utility that combines `clsx` and `tailwind-merge`:
```ts
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
```
This lets you safely merge and override Tailwind classes when composing components.
# Theming
> Customize colors, surfaces, and shadows using OKLCH tokens and CSS custom properties.
URL: https://prototyper-ui.com/docs/theming
Prototyper UI's design system is built on CSS custom properties using the OKLCH color space. Every color, surface, shadow, and easing curve is a token you can override.
## OKLCH Color System
All colors use the [OKLCH color space](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) — a perceptually uniform model where lightness, chroma, and hue are independent axes.
```css
/* OKLCH values: lightness chroma hue */
--primary: 39.11% 0.084 240.8;
```
### Why OKLCH?
- **Perceptually uniform** — a 10% lightness shift looks like 10% regardless of the base hue. HSL lightness is wildly inconsistent across colors.
- **Predictable color-mix()** — derived states (hover, soft) use `color-mix(in oklab)` and produce consistent results across the entire hue range.
- **Wide gamut** — OKLCH can represent P3 and Rec.2020 colors that HSL cannot.
### How Derived Colors Work
Hover states, soft variants, and surface tiers are computed automatically using `color-mix()`:
```css
/* Hover: 90% base + 10% paired foreground */
--primary-hover: color-mix(in oklab, oklch(var(--primary)) 90%, oklch(var(--primary-foreground)) 10%);
/* Soft variant: 15% base on transparent */
--primary-soft: color-mix(in oklab, oklch(var(--primary)) 15%, transparent);
/* Surface tiers: progressive foreground mixing */
--surface-secondary: color-mix(in oklab, oklch(var(--surface)) 94%, oklch(var(--surface-foreground)) 6%);
--surface-tertiary: color-mix(in oklab, oklch(var(--surface)) 88%, oklch(var(--surface-foreground)) 12%);
```
You only need to define the base color and its foreground — all interaction states derive automatically.
## Token Reference
### Base Colors
| Token | Description | Light Default |
|-------|-------------|---------------|
| `--background` | Page background | `100% 0 0` (white) |
| `--foreground` | Default text color | `14.05% 0.004 285.8` |
| `--radius` | Base border radius | `0.5rem` |
### Primary Ramp
| Token | Description | Light Default |
|-------|-------------|---------------|
| `--primary` | Primary brand color, buttons | `39.11% 0.084 240.8` |
| `--primary-foreground` | Text on primary backgrounds | `98.48% 0 0` |
| `--primary-light` | Gradient light stop | `75.84% 0.137 231.6` |
| `--primary-middle` | Gradient middle stop | `49.96% 0.118 242.2` |
| `--primary-dark` | Gradient dark stop | `39.20% 0.084 240.8` |
### Semantic Colors
| Token | Description |
|-------|-------------|
| `--secondary` / `--secondary-foreground` | Secondary actions, subtle buttons |
| `--muted` / `--muted-foreground` | Muted backgrounds, placeholder text |
| `--accent` / `--accent-foreground` | Accent highlights, hover backgrounds |
| `--destructive` / `--destructive-foreground` | Destructive actions, error states |
| `--success` / `--success-foreground` | Success states |
| `--warning` / `--warning-foreground` | Warning states |
| `--info` / `--info-foreground` | Informational states |
### Surfaces
| Token | Description |
|-------|-------------|
| `--surface` / `--surface-foreground` | Cards, panels, tabs |
| `--surface-secondary` | Nested containers (derived via `color-mix`) |
| `--surface-tertiary` | Deeper nesting (derived via `color-mix`) |
| `--overlay` / `--overlay-foreground` | Dialogs, popovers, menus |
| `--card` / `--card-foreground` | Card backgrounds |
| `--popover` / `--popover-foreground` | Popover backgrounds |
### Fields
| Token | Description |
|-------|-------------|
| `--field-background` | Input, select, combobox backgrounds |
| `--field-border` | Field border color |
| `--field-border-hover` | Field border on hover (derived) |
| `--field-border-invalid` | Field border on validation error (derived) |
### Borders
| Token | Description |
|-------|-------------|
| `--border` | Default border |
| `--border-light` | Subtle border (dividers, separators) |
| `--border-dark` | Emphasized border |
| `--input` | Input border (matches `--border`) |
| `--ring` | Focus ring color (matches `--primary`) |
### Shadows
| Token | Light Mode | Dark Mode |
|-------|-----------|-----------|
| `--shadow-surface` | Multi-layer subtle shadow | `none` |
| `--shadow-field` | Subtle shadow + 1px edge | `none` |
| `--shadow-overlay` | Heavy multi-layer shadow | 1px white inset glow + deep shadow |
### Easings
| Token | Value | Usage |
|-------|-------|-------|
| `--ease-smooth` | `cubic-bezier(0.4, 0, 0.2, 1)` | General transitions |
| `--ease-out-fluid` | `cubic-bezier(0.32, 0.72, 0, 1)` | Signature deceleration curve |
| `--ease-out-quad` | `cubic-bezier(0.25, 0.46, 0.45, 0.94)` | Subtle ease-out |
| `--ease-out-quart` | `cubic-bezier(0.165, 0.84, 0.44, 1)` | Pronounced ease-out |
| `--ease-in-quad` | `cubic-bezier(0.55, 0.085, 0.68, 0.53)` | Subtle ease-in |
| `--ease-in-quart` | `cubic-bezier(0.895, 0.03, 0.685, 0.22)` | Pronounced ease-in |
| `--ease-in-out-quad` | `cubic-bezier(0.455, 0.03, 0.515, 0.955)` | Symmetric ease |
### Derived Interaction Colors
These are auto-computed from base tokens — you typically do not override them:
| Token | Formula |
|-------|---------|
| `--primary-hover` | 90% `--primary` + 10% `--primary-foreground` |
| `--destructive-hover` | 90% `--destructive` + 10% `--destructive-foreground` |
| `--success-hover` | 90% `--success` + 10% `--success-foreground` |
| `--warning-hover` | 90% `--warning` + 10% `--warning-foreground` |
| `--accent-hover` | 92% `--accent` + 8% `--accent-foreground` |
| `--primary-soft` | 15% `--primary` on transparent |
| `--primary-soft-hover` | 20% `--primary` on transparent |
| `--destructive-soft` | 15% `--destructive` on transparent |
| `--destructive-soft-hover` | 20% `--destructive` on transparent |
## Customize Colors
Override any token in your `globals.css` to change the look of all components at once. Tokens use raw OKLCH values (lightness, chroma, hue) without the `oklch()` wrapper:
```css
:root {
/* Change primary to a teal */
--primary: 55% 0.15 180;
--primary-foreground: 98% 0 0;
--primary-light: 75% 0.12 175;
--primary-middle: 60% 0.14 178;
--primary-dark: 45% 0.13 182;
/* Update the focus ring to match */
--ring: 55% 0.15 180;
}
.dark {
--primary: 40% 0.10 180;
--primary-foreground: 95% 0.02 178;
--primary-light: 75% 0.12 175;
--primary-middle: 60% 0.14 178;
--primary-dark: 45% 0.13 182;
--ring: 70% 0.08 178;
}
```
All derived tokens (`--primary-hover`, `--primary-soft`, etc.) will auto-adapt because they use `color-mix()` with your new base values.
## Create a Custom Theme
Here is a full example of a custom theme with both light and dark modes. Copy this into your `globals.css` and adjust the values:
```css
:root {
/* Base */
--background: 100% 0 0;
--foreground: 14.05% 0.004 285.8;
--radius: 0.5rem;
/* Primary — violet example */
--primary: 50% 0.2 280;
--primary-foreground: 98% 0 0;
--primary-light: 75% 0.15 275;
--primary-middle: 58% 0.18 278;
--primary-dark: 42% 0.19 282;
/* Secondary */
--secondary: 96% 0.01 280;
--secondary-foreground: 25% 0.03 280;
/* Muted */
--muted: 96% 0 0;
--muted-foreground: 55% 0.01 285;
/* Accent */
--accent: 96% 0 0;
--accent-foreground: 21% 0.006 285;
/* Destructive */
--destructive: 63% 0.21 25;
--destructive-foreground: 98% 0 0;
/* Borders */
--border: 92% 0.004 286;
--border-light: 96% 0 0;
--border-dark: 80% 0.01 286;
--input: 92% 0.004 286;
--ring: 50% 0.2 280;
/* Surfaces */
--surface: 98% 0 0;
--surface-foreground: 14% 0.004 285;
--surface-secondary: color-mix(in oklab, oklch(var(--surface)) 94%, oklch(var(--surface-foreground)) 6%);
--surface-tertiary: color-mix(in oklab, oklch(var(--surface)) 88%, oklch(var(--surface-foreground)) 12%);
--overlay: 100% 0 0;
--overlay-foreground: 14% 0.004 285;
/* Cards & Popovers */
--card: 98% 0 0;
--card-foreground: 14% 0.004 285;
--popover: 100% 0 0;
--popover-foreground: 14% 0.004 285;
/* Fields */
--field-background: 100% 0 0;
--field-border: 92% 0.004 286;
--field-border-hover: color-mix(in oklab, oklch(var(--field-border)) 70%, oklch(var(--foreground)) 30%);
--field-border-invalid: oklch(var(--destructive));
/* Derived hover states — auto-adapt */
--primary-hover: color-mix(in oklab, oklch(var(--primary)) 90%, oklch(var(--primary-foreground)) 10%);
--destructive-hover: color-mix(in oklab, oklch(var(--destructive)) 90%, oklch(var(--destructive-foreground)) 10%);
--accent-hover: color-mix(in oklab, oklch(var(--accent)) 92%, oklch(var(--accent-foreground)) 8%);
/* Derived soft variants */
--primary-soft: color-mix(in oklab, oklch(var(--primary)) 15%, transparent);
--primary-soft-hover: color-mix(in oklab, oklch(var(--primary)) 20%, transparent);
--destructive-soft: color-mix(in oklab, oklch(var(--destructive)) 15%, transparent);
--destructive-soft-hover: color-mix(in oklab, oklch(var(--destructive)) 20%, transparent);
}
.dark {
/* Base */
--background: 14% 0.004 285;
--foreground: 98% 0 0;
/* Primary — violet dark mode */
--primary: 35% 0.14 282;
--primary-foreground: 95% 0.02 278;
--primary-light: 75% 0.15 275;
--primary-middle: 58% 0.18 278;
--primary-dark: 42% 0.19 282;
/* Secondary */
--secondary: 28% 0.03 280;
--secondary-foreground: 98% 0 0;
/* Muted */
--muted: 27% 0.006 286;
--muted-foreground: 71% 0.013 286;
/* Accent */
--accent: 27% 0.006 286;
--accent-foreground: 98% 0 0;
/* Destructive */
--destructive: 40% 0.13 26;
--destructive-foreground: 98% 0 0;
/* Borders */
--border: 32% 0.007 286;
--border-light: 37% 0.008 286;
--border-dark: 23% 0.004 286;
--input: 27% 0.006 286;
--ring: 70% 0.1 278;
/* Surfaces */
--surface: 16% 0.006 285;
--surface-foreground: 98% 0 0;
--surface-secondary: color-mix(in oklab, oklch(var(--surface)) 94%, oklch(var(--surface-foreground)) 6%);
--surface-tertiary: color-mix(in oklab, oklch(var(--surface)) 88%, oklch(var(--surface-foreground)) 12%);
--overlay: 20% 0.008 285;
--overlay-foreground: 98% 0 0;
/* Cards & Popovers */
--card: 16% 0.006 285;
--card-foreground: 98% 0 0;
--popover: 14% 0.004 285;
--popover-foreground: 98% 0 0;
/* Fields */
--field-background: 16% 0.006 285;
--field-border: 32% 0.007 286;
--field-border-hover: color-mix(in oklab, oklch(var(--field-border)) 70%, oklch(var(--foreground)) 30%);
--field-border-invalid: oklch(var(--destructive));
/* Derived — same formulas, auto-adapt to dark values */
--primary-hover: color-mix(in oklab, oklch(var(--primary)) 90%, oklch(var(--primary-foreground)) 10%);
--destructive-hover: color-mix(in oklab, oklch(var(--destructive)) 90%, oklch(var(--destructive-foreground)) 10%);
--accent-hover: color-mix(in oklab, oklch(var(--accent)) 92%, oklch(var(--accent-foreground)) 8%);
--primary-soft: color-mix(in oklab, oklch(var(--primary)) 15%, transparent);
--primary-soft-hover: color-mix(in oklab, oklch(var(--primary)) 20%, transparent);
--destructive-soft: color-mix(in oklab, oklch(var(--destructive)) 15%, transparent);
--destructive-soft-hover: color-mix(in oklab, oklch(var(--destructive)) 20%, transparent);
/* Shadows — tonal contrast replaces shadows */
--shadow-surface: none;
--shadow-field: none;
--shadow-overlay: 0 0 0 1px oklch(100% 0 0 / 0.08), 0 8px 30px oklch(0% 0 0 / 0.35);
}
```
### What to Define vs. What to Derive
| Define manually | Derived via `color-mix()` |
|-----------------|---------------------------|
| Base colors (`--primary`, `--destructive`, etc.) | Hover states (90% base + 10% foreground) |
| Foreground colors (`--primary-foreground`, etc.) | Soft variants (15% base + transparent) |
| Color ramps (`--primary-light/middle/dark`) | Surface tiers (secondary, tertiary) |
| Surface/overlay/field backgrounds | Border hover progressions |
Foreground colors cannot be auto-derived — green needs dark text in both modes, while blue needs light text in both modes. Ramp derivation (light/middle/dark stops) collapses for light colors and washes chroma for dark colors. Define where it matters, derive where it's safe.
## Runtime Theme Switching
### Toggle Dark Mode
Add or remove the `.dark` class on the `` element:
```ts
// Toggle dark mode
document.documentElement.classList.toggle("dark")
// Set explicitly
document.documentElement.classList.add("dark") // dark
document.documentElement.classList.remove("dark") // light
```
With [next-themes](https://github.com/paisan-s/next-themes), use the `useTheme` hook:
```tsx
import { useTheme } from "next-themes"
function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
setTheme(theme === "dark" ? "light" : "dark")}>
Toggle theme
)
}
```
### data-theme Attribute
You can also switch themes using a `data-theme` attribute, which is useful when supporting multiple named themes beyond light/dark:
```html
```
```css
[data-theme="dark"] {
--background: 14.05% 0.004 285.8;
--foreground: 98.48% 0 0;
/* ... all dark mode overrides */
}
```
### Swap Color Themes at Runtime
To switch between color themes (e.g., different primary colors) at runtime, apply CSS custom property overrides via a class or attribute:
```css
[data-theme="violet"] {
--primary: 50% 0.2 280;
--primary-foreground: 98% 0 0;
--primary-light: 75% 0.15 275;
--primary-middle: 58% 0.18 278;
--primary-dark: 42% 0.19 282;
--ring: 50% 0.2 280;
}
[data-theme="violet"].dark {
--primary: 35% 0.14 282;
--primary-foreground: 95% 0.02 278;
--ring: 70% 0.1 278;
}
```
```ts
// Switch color theme
document.documentElement.setAttribute("data-theme", "violet")
```
## Theme Builder
Use the interactive [Theme Builder](/themes) to preview color combinations and generate the CSS tokens for your custom theme.