` with ARIA tie | Yes |
## Examples
### Disabled
```tsx
import {
Checkbox,
CheckboxControl,
CheckboxIndicator,
} from "@/components/ui/checkbox";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
export default function FieldsetDisabled() {
return (
Notifications (disabled)
Email notifications
SMS notifications
);
}
```
### With Fields
```tsx
import { Field, FieldLabel, FieldDescription } from "@/components/ui/field";
import { Fieldset, FieldsetLegend } from "@/components/ui/fieldset";
import { Input } from "@/components/ui/input";
export default function FieldsetWithFields() {
return (
Shipping Address
Street
City
ZIP Code
5-digit ZIP code.
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the fieldset:
| Slot name | Element |
| ----------------- | ----------------------- |
| `fieldset` | Root fieldset container |
| `fieldset-legend` | Legend text |
### Customization Examples
```css
/* Tighter fieldset spacing */
[data-slot="fieldset"] {
@apply gap-4;
}
/* Style the legend as a smaller label */
[data-slot="fieldset-legend"] {
@apply text-sm text-muted-foreground;
}
```
```tsx
{
/* Label-sized legend variant */
}
Settings
{/* ... */}
;
```
## API Reference
### Fieldset
Root container that groups related form controls. Renders a `
` element.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `disabled` | `boolean` | `false` | Whether all controls within the fieldset are disabled |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Fieldset.Root props](https://base-ui.com/react/components/fieldset) are forwarded via `...props`.
### FieldsetLegend
Accessible legend that is automatically associated with the fieldset. Renders a `` element.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"legend" \| "label"` | `"legend"` | Size variant — "legend" for section headings, "label" for smaller inline labels |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Fieldset.Legend props](https://base-ui.com/react/components/fieldset) are forwarded via `...props`.
## Accessibility
### ARIA Attributes
- `Fieldset` renders a `
` element with native grouping semantics.
- `FieldsetLegend` renders a `` but is automatically associated with the fieldset via `aria-labelledby`.
- When `disabled` is set on the fieldset, all nested controls inherit the disabled state.
- Screen readers announce the legend as the group label when navigating into the fieldset.
## Full Component Source
```tsx
"use client";
export { Fieldset, FieldsetLegend } from "@prototyperco/ui/components/fieldset";
```
# Form
> form with consolidated error handling on Base UI
URL: https://prototyper-ui.com/docs/components/form
Base UI reference: https://base-ui.com/react/components/form
```tsx
"use client";
import { Field, FieldLabel, FieldDescription } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
export default function FormDemo() {
return (
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/form.json
```
This will add the following files to your project:
- `components/ui/form.tsx`
## Usage
```tsx
import { Form } from "@/components/ui/form";
;
```
## Examples
### Server Errors
```tsx
"use client";
import { useState } from "react";
import { Field, FieldLabel, FieldError } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
export default function FormServerErrors() {
const [errors, setErrors] = useState>({});
return (
);
}
```
### Validation Modes
```tsx
"use client";
import { Field, FieldLabel, FieldError } from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
export default function FormValidationModes() {
return (
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target the form:
| Slot name | Element |
| --------- | ----------------- |
| `form` | Root form element |
### Customization Examples
```css
/* Custom form layout */
[data-slot="form"] {
@apply gap-4;
}
```
```tsx
{
/* Override gap spacing */
}
;
```
## API Reference
### Form
Root form element with consolidated error handling. Renders a `
# FormField
> A react-hook-form helper that wires Field, label, description, and error to a Controller in one component
URL: https://prototyper-ui.com/docs/components/form-field
```tsx
"use client";
import { useForm, FormProvider } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormField } from "@/components/ui/form-field";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
const schema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Please enter a valid email"),
message: z.string().min(10, "Message must be at least 10 characters"),
});
type FormValues = z.infer;
export default function FormFieldDemo() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { name: "", email: "", message: "" },
});
const onSubmit = (data: FormValues) => {
alert(JSON.stringify(data, null, 2));
};
return (
Submit
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/form-field.json
```
This will add the following files to your project:
- `components/ui/form-field.tsx`
> **Peer dependency:** This component requires `react-hook-form`. Install it alongside:
>
> ```bash
> npm install react-hook-form @hookform/resolvers zod
> ```
> **Note:** This component depends on [Field](/docs/components/field). It will be installed automatically.
## Usage
```tsx
import { useForm, FormProvider } from "react-hook-form"
import { FormField } from "@/components/ui/form-field"
import { Input } from "@/components/ui/input"
const form = useForm({ defaultValues: { email: "" } })
```
`FormField` uses `Controller` + `useFormContext` internally, so it must be rendered inside a ``. It auto-wires `value`, `onChange`, `onBlur`, and `ref` to the child input via `cloneElement`.
### When to use FormField vs raw Field
| Use `FormField` | Use raw `Field` + `Controller` |
| ------------------------------------- | ------------------------------------------------------- |
| Standard text inputs, textareas | Select, NumberField, Checkbox, Switch, RadioGroup |
| Quick forms with minimal boilerplate | Components that use `onValueChange` / `onCheckedChange` |
| Inputs accepting `value` + `onChange` | Custom controlled components |
For components with non-standard change handlers, use `Controller` directly with `Field` as shown in the [Forms guide](/docs/forms#react-hook-form-integration).
## Examples
### Validation
```tsx
"use client";
import { useForm, FormProvider } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormField } from "@/components/ui/form-field";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const schema = z
.object({
username: z
.string()
.min(3, "Username must be at least 3 characters")
.max(20, "Username must be at most 20 characters")
.regex(
/^[a-z0-9_]+$/,
"Only lowercase letters, numbers, and underscores",
),
email: z.string().email("Please enter a valid email address"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.regex(/[A-Z]/, "Must contain at least one uppercase letter")
.regex(/[0-9]/, "Must contain at least one number"),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwords do not match",
path: ["confirmPassword"],
});
type FormValues = z.infer;
export default function FormFieldValidation() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: {
username: "",
email: "",
password: "",
confirmPassword: "",
},
});
const onSubmit = (data: FormValues) => {
alert(JSON.stringify(data, null, 2));
};
return (
Create Account
);
}
```
### All Input Types
```tsx
"use client";
import * as React from "react";
import { useForm, FormProvider, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormField } from "@/components/ui/form-field";
import { Field, FieldLabel, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select";
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import {
Checkbox,
CheckboxControl,
CheckboxIndicator,
} from "@/components/ui/checkbox";
import { Switch, SwitchTrack, SwitchThumb } from "@/components/ui/switch";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Button } from "@/components/ui/button";
const schema = z.object({
name: z.string().min(1, "Name is required"),
bio: z.string().optional(),
role: z.string().min(1, "Please select a role"),
age: z.number().min(18, "Must be at least 18"),
terms: z.boolean().refine((v) => v, "You must accept the terms"),
notifications: z.boolean().optional(),
plan: z.string().min(1, "Please select a plan"),
});
type FormValues = z.infer;
export default function FormFieldAllInputs() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: "",
bio: "",
role: "",
age: 25,
terms: false,
notifications: true,
plan: "",
},
});
const onSubmit = (data: FormValues) => {
alert(JSON.stringify(data, null, 2));
};
return (
{/* TextField — works directly with FormField */}
{/* Textarea — works directly with FormField */}
{/* Select — use Controller for onValueChange mapping */}
(
Role
Developer
Designer
Manager
{fieldState.error?.message}
)}
/>
{/* NumberField — use Controller for onValueChange mapping */}
(
Age
{fieldState.error?.message}
)}
/>
{/* Checkbox — use Controller for checked/onCheckedChange */}
(
I accept the terms and conditions
{fieldState.error?.message}
)}
/>
{/* Switch — use Controller for checked/onCheckedChange */}
(
Email Notifications
)}
/>
{/* RadioGroup — use Controller for onValueChange */}
(
Plan
Free
Pro
Enterprise
{fieldState.error?.message}
)}
/>
Submit
);
}
```
### Dynamic Fields
```tsx
"use client";
import { useForm, FormProvider, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormField } from "@/components/ui/form-field";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Plus, Trash2 } from "lucide-react";
const schema = z.object({
teamName: z.string().min(1, "Team name is required"),
members: z
.array(
z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
}),
)
.min(1, "Add at least one member"),
});
type FormValues = z.infer;
export default function FormFieldDynamic() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: {
teamName: "",
members: [{ name: "", email: "" }],
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "members",
});
const onSubmit = (data: FormValues) => {
alert(JSON.stringify(data, null, 2));
};
return (
Team Members
{fields.map((field, index) => (
))}
append({ name: "", email: "" })}
>
Add Member
Create Team
);
}
```
### Multi-Step Form
```tsx
"use client";
import * as React from "react";
import { useForm, FormProvider } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { FormField } from "@/components/ui/form-field";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
const step1Schema = z.object({
firstName: z.string().min(1, "First name is required"),
lastName: z.string().min(1, "Last name is required"),
email: z.string().email("Please enter a valid email"),
});
const step2Schema = z.object({
company: z.string().min(1, "Company is required"),
role: z.string().min(1, "Role is required"),
});
const step3Schema = z.object({
message: z.string().min(10, "Message must be at least 10 characters"),
});
const fullSchema = step1Schema.merge(step2Schema).merge(step3Schema);
type FormValues = z.infer;
const stepSchemas = [step1Schema, step2Schema, step3Schema] as const;
const stepFields: (keyof FormValues)[][] = [
["firstName", "lastName", "email"],
["company", "role"],
["message"],
];
export default function FormFieldMultiStep() {
const [step, setStep] = React.useState(0);
const form = useForm({
resolver: zodResolver(fullSchema),
defaultValues: {
firstName: "",
lastName: "",
email: "",
company: "",
role: "",
message: "",
},
mode: "onTouched",
});
const goNext = async () => {
const fields = stepFields[step];
const valid = await form.trigger(fields);
if (valid) setStep((s) => s + 1);
};
const goBack = () => setStep((s) => s - 1);
const onSubmit = (data: FormValues) => {
alert(JSON.stringify(data, null, 2));
};
return (
{stepSchemas.map((_, i) => (
))}
Step {step + 1} of {stepSchemas.length}
{step === 0 && (
)}
{step === 1 && (
)}
{step === 2 && (
)}
{step > 0 && (
Back
)}
{step < stepSchemas.length - 1 ? (
Continue
) : (
Submit
)}
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the component:
| Slot name | Element |
| ------------------- | ----------------------- |
| `form-field` | Root `Field` wrapper |
| `field-label` | Label element |
| `field-description` | Description paragraph |
| `field-error` | Error message container |
### Customization Examples
```css
/* Increase spacing between form fields */
[data-slot="form-field"] {
@apply gap-2;
}
```
```tsx
{
/* Override width via className */
}
;
```
## API Reference
### FormField
A helper component that wires a `Controller` to `Field` + `FieldLabel` + `FieldDescription` + `FieldError`.
| Prop | Type | Default | Description |
| ------------- | -------------------- | ------- | ---------------------------------------- |
| `name` | `string` | - | Field name matching the form schema |
| `label` | `string` | - | Label text displayed above the input |
| `description` | `string` | - | Help text displayed below the label |
| `required` | `boolean` | - | Shows a required indicator on the label |
| `className` | `string` | - | Additional CSS classes on the root Field |
| `children` | `React.ReactElement` | - | The input component to wire up |
## Accessibility
### Keyboard Interactions
| Key | Action |
| ----- | ----------------------------------------- |
| `Tab` | Moves focus to the input within the field |
### ARIA Attributes
- The `Field` wrapper renders with `role="group"`.
- `FieldLabel` is associated with the input via Base UI's label mechanism.
- `FieldError` renders with `role="alert"` and `aria-live="polite"` for screen reader announcements.
- When `invalid` is true, `data-invalid` is set on the field for styling hooks.
## Full Component Source
```tsx
"use client";
import * as React from "react";
import {
Controller,
useFormContext,
type FieldValues,
type Path,
type FieldError as RHFFieldError,
} from "react-hook-form";
import { cn } from "@/lib/utils";
import {
Field,
FieldLabel,
FieldDescription,
FieldError,
} from "@/components/ui/field";
interface FormFieldProps {
name: Path;
label: string;
description?: string;
required?: boolean;
className?: string;
children: React.ReactElement;
}
function FormField({
name,
label,
description,
required,
className,
children,
}: FormFieldProps) {
const { control } = useFormContext();
return (
{
const error = fieldState.error as RHFFieldError | undefined;
return (
{label}
{description && {description} }
{React.cloneElement(children, { ...field })}
{error?.message}
);
}}
/>
);
}
export { FormField };
export type { FormFieldProps };
```
# Input
> a styled text input field built on Base UI
URL: https://prototyper-ui.com/docs/components/input
Base UI reference: https://base-ui.com/react/components/input
```tsx
import { Input } from "@/components/ui/input";
export default function InputDemo() {
return ;
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/input.json
```
This will add the following files to your project:
- `components/ui/input.tsx`
## Usage
```tsx
import { Input } from "@/components/ui/input";
;
```
## Styling
### Data Slots
Use `data-slot` attributes to target the input in CSS:
| Slot name | Element |
| --------- | ------------------ |
| `input` | The ` ` root |
### Customization Examples
```css
/* Make all inputs taller */
[data-slot="input"] {
@apply h-12 text-lg;
}
```
```tsx
{
/* Override styles via className */
}
;
```
## API Reference
### Input
A styled text input built on Base UI `Input`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `string` | - | HTML input type (e.g. "text", "email", "password") |
| `className` | `string` | - | Additional CSS classes |
All native `input` props and [Base UI Input props](https://base-ui.com/react/components/input) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ----- | ------------------------------------ |
| `Tab` | Moves focus into or out of the input |
### ARIA Attributes
- Renders as a native ` ` element via Base UI `Input`.
- `aria-invalid` styling is applied when the input has validation errors.
- Pair with a `` or `aria-label` to ensure the input is accessible to screen readers.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="input.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A text input field for user data entry",
props: z.object({
label: z.string().optional().describe("Label displayed above the input"),
name: z.string().optional().describe("Field name for form submission"),
placeholder: z.string().optional().describe("Placeholder text"),
type: z
.enum(["text", "email", "password", "number", "search", "tel", "url"])
.optional()
.describe("Input type"),
disabled: z.boolean().optional().describe("Whether the input is disabled"),
readOnly: z.boolean().optional().describe("Whether the input is read-only"),
value: z.string().optional().describe("Controlled input value"),
}),
events: ["change", "focus", "blur", "submit"],
example: { label: "Name", placeholder: "Enter your name...", type: "text" },
});
```
### Example Spec
```json
{
"root": "nameInput",
"elements": {
"nameInput": {
"type": "Input",
"props": {
"label": "Name",
"placeholder": "Enter your name...",
"type": "text"
},
"$bindState": { "path": "/name", "event": "change" }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export { Input } from "@prototyperco/ui/components/input";
```
# InputGroup
> a composable input group with addons, buttons, and text
URL: https://prototyper-ui.com/docs/components/input-group
```tsx
import { ArrowRight } from "lucide-react";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
export default function InputGroupDemo() {
return (
Subscribe
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/input-group.json
```
This will add the following files to your project:
- `components/ui/input-group.tsx`
> **Note:** This component depends on [Button](/docs/components/button), [Input](/docs/components/input), and Textarea. They will be installed automatically.
## Usage
```tsx
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupInput,
} from "@/components/ui/input-group";
Go
;
```
## Anatomy
```tsx
```
Or with a textarea:
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| -------------------- | --------------------- | ------------------------------------------------ | -------- |
| `InputGroup` | `input-group` | Root wrapper, provides group styling context | Yes |
| `InputGroupAddon` | `input-group-addon` | Container for icons, text, buttons, or kbd hints | No |
| `InputGroupButton` | - | A compact button styled for use inside the group | No |
| `InputGroupText` | - | Inline text or icon container | No |
| `InputGroupInput` | `input-group-control` | Single-line text input (borderless) | Yes\* |
| `InputGroupTextarea` | `input-group-control` | Multi-line textarea (borderless) | Yes\* |
\* Use either `InputGroupInput` or `InputGroupTextarea`, not both.
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the input group:
| Slot name | Element |
| --------------------- | -------------------------------- |
| `input-group` | Root wrapper `` |
| `input-group-addon` | Addon container (icons, buttons) |
| `input-group-control` | The input or textarea element |
### Customization Examples
```css
/* Remove border from all input groups */
[data-slot="input-group"] {
@apply border-0 shadow-none;
}
/* Style addons */
[data-slot="input-group-addon"] {
@apply text-foreground;
}
```
```tsx
{
/* Override styles via className */
}
;
```
## API Reference
### InputGroup
Root wrapper that provides grouped styling and focus management.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Group content (addons, input, buttons) |
All native `div` props are forwarded via `...props`.
### InputGroupAddon
Container for icons, text, buttons, or keyboard shortcut hints.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"inline-start" \| "inline-end" \| "block-start" \| "block-end"` | `"inline-start"` | Position of the addon relative to the input |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Addon content (icons, text, buttons) |
All native `div` props are forwarded via `...props`.
### InputGroupButton
A compact button styled for use inside the input group. Extends [Button](/docs/components/button) props.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"xs" \| "sm" \| "icon-xs" \| "icon-sm"` | `"xs"` | Size of the button |
| `variant` | `ButtonProps["variant"]` | `"ghost"` | Visual style variant (from Button) |
| `type` | `"button" \| "submit" \| "reset"` | `"button"` | HTML button type |
| `className` | `string` | - | Additional CSS classes |
All [Button](/docs/components/button) props (except `size` and `type`) are forwarded via `...props`.
### InputGroupText
Inline text or icon container for display content inside the group.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Text or icon content |
All native `span` props are forwarded via `...props`.
### InputGroupInput
A borderless single-line input styled for use inside the group. Built on [Input](/docs/components/input).
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All native `input` props are forwarded via `...props`.
### InputGroupTextarea
A borderless multi-line textarea styled for use inside the group. Built on Textarea.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All native `textarea` props are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ----- | --------------------------------------------- |
| `Tab` | Moves focus into or out of the input/textarea |
### ARIA Attributes
- `InputGroup` renders with `role="group"` to associate the input with its addons.
- `InputGroupAddon` renders with `role="group"` for semantic grouping.
- Clicking an addon area focuses the input for better usability.
- Pair with a `
` or `aria-label` on the input to ensure accessibility.
## Full Component Source
```tsx
"use client";
export {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupText,
InputGroupInput,
InputGroupTextarea,
} from "@prototyperco/ui/components/input-group";
```
# Label
> a form label with disabled and invalid state styling
URL: https://prototyper-ui.com/docs/components/label
Label renders a styled native `` element for form controls. It picks up disabled and invalid state from a surrounding `Field` automatically, so you rarely have to wire styling by hand — the label dims when the input is disabled and turns red when the field is marked invalid. Use it for every form input you ship, both for accessibility and because users rely on visible labels far more than placeholder text alone.
```tsx
import { Label } from "@/components/ui/label";
export default function LabelDemo() {
return Email address ;
}
```
### When to use
- Any time a form control needs a visible, persistent name — inputs, selects, switches, checkboxes, radio groups.
- When you want disabled and invalid styling to flow from a parent `Field` without writing conditional class names.
- When you need a clickable target that focuses or toggles the associated control, since the native `` element handles that for free.
Avoid using `Label` purely as decorative text — for headings, callouts, or section titles, reach for a semantic heading element instead so screen readers do not announce it as a form label.
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/label.json
```
This will add the following files to your project:
- `components/ui/label.tsx`
## Usage
```tsx
import { Label } from "@/components/ui/label";
```
```tsx
Email address
```
## Examples
### With Input
```tsx
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function LabelWithInput() {
return (
Email
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target the label in CSS:
| Slot name | Element |
| --------- | ------------------ |
| `label` | The `` root |
### Customization Examples
```css
/* Make all labels uppercase */
[data-slot="label"] {
@apply text-xs uppercase tracking-wide;
}
```
```tsx
Custom label
```
## API Reference
### Label
A styled `` element for form controls.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes. |
| `children` | `React.ReactNode` | - | Label content. |
All standard `label` element props are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
Label is a non-interactive display element and does not have keyboard interactions. Clicking a label focuses or activates its associated form control via the `htmlFor` attribute or nesting.
### ARIA Attributes
- Renders as a native `` element, so assistive technology announces it as the accessible name of the associated control automatically.
- Associate with a form control either by setting `htmlFor` to the control's `id`, or by nesting the control inside the label. Either pattern works; pick the one that fits your layout.
- Automatically receives `data-disabled` and `data-invalid` styling from parent `Field` or group contexts, so you do not need to mirror state on the label itself.
- If you need a hidden label for an input that has a visible icon or placeholder, prefer `aria-label` on the input over a visually-hidden Label — it keeps the markup simpler and avoids stale labels.
## Full Component Source
```tsx
"use client";
export { Label } from "@prototyperco/ui/components/label";
```
# Menu
> dropdown menu with submenus and selection built on base ui
URL: https://prototyper-ui.com/docs/components/menu
Base UI reference: https://base-ui.com/react/components/menu
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuDemo() {
return (
}
>
☰
alert("open")}>Open
alert("rename")}>
Rename…
alert("duplicate")}>
Duplicate
alert("share")}>
Share…
alert("delete")}>
Delete…
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/menu.json
```
This will add the following files to your project:
- `components/ui/menu.tsx`
## Usage
```tsx
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from "@/components/ui/menu";
Open Menu
Item 1
Item 2
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| -------------------------- | ----------------------------- | --------------------------------------- | -------- |
| `DropdownMenu` | `dropdown-menu` | Root provider, manages open/close state | Yes |
| `DropdownMenuTrigger` | `dropdown-menu-trigger` | Button that opens the menu | Yes |
| `DropdownMenuPortal` | `dropdown-menu-portal` | Renders children into a portal | No |
| `DropdownMenuContent` | `dropdown-menu-content` | The popup panel containing menu items | Yes |
| `DropdownMenuGroup` | `dropdown-menu-group` | Groups related menu items | No |
| `DropdownMenuLabel` | `dropdown-menu-label` | Non-interactive label for a group | No |
| `DropdownMenuItem` | `dropdown-menu-item` | Individual actionable menu item | Yes |
| `DropdownMenuCheckboxItem` | `dropdown-menu-checkbox-item` | Toggleable checkbox menu item | No |
| `DropdownMenuRadioGroup` | `dropdown-menu-radio-group` | Groups radio menu items | No |
| `DropdownMenuRadioItem` | `dropdown-menu-radio-item` | Radio-selectable menu item | No |
| `DropdownMenuSeparator` | `dropdown-menu-separator` | Visual separator between items | No |
| `DropdownMenuShortcut` | `dropdown-menu-shortcut` | Keyboard shortcut hint text | No |
| `DropdownMenuSub` | `dropdown-menu-sub` | Root for a submenu | No |
| `DropdownMenuSubTrigger` | `dropdown-menu-sub-trigger` | Item that opens a submenu | No |
| `DropdownMenuSubContent` | `dropdown-menu-sub-content` | Popup panel for a submenu | No |
## Examples
### Content
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuContent() {
const items = [
{ id: 1, name: "New" },
{ id: 2, name: "Open" },
{ id: 3, name: "Close" },
{ id: 4, name: "Save" },
{ id: 5, name: "Duplicate" },
{ id: 6, name: "Rename" },
{ id: 7, name: "Move" },
];
return (
}>
Actions
{items.map((item) => (
{item.name}
))}
);
}
```
### Disabled Items
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuDisabledItems() {
return (
}>
Actions
Copy
Cut
Paste
);
}
```
### Disabled Keys
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuDisabledKeys() {
const items = [
{ id: 1, name: "New" },
{ id: 2, name: "Open" },
{ id: 3, name: "Close" },
{ id: 4, name: "Save" },
{ id: 5, name: "Duplicate" },
{ id: 6, name: "Rename" },
{ id: 7, name: "Move" },
];
const disabledKeys = [4, 6];
return (
}>
Actions
{items.map((item) => (
{item.name}
))}
);
}
```
### Links
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuLinks() {
return (
}>
Links
Adobe
Apple
Google
Microsoft
);
}
```
### Long Press
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuLongPress() {
return (
alert("crop")}
onContextMenu={(e: React.MouseEvent) => e.preventDefault()}
/>
}
>
Crop
Rotate
Slice
Clone stamp
);
}
```
### Reusable
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
function ProtoMenu({
label,
variant = "outline",
children,
}: {
label: string;
variant?:
| "outline"
| "default"
| "ghost"
| "secondary"
| "destructive"
| "link";
children: React.ReactNode;
}) {
return (
}>
{label}
{children}
);
}
export default function MenuReusable() {
return (
Cut
Copy
Paste
Delete Item
);
}
```
### Sections
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuSections() {
return (
}>
Actions
Styles
Bold
Underline
Align
Left
Middle
Right
);
}
```
### Sections Dynamic
```tsx
"use client";
import React from "react";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuSectionsDynamic() {
const [selected, setSelected] = React.useState>({
1: true,
3: true,
});
const openWindows = [
{
name: "Left Panel",
id: "left",
children: [{ id: 1, name: "Final Copy (1)" }],
},
{
name: "Right Panel",
id: "right",
children: [
{ id: 2, name: "index.ts" },
{ id: 3, name: "package.json" },
{ id: 4, name: "license.txt" },
],
},
];
const handleCheckedChange = (id: number, checked: boolean) => {
setSelected((prev) => ({ ...prev, [id]: checked }));
};
return (
}>
Window
{openWindows.map((section) => (
{section.name}
{section.children.map((item) => (
handleCheckedChange(item.id, checked)
}
>
{item.name}
))}
))}
);
}
```
### Selection Multiple
```tsx
"use client";
import React from "react";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuSelectionMultiple() {
const [sidebar, setSidebar] = React.useState(true);
const [searchbar, setSearchbar] = React.useState(false);
const [tools, setTools] = React.useState(false);
const [console, setConsole] = React.useState(true);
return (
}>
View
Sidebar
Searchbar
Tools
Console
);
}
```
### Selection Single
```tsx
"use client";
import React from "react";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuSelectionSingle() {
const [selected, setSelected] = React.useState("center");
return (
}>
Align
Left
Center
Right
);
}
```
### Separators
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuSeparators() {
return (
}>
Actions
New…
Open…
Save
Save as…
Rename…
Page setup…
Print…
);
}
```
### Sub Menu
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuSubMenus() {
return (
}>
Actions
Copy
Cut
Delete
Share
SMS
Twitter
Email
Work
Personal
);
}
```
### Sub Menu Dynamic
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
type MenuItem = {
id: string;
name: string;
children?: MenuItem[];
};
export default function MenuSubMenuDynamic() {
const items: MenuItem[] = [
{ id: "cut", name: "Cut" },
{ id: "copy", name: "Copy" },
{ id: "delete", name: "Delete" },
{
id: "share",
name: "Share",
children: [
{ id: "sms", name: "SMS" },
{ id: "twitter", name: "Twitter" },
{
id: "email",
name: "Email",
children: [
{ id: "work", name: "Work" },
{ id: "personal", name: "Personal" },
],
},
],
},
];
function renderSubmenu(item: MenuItem) {
if (item.children) {
return (
{item.name}
{item.children.map((child) => renderSubmenu(child))}
);
} else {
return {item.name} ;
}
}
return (
}>
Actions
{items.map((item) => renderSubmenu(item))}
);
}
```
### Text Slots
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from "@/components/ui/menu";
import { Button } from "@/components/ui/button";
export default function MenuTextSlots() {
return (
}>
Actions
Copy
Copy the selected text
⌘C
Cut
Cut the selected text
⌘X
Paste
Paste the copied text
⌘V
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the menu:
| Slot name | Element |
| ----------------------------- | ------------------------------- |
| `dropdown-menu` | Root provider (no DOM rendered) |
| `dropdown-menu-trigger` | The trigger button |
| `dropdown-menu-portal` | Portal wrapper |
| `dropdown-menu-content` | The popup panel |
| `dropdown-menu-group` | Group wrapper |
| `dropdown-menu-label` | Group label |
| `dropdown-menu-item` | Individual menu item |
| `dropdown-menu-checkbox-item` | Checkbox menu item |
| `dropdown-menu-radio-group` | Radio group wrapper |
| `dropdown-menu-radio-item` | Radio menu item |
| `dropdown-menu-separator` | Visual separator line |
| `dropdown-menu-shortcut` | Keyboard shortcut text |
| `dropdown-menu-sub` | Submenu root |
| `dropdown-menu-sub-trigger` | Submenu trigger item |
| `dropdown-menu-sub-content` | Submenu popup panel |
### Customization Examples
```css
/* Change menu item highlight color */
[data-slot="dropdown-menu-item"][data-highlighted] {
@apply bg-primary text-primary-foreground;
}
/* Wider menu popup */
[data-slot="dropdown-menu-content"] {
@apply min-w-[12rem];
}
```
```tsx
{
/* Override styles via className */
}
Custom Item
;
```
## API Reference
### DropdownMenu
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 |
All [Base UI Menu.Root props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuTrigger
Button that opens the menu when clicked.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.Trigger props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuContent
The popup panel containing menu items, rendered inside a portal with a positioner.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"start"` | 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` | - | Menu content |
All [Base UI Menu.Popup props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuGroup
Groups related menu items together.
All [Base UI Menu.Group props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuLabel
Non-interactive label for a group of menu items.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.GroupLabel props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuItem
Individual actionable menu item.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "destructive"` | `"default"` | Visual style variant |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.Item props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuCheckboxItem
Toggleable checkbox menu item with a check indicator.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | - | Whether the item is checked |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Item content |
All [Base UI Menu.CheckboxItem props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuRadioGroup
Groups radio menu items for single-selection behavior.
All [Base UI Menu.RadioGroup props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuRadioItem
Radio-selectable menu item within a radio group.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Item content |
All [Base UI Menu.RadioItem props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuSeparator
Visual separator between menu items or groups.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.Separator props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuShortcut
Displays a keyboard shortcut hint aligned to the right of a menu item.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All standard `span` props are forwarded via `...props`.
### DropdownMenuSub
Root provider for a submenu.
All [Base UI Menu.SubmenuRoot props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuSubTrigger
Menu item that opens a submenu on hover or keyboard navigation.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Trigger content |
All [Base UI Menu.SubmenuTrigger props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### DropdownMenuSubContent
Popup panel for a submenu, rendered inside a portal with a positioner.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment relative to the trigger |
| `alignOffset` | `number` | `-3` | Offset from the alignment edge |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"right"` | Preferred side relative to trigger |
| `sideOffset` | `number` | `0` | Gap between trigger and popup |
| `className` | `string` | - | Additional CSS classes |
All `DropdownMenuContent` props are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | -------------------------------------------------------- |
| `Space` | Activates the focused menu item |
| `Enter` | Activates the focused menu item |
| `ArrowDown` | Moves focus to the next menu item |
| `ArrowUp` | Moves focus to the previous menu item |
| `ArrowRight` | Opens a submenu when focused on a sub-trigger |
| `ArrowLeft` | Closes a submenu and returns focus to the parent trigger |
| `Escape` | Closes the menu |
| `Home` | Moves focus to the first menu item |
| `End` | Moves focus to the last menu item |
### ARIA Attributes
- `DropdownMenuContent` receives `role="menu"`.
- `DropdownMenuItem` receives `role="menuitem"`.
- `DropdownMenuCheckboxItem` receives `role="menuitemcheckbox"` with `aria-checked`.
- `DropdownMenuRadioItem` receives `role="menuitemradio"` with `aria-checked`.
- `DropdownMenuSub` content receives `role="menu"` for nested menus.
- `data-disabled` is set on disabled items, which sets `aria-disabled`.
- Focus is managed within the menu and returns to the trigger when the menu closes.
## Full Component Source
```tsx
"use client";
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
} from "@prototyperco/ui/components/menu";
```
# Menubar
> A horizontal menu bar with multiple dropdown menus, built on Base UI
URL: https://prototyper-ui.com/docs/components/menubar
Base UI reference: https://base-ui.com/react/components/menubar
```tsx
"use client";
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar";
export default function MenubarDemo() {
return (
File
New Tab Ctrl+T
New Window Ctrl+N
Share
Email
Messages
Print Ctrl+P
Edit
Undo Ctrl+Z
Redo Ctrl+Y
Cut Ctrl+X
Copy Ctrl+C
Paste Ctrl+V
View
Zoom In Ctrl++
Zoom Out Ctrl+-
Full Screen F11
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/menubar.json
```
This will add the following files to your project:
- `components/ui/menubar.tsx`
- `components/ui/menu.tsx` (dependency)
## Usage
```tsx
import {
Menubar,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarItem,
} from "@/components/ui/menubar";
File
New File
Open
Save
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| --------------------- | ----------------------- | ------------------------------------- | -------- |
| `Menubar` | `menubar` | Root container, horizontal bar | Yes |
| `MenubarMenu` | `menubar-menu` | Individual menu within the bar | Yes |
| `MenubarTrigger` | `menubar-trigger` | Button that opens a menu | Yes |
| `MenubarPortal` | `menubar-portal` | Renders children into a portal | No |
| `MenubarContent` | `menubar-content` | The popup panel containing menu items | Yes |
| `MenubarGroup` | `menubar-group` | Groups related menu items | No |
| `MenubarLabel` | `menubar-label` | Non-interactive label for a group | No |
| `MenubarItem` | `menubar-item` | Individual actionable menu item | Yes |
| `MenubarCheckboxItem` | `menubar-checkbox-item` | Toggleable checkbox menu item | No |
| `MenubarRadioGroup` | `menubar-radio-group` | Groups radio menu items | No |
| `MenubarRadioItem` | `menubar-radio-item` | Radio-selectable menu item | No |
| `MenubarSeparator` | `menubar-separator` | Visual separator between items | No |
| `MenubarShortcut` | `menubar-shortcut` | Keyboard shortcut hint text | No |
| `MenubarSub` | `menubar-sub` | Root for a submenu | No |
| `MenubarSubTrigger` | `menubar-sub-trigger` | Item that opens a submenu | No |
| `MenubarSubContent` | `menubar-sub-content` | Popup panel for a submenu | No |
## Examples
### Application Menu
```tsx
"use client";
import {
Menubar,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar";
export default function MenubarDemo() {
return (
File
New Tab Ctrl+T
New Window Ctrl+N
Share
Email
Messages
Print Ctrl+P
Edit
Undo Ctrl+Z
Redo Ctrl+Y
Cut Ctrl+X
Copy Ctrl+C
Paste Ctrl+V
View
Zoom In Ctrl++
Zoom Out Ctrl+-
Full Screen F11
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the menubar:
| Slot name | Element |
| ----------------------- | ---------------------------- |
| `menubar` | The horizontal bar container |
| `menubar-menu` | Individual menu root |
| `menubar-trigger` | The trigger button |
| `menubar-portal` | Portal wrapper |
| `menubar-content` | The popup panel |
| `menubar-group` | Group wrapper |
| `menubar-label` | Group label |
| `menubar-item` | Individual menu item |
| `menubar-checkbox-item` | Checkbox menu item |
| `menubar-radio-group` | Radio group wrapper |
| `menubar-radio-item` | Radio menu item |
| `menubar-separator` | Visual separator line |
| `menubar-shortcut` | Keyboard shortcut text |
| `menubar-sub` | Submenu root |
| `menubar-sub-trigger` | Submenu trigger item |
| `menubar-sub-content` | Submenu popup panel |
### Customization Examples
```css
/* Change menubar background */
[data-slot="menubar"] {
@apply bg-muted;
}
/* Change menu item highlight color */
[data-slot="menubar-item"][data-highlighted] {
@apply bg-primary text-primary-foreground;
}
```
```tsx
{
/* Override styles via className */
}
File
Custom Item
;
```
## API Reference
### Menubar
Root container that renders a horizontal menu bar.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menubar props](https://base-ui.com/react/components/menubar) are forwarded via `...props`.
### MenubarMenu
Individual menu within the menu bar. Wraps `DropdownMenu`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | - | Controlled open state |
| `onOpenChange` | `(open: boolean) => void` | - | Callback when open state changes |
All [Base UI Menu.Root props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarTrigger
Button within the menu bar that opens a dropdown menu.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.Trigger props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarContent
The popup panel containing menu items, rendered inside a portal with a positioner.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment relative to the trigger |
| `alignOffset` | `number` | `-4` | Offset from the alignment edge |
| `sideOffset` | `number` | `8` | Gap between trigger and popup |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Menu content |
All [Base UI Menu.Popup props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarGroup
Groups related menu items together.
All [Base UI Menu.Group props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarLabel
Non-interactive label for a group of menu items.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.GroupLabel props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarItem
Individual actionable menu item.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "destructive"` | `"default"` | Visual style variant |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.Item props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarCheckboxItem
Toggleable checkbox menu item with a check indicator.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `checked` | `boolean` | - | Whether the item is checked |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Item content |
All [Base UI Menu.CheckboxItem props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarRadioGroup
Groups radio menu items for single-selection behavior.
All [Base UI Menu.RadioGroup props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarRadioItem
Radio-selectable menu item within a radio group.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Item content |
All [Base UI Menu.RadioItem props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarSeparator
Visual separator between menu items or groups.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.Separator props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarShortcut
Displays a keyboard shortcut hint aligned to the right of a menu item.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All standard `span` props are forwarded via `...props`.
### MenubarSub
Root provider for a submenu.
All [Base UI Menu.SubmenuRoot props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarSubTrigger
Menu item that opens a submenu on hover or keyboard navigation.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `inset` | `boolean` | - | Adds left padding to align with items that have icons |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Trigger content |
All [Base UI Menu.SubmenuTrigger props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
### MenubarSubContent
Popup panel for a submenu, rendered inside a portal with a positioner.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment relative to the trigger |
| `alignOffset` | `number` | `-3` | Offset from the alignment edge |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"right"` | Preferred side relative to trigger |
| `sideOffset` | `number` | `0` | Gap between trigger and popup |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Menu.SubmenuPopup props](https://base-ui.com/react/components/menu) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | ------------------------------------------- |
| `Space` | Activates the focused menu item |
| `Enter` | Activates the focused menu item |
| `ArrowDown` | Moves focus to the next menu item |
| `ArrowUp` | Moves focus to the previous menu item |
| `ArrowRight` | Opens the next menu or a submenu |
| `ArrowLeft` | Opens the previous menu or closes a submenu |
| `Escape` | Closes the currently open menu |
| `Home` | Moves focus to the first menu item |
| `End` | Moves focus to the last menu item |
### ARIA Attributes
- `Menubar` receives `role="menubar"`.
- `MenubarTrigger` receives `role="menuitem"` within the menubar context.
- `MenubarContent` receives `role="menu"`.
- `MenubarItem` receives `role="menuitem"`.
- `MenubarCheckboxItem` receives `role="menuitemcheckbox"` with `aria-checked`.
- `MenubarRadioItem` receives `role="menuitemradio"` with `aria-checked`.
- `MenubarSub` content receives `role="menu"` for nested menus.
- `data-disabled` is set on disabled items, which sets `aria-disabled`.
- Focus is managed within the menu bar and returns to the trigger when a menu closes.
## Full Component Source
```tsx
"use client";
export {
Menubar,
MenubarPortal,
MenubarMenu,
MenubarTrigger,
MenubarContent,
MenubarGroup,
MenubarSeparator,
MenubarLabel,
MenubarItem,
MenubarShortcut,
MenubarCheckboxItem,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSub,
MenubarSubTrigger,
MenubarSubContent,
} from "@prototyperco/ui/components/menubar";
```
# Meter
> A meter displaying a value within a known range
URL: https://prototyper-ui.com/docs/components/meter
Base UI reference: https://base-ui.com/react/components/meter
```tsx
"use client";
import React from "react";
import {
Meter,
MeterIndicator,
MeterLabel,
MeterTrack,
MeterValue,
} from "@/components/ui/meter";
export default function MeterDemo() {
const [progress, setProgress] = React.useState(13);
React.useEffect(() => {
const timer = setTimeout(() => setProgress(66), 500);
return () => clearTimeout(timer);
}, []);
return (
Storage space
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/meter.json
```
This will add the following files to your project:
- `components/ui/meter.tsx`
## Usage
```tsx
import {
Meter,
MeterTrack,
MeterIndicator,
MeterLabel,
MeterValue,
} from "@/components/ui/meter";
Storage
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ---------------- | ----------------- | ------------------------------------- | -------- |
| `Meter` | `meter` | Root provider, manages value state | Yes |
| `MeterTrack` | `meter-track` | Background track for the indicator | Yes |
| `MeterIndicator` | `meter-indicator` | Filled portion representing the value | Yes |
| `MeterLabel` | `meter-label` | Accessible label for the meter | No |
| `MeterValue` | `meter-value` | Displays the current value as text | No |
## Examples
### Custom Format
```tsx
import {
Meter,
MeterIndicator,
MeterLabel,
MeterTrack,
} from "@/components/ui/meter";
export default function MeterCustomFormat() {
return (
Space used
54 of 60GB
);
}
```
### Reusable
```tsx
import {
Meter,
MeterIndicator,
MeterLabel,
MeterTrack,
MeterValue,
} from "@/components/ui/meter";
export default function MeterReusable() {
return (
Storage space
);
}
```
### 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" \| "info"` | `"default"` | Color variant for the track |
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Track height variant |
| `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.
## Full Component Source
```tsx
"use client";
export {
Meter,
MeterTrack,
MeterIndicator,
MeterLabel,
MeterValue,
meterIndicatorVariants,
meterTrackVariants,
} from "@prototyperco/ui/components/meter";
```
# Navigation Menu
> site navigation component with dropdown menus on Base UI
URL: https://prototyper-ui.com/docs/components/navigation-menu
Base UI reference: https://base-ui.com/react/components/navigation-menu
```tsx
"use client";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
export default function NavigationMenuDemo() {
return (
Getting Started
Introduction
Learn the basics and get up and running quickly.
Installation
Step-by-step guide to install and configure.
Typography
Styles for headings, paragraphs, and lists.
CLI
Add components using the command line.
Components
Button
Trigger actions and events.
Dialog
Modal and non-modal overlays.
Select
Pick a value from a dropdown list.
Tabs
Organize content into tabbed panels.
Documentation
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/navigation-menu.json
```
This will add the following files to your project:
- `components/ui/navigation-menu.tsx`
## Usage
```tsx
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu";
Item One
Documentation
About
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| -------------------------- | ---------------------------- | -------------------------------------------------------------- | -------- |
| `NavigationMenu` | `navigation-menu` | Root provider, manages open state and renders the positioner | Yes |
| `NavigationMenuList` | `navigation-menu-list` | Container for menu items | Yes |
| `NavigationMenuItem` | `navigation-menu-item` | Wraps a single trigger + content pair or a standalone link | Yes |
| `NavigationMenuTrigger` | `navigation-menu-trigger` | Button that opens a dropdown content area | No |
| `NavigationMenuContent` | `navigation-menu-content` | Dropdown panel associated with a trigger | No |
| `NavigationMenuLink` | `navigation-menu-link` | Link item inside the content or used standalone | No |
| `NavigationMenuPositioner` | `navigation-menu-positioner` | Positions the popup relative to the trigger (rendered by root) | Auto |
| `NavigationMenuIndicator` | `navigation-menu-indicator` | Optional visual indicator arrow | No |
## Examples
### With Dropdowns
```tsx
"use client";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu";
export default function NavigationMenuDemo() {
return (
Getting Started
Introduction
Learn the basics and get up and running quickly.
Installation
Step-by-step guide to install and configure.
Typography
Styles for headings, paragraphs, and lists.
CLI
Add components using the command line.
Components
Button
Trigger actions and events.
Dialog
Modal and non-modal overlays.
Select
Pick a value from a dropdown list.
Tabs
Organize content into tabbed panels.
Documentation
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the navigation menu:
| Slot name | Element |
| --------------------------------- | ------------------------------------- |
| `navigation-menu` | Root wrapper |
| `navigation-menu-list` | Container for items |
| `navigation-menu-item` | Individual item wrapper |
| `navigation-menu-trigger` | Trigger button that opens content |
| `navigation-menu-content` | Dropdown content panel |
| `navigation-menu-link` | Link inside content or standalone |
| `navigation-menu-positioner` | Floating positioner element |
| `navigation-menu-popup` | Popup container within the positioner |
| `navigation-menu-viewport` | Viewport inside the popup |
| `navigation-menu-indicator` | Optional indicator arrow |
| `navigation-menu-indicator-arrow` | Arrow element inside the indicator |
### Customization Examples
```css
/* Wider dropdown content */
[data-slot="navigation-menu-content"] {
@apply p-4;
}
/* Custom trigger active state */
[data-slot="navigation-menu-trigger"][data-popup-open] {
@apply bg-primary text-primary-foreground;
}
```
```tsx
{
/* Style the navigation menu list with additional gap */
}
Item
...
;
```
## API Reference
### NavigationMenu
Root component that manages navigation state and renders the popup positioner.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment of the popup relative to the trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Navigation menu content |
All [Base UI NavigationMenu.Root props](https://base-ui.com/react/components/navigation-menu) are forwarded via `...props`.
### NavigationMenuList
Container for navigation menu items.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Navigation menu items |
All [Base UI NavigationMenu.List props](https://base-ui.com/react/components/navigation-menu) are forwarded via `...props`.
### NavigationMenuItem
Wraps a trigger + content pair or a standalone link.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Trigger and content, or a standalone link |
All [Base UI NavigationMenu.Item props](https://base-ui.com/react/components/navigation-menu) are forwarded via `...props`.
### NavigationMenuTrigger
Button that opens the associated dropdown content.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Trigger label content |
All [Base UI NavigationMenu.Trigger props](https://base-ui.com/react/components/navigation-menu) are forwarded via `...props`.
### NavigationMenuContent
Dropdown panel shown when its associated trigger is activated.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Content items, typically NavigationMenuLink elements |
All [Base UI NavigationMenu.Content props](https://base-ui.com/react/components/navigation-menu) are forwarded via `...props`.
### NavigationMenuLink
A link element inside the navigation menu. Can be used within content dropdowns or as a standalone item.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | - | URL the link points to |
| `active` | `boolean` | `false` | Whether the link is the currently active page |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Link label content |
All [Base UI NavigationMenu.Link props](https://base-ui.com/react/components/navigation-menu) are forwarded via `...props`.
### NavigationMenuPositioner
Controls the position of the popup. Rendered automatically by `NavigationMenu`; you do not need to add it manually.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Which side of the trigger to place the popup |
| `sideOffset` | `number` | `8` | Distance from the trigger in pixels |
| `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment of the popup along the trigger edge |
| `alignOffset` | `number` | `0` | Offset along the alignment axis in pixels |
| `className` | `string` | - | Additional CSS classes |
### NavigationMenuIndicator
Optional visual indicator arrow displayed below the active trigger.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI NavigationMenu.Icon props](https://base-ui.com/react/components/navigation-menu) are forwarded via `...props`.
### navigationMenuTriggerStyle
A `cva` helper exported for use outside of the `` component (e.g., applying trigger styles to standalone links).
```tsx
import { navigationMenuTriggerStyle } from "@/components/ui/navigation-menu";
Documentation
;
```
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | ----------------------------------------------------------------- |
| `Space` | Opens the dropdown when a trigger is focused |
| `Enter` | Opens the dropdown when a trigger is focused, activates links |
| `ArrowDown` | Opens the dropdown from a trigger; moves focus within content |
| `ArrowUp` | Moves focus within content |
| `ArrowRight` | Moves focus to the next trigger in the list |
| `ArrowLeft` | Moves focus to the previous trigger in the list |
| `Home` | Moves focus to the first trigger |
| `End` | Moves focus to the last trigger |
| `Escape` | Closes the open dropdown and returns focus to the trigger |
| `Tab` | Moves focus out of the navigation menu, closing any open dropdown |
### ARIA Attributes
- The root element renders with `role="navigation"`.
- Each trigger renders with `aria-expanded` and `aria-haspopup` attributes.
- The content panel is linked to its trigger via `aria-controls`.
- Links render with `role="link"`.
- Active links can be marked with `data-active="true"` for `aria-current` behavior.
## Full Component Source
```tsx
"use client";
export {
NavigationMenu,
NavigationMenuContent,
NavigationMenuIndicator,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
NavigationMenuPositioner,
} from "@prototyperco/ui/components/navigation-menu";
```
# NumberField
> A number input with increment/decrement controls built on Base UI
URL: https://prototyper-ui.com/docs/components/number-field
Base UI reference: https://base-ui.com/react/components/number-field
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldDemo() {
return (
Width
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/number-field.json
```
This will add the following files to your project:
- `components/ui/number-field.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/number-field";
;
```
## 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/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldCurrency() {
return (
Transaction amount
);
}
```
### Description
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
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/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldDisabled() {
return (
Disabled
);
}
```
### Formatting
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldFormatting() {
return (
Adjust exposure
);
}
```
### Percentages
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldPercentages() {
return (
Sales tax
);
}
```
### Read Only
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldReadonly() {
return (
Read only
);
}
```
### Reusable
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import { FieldDescription, FieldLabel } from "@/components/ui/field";
export default function NumberfieldReusable() {
return (
Cookies
Please enter a number
);
}
```
### Step Values
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldStepValues() {
return (
Step
Step + minValue
Step + minValue + maxValue
);
}
```
### Units
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
import { FieldLabel } from "@/components/ui/field";
export default function NumberFieldUnits() {
return (
Package width
);
}
```
### Validation
```tsx
import {
NumberField,
NumberFieldGroup,
NumberFieldInput,
NumberFieldSteppers,
} from "@/components/ui/number-field";
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/number-field";
import { Button } from "@/components/ui/button";
import { FieldError, FieldLabel } from "@/components/ui/field";
export default function NumberFieldValidationError() {
return (
Width
Submit
);
}
```
## 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/number-field";
...
;
```
## 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.
## Full Component Source
```tsx
"use client";
export {
NumberField,
NumberFieldGroup,
numberFieldGroupVariants,
NumberFieldInput,
NumberFieldIncrement,
NumberFieldDecrement,
NumberFieldSteppers,
NumberFieldScrubArea,
NumberFieldScrubAreaCursor,
} from "@prototyperco/ui/components/number-field";
```
# 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-Fi
Bluetooth
Mute
);
}
```
## 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
This is a popover.
);
}
```
### Cross Offset
```tsx
import { Button } from "@/components/ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
export default function PopoverCrossOffset() {
return (
}>
Cross offset
Offset by an additional 100px.
);
}
```
### 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.
## Full Component Source
```tsx
"use client";
export {
Popover,
PopoverContent,
PopoverDescription,
PopoverHeader,
PopoverTitle,
PopoverTrigger,
} from "@prototyperco/ui/components/popover";
```
# Hover Card
> popup showing content preview on link hover
URL: https://prototyper-ui.com/docs/components/preview-card
Base UI reference: https://base-ui.com/react/components/preview-card
```tsx
"use client";
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from "@/components/ui/preview-card";
export default function PreviewCardDemo() {
return (
@base-ui
Base UI
Unstyled React components for building accessible UIs. Created and
maintained by MUI.
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/preview-card.json
```
This will add the following files to your project:
- `components/ui/preview-card.tsx`
## Usage
```tsx
import {
HoverCard,
HoverCardTrigger,
HoverCardContent,
} from "@/components/ui/preview-card";
@example
Preview content here.
;
```
## Anatomy
```tsx
{/* your preview content */}
```
| Sub-component | `data-slot` | Purpose | Required |
| ------------------ | -------------------- | ----------------------------------------- | -------- |
| `HoverCard` | `hover-card` | Root provider, manages open/close state | Yes |
| `HoverCardTrigger` | `hover-card-trigger` | Link element that shows the card on hover | Yes |
| `HoverCardContent` | `hover-card-content` | The popup panel anchored to the trigger | Yes |
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the hover card:
| Slot name | Element |
| -------------------- | ------------------------------- |
| `hover-card` | Root provider (no DOM rendered) |
| `hover-card-trigger` | The trigger link element |
| `hover-card-portal` | Portal container |
| `hover-card-content` | The popup panel |
### Customization Examples
```css
/* Make hover card wider */
[data-slot="hover-card-content"] {
@apply w-80;
}
/* Style the trigger link */
[data-slot="hover-card-trigger"] {
@apply underline decoration-foreground/30 underline-offset-4;
}
```
```tsx
{
/* Override styles via className */
}
{/* Rich preview content */}
;
```
## API Reference
### HoverCard
Root component that manages open/close state. The card opens when the trigger is hovered.
| 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 PreviewCard.Root props](https://base-ui.com/react/components/preview-card) are forwarded via `...props`.
### HoverCardTrigger
The link element that shows the hover card when hovered. Renders an `` element by default.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | - | URL the link points to |
| `className` | `string` | - | Additional CSS classes |
All [Base UI PreviewCard.Trigger props](https://base-ui.com/react/components/preview-card) are forwarded via `...props`.
### HoverCardContent
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` | `4` | 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` | - | Hover card content |
All [Base UI PreviewCard.Popup props](https://base-ui.com/react/components/preview-card) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| -------- | --------------------- |
| `Escape` | Closes the hover card |
### ARIA Attributes
- The trigger renders as an ` ` element, providing native link semantics.
- The hover card is intended for sighted users as a progressive enhancement -- the link remains functional without the card.
- Content inside the card is not announced to screen readers unless focused.
- The card opens on hover with a default delay of 600ms and closes with a 300ms delay.
## Full Component Source
```tsx
"use client";
export {
HoverCard,
HoverCardTrigger,
HoverCardContent,
} from "@prototyperco/ui/components/preview-card";
```
# 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";
Loading...
;
```
## Anatomy
```tsx
{/* ProgressTrack and ProgressIndicator are rendered internally by Progress */}
```
| 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 (
Feeding...
30 of 100 dogs
);
}
```
### 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 */
}
Upload
;
```
## 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 |
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size variant for the track height |
| `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.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="progress.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A progress bar indicating completion status",
props: z.object({
value: z.number().min(0).max(100).describe("Progress value from 0 to 100"),
label: z
.string()
.optional()
.describe("Label displayed above the progress bar"),
color: z
.enum(["default", "success", "warning", "destructive", "info"])
.optional()
.describe("Color variant of the progress fill"),
size: z
.enum(["sm", "md", "lg", "xl"])
.optional()
.describe("Height of the progress bar"),
}),
example: {
value: 65,
label: "Upload progress",
color: "default",
size: "md",
},
});
```
### Example Spec
```json
{
"root": "progress",
"elements": {
"progress": {
"type": "Progress",
"props": {
"value": 65,
"label": "Upload progress",
"color": "default",
"size": "md"
}
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export {
Progress,
ProgressTrack,
ProgressIndicator,
ProgressLabel,
ProgressValue,
progressIndicatorVariants,
progressTrackVariants,
} from "@prototyperco/ui/components/progress";
```
# 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
Dog
Cat
Dragon
);
}
```
## 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 1
Option 2
Option 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 (internal element managed by `RadioGroupItem`, not composable) | — |
## Examples
### Description
```tsx
import { FieldLabel } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function RadioGroupDescription() {
return (
Favorite avatar
Wizard
Dragon
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
Soccer
Baseball
Basketball
);
}
```
### Disabled Individual
```tsx
import { FieldLabel } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function RadioGroupDisabledIndividual() {
return (
Favorite sport
Soccer
Baseball
Basketball
);
}
```
### Orientation
```tsx
import { FieldLabel } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function RadioGroupOrientation() {
return (
Favorite avatar
Wizard
Dragon
);
}
```
### Read Only
```tsx
import { FieldLabel } from "@/components/ui/field";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
export default function RadioGroupReadonly() {
return (
Favorite avatar
Wizard
Dragon
);
}
```
### Reusable
```tsx
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { FieldLabel } from "@/components/ui/field";
export default function RadioGroupReusable() {
return (
Favorite sport *
Soccer
Baseball
Basketball
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 (
Favorite pet
Dog
Cat
Dragon
Submit
);
}
```
## 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 */
}
A
B
;
```
## 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.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="radio-group.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description:
"A group of radio buttons for selecting a single option from a list",
props: z.object({
label: z.string().optional().describe("Label for the radio group fieldset"),
options: z
.array(
z.object({
label: z.string().describe("Display label for the option"),
value: z.string().describe("Value of the option"),
}),
)
.describe("Available radio options"),
value: z
.string()
.optional()
.describe("Currently selected value (bindable)"),
orientation: z
.enum(["vertical", "horizontal"])
.optional()
.describe("Layout direction of the radio buttons"),
disabled: z
.boolean()
.optional()
.describe("Whether the radio group is disabled"),
}),
events: ["change"],
example: {
label: "Select a plan",
options: [
{ label: "Free", value: "free" },
{ label: "Pro", value: "pro" },
{ label: "Enterprise", value: "enterprise" },
],
value: "free",
},
});
```
### Example Spec
```json
{
"root": "planPicker",
"elements": {
"planPicker": {
"type": "RadioGroup",
"props": {
"label": "Select a plan",
"options": [
{ "label": "Free", "value": "free" },
{ "label": "Pro", "value": "pro" },
{ "label": "Enterprise", "value": "enterprise" }
],
"value": "free"
},
"$bindState": { "path": "/selectedPlan", "event": "change" }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export {
RadioGroup,
RadioGroupItem,
} from "@prototyperco/ui/components/radio-group";
```
# Resizable Panel
> Resizable split panels with drag handles for flexible layouts
URL: https://prototyper-ui.com/docs/components/resizable-panel
```tsx
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable-panel";
export default function ResizablePanelDemo() {
return (
Panel A
Panel B
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/resizable-panel.json
```
This will add the following files to your project:
- `components/ui/resizable-panel.tsx`
### Dependencies
This component uses [`react-resizable-panels`](https://github.com/bvaughn/react-resizable-panels) under the hood. It will be installed automatically.
## Usage
```tsx
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@/components/ui/resizable-panel";
Left panel content
Right panel content
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| --------------------- | ----------------------- | ------------------------------------ | -------- |
| `ResizablePanelGroup` | `resizable-panel-group` | Container that manages panel layout | Yes |
| `ResizablePanel` | `resizable-panel` | Individual panel with resizable size | Yes |
| `ResizableHandle` | `resizable-handle` | Drag handle between panels | Yes |
## Examples
### Vertical orientation
```tsx
Top
Bottom
```
### Three panels
```tsx
Sidebar
Main
Inspector
```
## Styling
### Data Slots
| Slot name | Element |
| ----------------------- | ----------------------------- |
| `resizable-panel-group` | Flex container for all panels |
| `resizable-panel` | Individual panel |
| `resizable-handle` | Drag handle separator |
### Customization Examples
```css
/* Custom handle color */
[data-slot="resizable-handle"] {
@apply bg-primary/20;
}
/* Wider drag zone */
[data-slot="resizable-handle"]::after {
left: -4px;
right: -4px;
}
```
## API Reference
### ResizablePanelGroup
Container that manages layout of child panels. Wraps `react-resizable-panels` `Group`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Panel layout direction |
| `className` | `string` | - | Additional CSS classes |
All [`react-resizable-panels` Group props](https://github.com/bvaughn/react-resizable-panels) are forwarded.
### ResizablePanel
Individual resizable panel. Wraps `react-resizable-panels` `Panel`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultSize` | `string` | - | Initial size as percentage (e.g. "50%") |
| `minSize` | `string` | - | Minimum size as percentage |
| `maxSize` | `string` | - | Maximum size as percentage |
| `className` | `string` | - | Additional CSS classes |
All [`react-resizable-panels` Panel props](https://github.com/bvaughn/react-resizable-panels) are forwarded.
### ResizableHandle
Drag handle between panels. Wraps `react-resizable-panels` `Separator`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `withHandle` | `boolean` | `false` | Show a visual grip icon on the handle |
| `className` | `string` | - | Additional CSS classes |
All [`react-resizable-panels` Separator props](https://github.com/bvaughn/react-resizable-panels) are forwarded.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | ------------------------------------------- |
| `ArrowLeft` | Shrinks the panel to the left of the handle |
| `ArrowRight` | Grows the panel to the left of the handle |
| `ArrowUp` | Shrinks the panel above the handle |
| `ArrowDown` | Grows the panel above the handle |
| `Home` | Collapses the panel to its minimum size |
| `End` | Expands the panel to its maximum size |
Keyboard interactions are provided by the underlying `react-resizable-panels` library.
## Full Component Source
```tsx
"use client";
export {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "@prototyperco/ui/components/resizable-panel";
```
# Row
> A horizontal flex layout with gap, alignment, and justify options.
URL: https://prototyper-ui.com/docs/components/row
```tsx
import { Row } from "@/components/ui/row";
export default function RowDemo() {
return (
Item 1
Item 2
Item 3
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/row.json
```
This will add the following files to your project:
- `components/ui/row.tsx`
## Usage
```tsx
import { Row } from "@/components/ui/row";
```
```tsx
Left
Right
```
Row is a horizontal flex container with props for `gap`, `align`, `justify`, and `wrap`. Use it for toolbars, header layouts, button groups, and tag lists.
## Examples
### Space Between
A common header pattern with logo on the left and navigation on the right.
```tsx
import { Row } from "@/components/ui/row";
export default function RowBetween() {
return (
Logo
Docs
Pricing
Blog
);
}
```
### Centered
Centered button group, useful for dialog footers and form actions.
```tsx
import { Row } from "@/components/ui/row";
import { Button } from "@/components/ui/button";
export default function RowCentered() {
return (
Cancel
Save Changes
);
}
```
### Wrapping
Wrapping row for tags, badges, or chips that overflow to the next line.
```tsx
import { Row } from "@/components/ui/row";
import { Badge } from "@/components/ui/badge";
export default function RowWrap() {
return (
React
TypeScript
Tailwind CSS
Base UI
Next.js
Turborepo
pnpm
Vitest
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target the row in CSS:
| Slot name | Element |
| --------- | ---------------- |
| `row` | The `` root |
### Customization Examples
```css
/* Add a bottom border to all rows */
[data-slot="row"] {
@apply border-b border-border pb-2;
}
```
```tsx
Custom styled row
```
## API Reference
### Row
A horizontal flex container with layout props.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `gap` | `number` | `3` | Gap between items. Maps to Tailwind gap utilities (0, 1, 2, 3, 4, 5, 6, 8, 10, 12). |
| `align` | `"start" \| "center" \| "end" \| "stretch" \| "baseline"` | `"center"` | Vertical alignment of items (items-*). |
| `justify` | `"start" \| "center" \| "end" \| "between" \| "around" \| "evenly"` | `"start"` | Horizontal distribution of items (justify-*). |
| `wrap` | `boolean` | `false` | Whether items wrap to the next line when they overflow. |
| `className` | `string` | - | Additional CSS classes. |
| `children` | `React.ReactNode` | - | Row content. |
Extends `React.ComponentProps<"div">`. All standard div props are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
Row is a non-interactive layout container and does not have keyboard interactions.
### ARIA Attributes
- Renders as a plain `
` with no implicit ARIA role.
- No additional ARIA attributes are needed for a flex layout container.
## Full Component Source
```tsx
"use client";
export { Row } from "@prototyperco/ui/components/row";
```
# Scroll Area
> A scrollable container with custom styled scrollbars
URL: https://prototyper-ui.com/docs/components/scroll-area
Base UI reference: https://base-ui.com/react/components/scroll-area
```tsx
"use client";
import { ScrollArea } from "@/components/ui/scroll-area";
const tags = Array.from({ length: 50 }).map(
(_, i, a) => `v1.2.0-beta.${a.length - i}`,
);
export default function ScrollAreaDemo() {
return (
Tags
{tags.map((tag) => (
))}
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/scroll-area.json
```
This will add the following files to your project:
- `components/ui/scroll-area.tsx`
## Usage
```tsx
import { ScrollArea } from "@/components/ui/scroll-area";
{/* Long content here */}
;
```
## Anatomy
```tsx
{/* ScrollArea.Viewport, ScrollBar, and ScrollArea.Corner are rendered internally */}
Your scrollable content
```
| Sub-component | `data-slot` | Purpose | Required |
| ------------- | ----------------------- | ----------------------------------------------- | -------- |
| `ScrollArea` | `scroll-area` | Root container with built-in viewport/scrollbar | Yes |
| `ScrollBar` | `scroll-area-scrollbar` | Scrollbar track (vertical or horizontal) | Internal |
| Viewport | `scroll-area-viewport` | Scrollable viewport container | Internal |
| Thumb | `scroll-area-thumb` | Draggable scrollbar indicator | Internal |
> **Note:** The viewport, scrollbar, thumb, and corner are rendered automatically inside the `ScrollArea` component. You only need to provide child content.
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the scroll area:
| Slot name | Element |
| ----------------------- | ------------------------- |
| `scroll-area` | Root wrapper |
| `scroll-area-viewport` | Scrollable viewport |
| `scroll-area-scrollbar` | Scrollbar track |
| `scroll-area-thumb` | Scrollbar thumb indicator |
### Customization Examples
```css
/* Make the scrollbar wider */
[data-slot="scroll-area-scrollbar"] {
@apply w-3.5;
}
/* Custom thumb color */
[data-slot="scroll-area-thumb"] {
@apply bg-primary/50;
}
```
```tsx
{
/* Add horizontal scrollbar with the ScrollBar component */
}
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
{items.map((item) => (
{item}
))}
;
```
## API Reference
### ScrollArea
Root component that provides a scrollable viewport with custom scrollbars. Renders a vertical scrollbar by default.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes for the root container |
| `children` | `React.ReactNode` | - | Scrollable content |
All [Base UI ScrollArea.Root props](https://base-ui.com/react/components/scroll-area) are forwarded via `...props`.
### ScrollBar
A scrollbar track with a draggable thumb. One vertical scrollbar is rendered by default inside `ScrollArea`. Use this component to add an additional horizontal scrollbar.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | The axis the scrollbar controls |
| `className` | `string` | - | Additional CSS classes for the scrollbar |
All [Base UI ScrollArea.Scrollbar props](https://base-ui.com/react/components/scroll-area) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Description |
| -------------------------- | ----------------------------------------------------- |
| `Tab` | Moves focus into the scroll area viewport |
| `ArrowUp` / `ArrowDown` | Scrolls content vertically when viewport is focused |
| `ArrowLeft` / `ArrowRight` | Scrolls content horizontally when viewport is focused |
| `PageUp` / `PageDown` | Scrolls content by one page |
| `Home` / `End` | Scrolls to the start or end of content |
### ARIA Attributes
- The viewport is focusable with `tabindex="0"` and includes a `focus-visible` ring for keyboard users.
- Scroll position is managed natively by the browser, providing standard scrolling behavior and screen reader compatibility.
- The custom scrollbars are decorative overlays; actual scrolling relies on native browser scroll mechanics.
- `data-has-overflow-x` and `data-has-overflow-y` attributes on the root indicate when content exceeds the viewport.
- `data-scrolling` is present on the root during active scrolling.
## Full Component Source
```tsx
"use client";
export { ScrollArea, ScrollBar } from "@prototyperco/ui/components/scroll-area";
```
# Section
> A full-width page section with background variants and constrained content.
URL: https://prototyper-ui.com/docs/components/section
```tsx
import { Section } from "@/components/ui/section";
export default function SectionDemo() {
return (
<>
Hero Section
Default background, large padding
Features
Surface background, medium padding
Call to Action
Muted background, large padding
>
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/section.json
```
This will add the following files to your project:
- `components/ui/section.tsx`
## Usage
```tsx
import { Section } from "@/components/ui/section";
```
```tsx
Section heading
Section content is automatically constrained and centered.
```
Section wraps content in a full-bleed container with an optional background color. The inner content is automatically constrained to a `maxWidth` and centered with horizontal padding.
## Examples
### Primary Background
```tsx
import { Section } from "@/components/ui/section";
export default function SectionPrimary() {
return (
Get Started Today
Primary background section for high-impact call-to-action areas.
);
}
```
### Narrow Content
```tsx
import { Section } from "@/components/ui/section";
export default function SectionNarrow() {
return (
Focused Content
A narrower max-width keeps text readable for long-form content like
articles and blog posts.
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target Section parts in CSS:
| Slot name | Element |
| ----------------- | ----------------------------- |
| `section` | The outer `` root |
| `section-content` | The inner constrained `` |
### Customization Examples
```css
/* Add a top border to surface sections */
[data-slot="section"] {
@apply border-t border-border;
}
/* Override inner content max-width */
[data-slot="section-content"] {
@apply max-w-3xl;
}
```
```tsx
```
## API Reference
### Section
A full-bleed section with constrained inner content.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `background` | `"none" \| "default" \| "surface" \| "surface-secondary" \| "surface-tertiary" \| "muted" \| "primary"` | `"none"` | Background color variant for the section. |
| `padding` | `"none" \| "sm" \| "md" \| "lg" \| "xl"` | `"lg"` | Vertical padding applied to the section. |
| `maxWidth` | `"sm" \| "md" \| "lg" \| "xl" \| "2xl" \| "4xl" \| "5xl" \| "full"` | `"5xl"` | Max-width constraint for the inner content. |
| `centered` | `boolean` | `true` | Whether to center the inner content with mx-auto. |
| `className` | `string` | - | Additional CSS classes applied to the outer section. |
| `children` | `React.ReactNode` | - | Section content. |
Extends `React.ComponentProps<"section">`. All standard section props are forwarded via `...props`.
### sectionVariants
A `cva` helper exported for use outside of the `
` component.
```tsx
import { sectionVariants } from "@/components/ui/section";
Custom element with section styles
;
```
## Accessibility
### Keyboard Interactions
Section is a non-interactive presentational container and does not have keyboard interactions.
### ARIA Attributes
- Renders as a native `` element, which is treated as a landmark by assistive technology when given an accessible name.
- Add `aria-label` or `aria-labelledby` to give the section a meaningful label for screen readers.
## Full Component Source
```tsx
"use client";
export { Section, sectionVariants } from "@prototyperco/ui/components/section";
```
# Segmented Control
> a segmented control for switching between options built on Base UI
URL: https://prototyper-ui.com/docs/components/segmented-control
Base UI reference: https://base-ui.com/react/components/radio-group
```tsx
import {
SegmentedControl,
SegmentedControlItem,
} from "@/components/ui/segmented-control";
export default function SegmentedControlDemo() {
return (
Daily
Weekly
Monthly
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/segmented-control.json
```
This will add the following files to your project:
- `components/ui/segmented-control.tsx`
## Usage
```tsx
import {
SegmentedControl,
SegmentedControlItem,
} from "@/components/ui/segmented-control";
```
```tsx
Option 1
Option 2
Option 3
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ---------------------- | ------------------------ | ------------------------------ | -------- |
| `SegmentedControl` | `segmented-control` | Root container and radio group | Yes |
| `SegmentedControlItem` | `segmented-control-item` | Individual selectable option | Yes |
## Examples
### Small
```tsx
import {
SegmentedControl,
SegmentedControlItem,
} from "@/components/ui/segmented-control";
export default function SegmentedControlSmall() {
return (
Grid
List
Table
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the segmented control:
| Slot name | Element |
| ----------------------------- | -------------------------------- |
| `segmented-control` | Root container |
| `segmented-control-item` | Individual option button |
| `segmented-control-indicator` | Check indicator inside each item |
### Customization Examples
```css
/* Squared segmented control */
[data-slot="segmented-control"] {
@apply rounded-lg;
}
[data-slot="segmented-control-item"] {
@apply rounded-md;
}
```
```tsx
A
B
```
## API Reference
### SegmentedControl
Root container that manages selection state. Built on Base UI RadioGroup.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"sm" \| "md"` | `"md"` | Size of the segmented control. |
| `value` | `string` | - | Controlled selected value. |
| `defaultValue` | `string` | - | Initial selected value for uncontrolled usage. |
| `onValueChange` | `(value: string, event: Event) => void` | - | Callback when the selected value changes. |
| `disabled` | `boolean` | `false` | Whether the entire control is disabled. |
| `className` | `string` | - | Additional CSS classes. |
| `children` | `React.ReactNode` | - | SegmentedControlItem elements. |
All [Base UI RadioGroup props](https://base-ui.com/react/components/radio-group) are forwarded via `...props`.
### SegmentedControlItem
An individual selectable option within the segmented control.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | - | The value of this option. |
| `disabled` | `boolean` | `false` | Whether this item is disabled. |
| `className` | `string` | - | Additional CSS classes. |
| `children` | `React.ReactNode` | - | Item label content. |
All [Base UI Radio props](https://base-ui.com/react/components/radio-group) are forwarded via `...props`.
### segmentedControlVariants
A `cva` helper exported for applying segmented control styles outside the component.
```tsx
import { segmentedControlVariants } from "@/components/ui/segmented-control";
Custom element
;
```
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | ---------------------------------------------- |
| `Tab` | Moves focus to the segmented control |
| `ArrowRight` | Moves focus and selection to the next item |
| `ArrowLeft` | Moves focus and selection to the previous item |
| `Space` | Selects the focused item |
### ARIA Attributes
- The root renders with `role="radiogroup"` via Base UI.
- Each item renders with `role="radio"`.
- `aria-checked` is set to `true` on the selected item, `false` on others.
- `aria-disabled` is set when the control or individual items are disabled.
## Full Component Source
```tsx
"use client";
export {
SegmentedControl,
SegmentedControlItem,
segmentedControlVariants,
} from "@prototyperco/ui/components/segmented-control";
```
# 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 (
Apple
Banana
Blueberry
Grapes
Pineapple
);
}
```
## 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";
Option 1
Option 2
;
```
## 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 (
{options.map((item) => (
{item.name}
))}
);
}
```
### Description
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectDescription() {
return (
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
Please select an animal.
);
}
```
### Disabled
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectDisabled() {
return (
Apple
Banana
Blueberry
);
}
```
### Disabled Items
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectDisabledItems() {
return (
Apple
Banana (out of stock)
Blueberry
Grapes (out of stock)
Pineapple
);
}
```
### Links
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectLinks() {
return (
Create new…
Proposal
Budget
Onboarding
);
}
```
### Reusable
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectReusable() {
return (
Ice cream flavor
Chocolate
Mint
Strawberry
Vanilla
Select a flavor
);
}
```
### Sections
```tsx
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectSections() {
return (
Fruits
Apple
Banana
Blueberry
Vegetables
Carrot
Potato
Tomato
);
}
```
### Sections Dynamic
```tsx
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectSectionsDynamic() {
const options = [
{
name: "Fruit",
children: [
{ name: "Apple" },
{ name: "Banana" },
{ name: "Orange" },
{ name: "Honeydew" },
{ name: "Grapes" },
{ name: "Watermelon" },
{ name: "Cantaloupe" },
{ name: "Pear" },
],
},
{
name: "Vegetable",
children: [
{ name: "Cabbage" },
{ name: "Broccoli" },
{ name: "Carrots" },
{ name: "Lettuce" },
{ name: "Spinach" },
{ name: "Bok Choy" },
{ name: "Cauliflower" },
{ name: "Potatoes" },
],
},
];
return (
{options.map((section) => (
{section.name}
{section.children.map((item) => (
{item.name}
))}
))}
);
}
```
### Text Slots
```tsx
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectTextSlots() {
return (
Read only
Read only
Write
Read and write only
Admin
Full access
);
}
```
### Validation
```tsx
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectValidation() {
const [value, setValue] = useState(null);
const [error, setError] = useState();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!value) {
setError("Please select an animal");
} else {
setError(undefined);
alert(`Selected: ${value}`);
}
};
return (
{
setValue(v);
setError(undefined);
}}
>
Aardvark
Cat
Dog
Kangaroo
Panda
Snake
{error &&
{error}
}
Submit
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the select:
| Slot name | Element |
| --------------------------- | ------------------------------- |
| `select` | Root provider |
| `select-trigger` | The trigger button |
| `select-value` | Displayed selected value text |
| `select-content` | The dropdown popup panel |
| `select-item` | An individual option |
| `select-group` | Group wrapper for related items |
| `select-label` | Label for a group |
| `select-separator` | Visual divider |
| `select-scroll-up-button` | Top scroll indicator |
| `select-scroll-down-button` | Bottom scroll indicator |
### Customization Examples
```css
/* Make the trigger wider */
[data-slot="select-trigger"] {
@apply w-64;
}
/* Custom highlight color for items */
[data-slot="select-item"][data-highlighted] {
@apply bg-primary text-primary-foreground;
}
```
```tsx
{
/* Override trigger size via className */
}
;
```
## API Reference
### Select
Root component that manages selection state and context.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultValue` | `any` | - | Initial value for uncontrolled usage |
| `value` | `any` | - | Controlled selected value |
| `onValueChange` | `(value: any) => void` | - | Callback when the selected value changes |
| `disabled` | `boolean` | `false` | Whether the select is disabled |
All [Base UI Select.Root props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectTrigger
Button that opens the dropdown when clicked.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"default" \| "sm" \| "lg"` | `"default"` | Size of the trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Trigger content |
All [Base UI Select.Trigger props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectValue
Displays the currently selected value or a placeholder.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `placeholder` | `string` | - | Text shown when no value is selected |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Select.Value props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectContent
The dropdown popup panel rendered inside a portal with positioning.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | `"top" \| "bottom" \| "left" \| "right"` | `"bottom"` | Preferred side of the trigger to render |
| `sideOffset` | `number` | `4` | Distance from the trigger in pixels |
| `align` | `"start" \| "center" \| "end"` | `"center"` | Alignment relative to the trigger |
| `alignOffset` | `number` | `0` | Offset from the alignment edge in pixels |
| `alignItemWithTrigger` | `boolean` | `true` | Align the selected item with the trigger |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Dropdown content |
All [Base UI Select.Popup props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectItem
An individual selectable option within the dropdown.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `any` | - | The value of this item |
| `disabled` | `boolean` | `false` | Whether the item is disabled |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Item label content |
All [Base UI Select.Item props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectGroup
Groups related items together with optional label.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Group content |
All [Base UI Select.Group props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectLabel
Label for a group of items.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Select.GroupLabel props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectSeparator
Visual divider between items or groups.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Select.Separator props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectScrollUpButton
Scroll indicator at the top of the dropdown list.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Select.ScrollUpArrow props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### SelectScrollDownButton
Scroll indicator at the bottom of the dropdown list.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Select.ScrollDownArrow props](https://base-ui.com/react/components/select) are forwarded via `...props`.
### selectTriggerVariants
A `cva` helper exported for use outside of the `` component (e.g., applying select trigger styles to custom elements).
```tsx
import { selectTriggerVariants } from "@/components/ui/select";
Custom trigger
;
```
## Accessibility
### Keyboard Interactions
| Key | Action |
| ----------- | ---------------------------------------------------------- |
| `Space` | Opens the dropdown or selects the highlighted item |
| `Enter` | Opens the dropdown or selects the highlighted item |
| `ArrowDown` | Opens the dropdown or moves highlight to the next item |
| `ArrowUp` | Opens the dropdown or moves highlight to the previous item |
| `Home` | Moves highlight to the first item |
| `End` | Moves highlight to the last item |
| `Escape` | Closes the dropdown |
| `Tab` | Closes the dropdown and moves focus to the next element |
### ARIA Attributes
- The trigger renders with `role="combobox"` and `aria-haspopup="listbox"`.
- `aria-expanded` is set to `true` when the dropdown is open.
- The popup receives `role="listbox"`.
- Each item receives `role="option"` with `aria-selected` indicating the current selection.
- `aria-disabled` is set on disabled items.
- Screen readers announce the currently selected value from the trigger.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="select.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A dropdown select field for choosing from a list of options",
props: z.object({
label: z.string().optional().describe("Label displayed above the select"),
placeholder: z
.string()
.optional()
.describe("Placeholder text when no value is selected"),
options: z
.array(
z.object({
label: z.string().describe("Display text for the option"),
value: z.string().describe("Value of the option"),
}),
)
.describe("List of selectable options"),
value: z.string().optional().describe("Currently selected value"),
disabled: z.boolean().optional().describe("Whether the select is disabled"),
}),
events: ["change"],
example: {
label: "Country",
placeholder: "Select a country...",
options: [
{ label: "United States", value: "us" },
{ label: "Canada", value: "ca" },
{ label: "United Kingdom", value: "uk" },
],
},
});
```
### Example Spec
```json
{
"root": "countrySelect",
"elements": {
"countrySelect": {
"type": "Select",
"props": {
"label": "Country",
"placeholder": "Select a country...",
"options": [
{ "label": "United States", "value": "us" },
{ "label": "Canada", "value": "ca" },
{ "label": "United Kingdom", "value": "uk" }
]
},
"$bindState": { "path": "/country", "event": "change" }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
selectTriggerVariants,
SelectValue,
} from "@prototyperco/ui/components/select";
```
# Separator
> a horizontal or vertical divider built on Base UI
URL: https://prototyper-ui.com/docs/components/separator
Base UI reference: https://base-ui.com/react/components/separator
```tsx
import { Separator } from "@/components/ui/separator";
export default function SeparatorDemo() {
return (
Prototyper UI
An open-source component library.
Docs
Source
Themes
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/separator.json
```
This will add the following files to your project:
- `components/ui/separator.tsx`
## Usage
```tsx
import { Separator } from "@/components/ui/separator";
```
```tsx
```
## Styling
### Data Slots
Use `data-slot` attributes to target the separator in CSS:
| Slot name | Element |
| ----------- | -------------------------- |
| `separator` | The separator `` root |
### Customization Examples
```css
/* Custom separator color */
[data-slot="separator"] {
@apply bg-primary/20;
}
/* Thicker horizontal separator */
[data-slot="separator"][data-horizontal] {
@apply h-0.5;
}
```
```tsx
```
## API Reference
### Separator
A visual divider between content sections.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | The orientation of the separator. |
| `className` | `string` | - | Additional CSS classes. |
All [Base UI Separator props](https://base-ui.com/react/components/separator) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
Separator is a non-interactive display element and does not have keyboard interactions.
### ARIA Attributes
- Renders with `role="separator"` via Base UI.
- `aria-orientation` is set to `"horizontal"` or `"vertical"` based on the `orientation` prop.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="separator.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A visual divider between sections of content",
props: z.object({
orientation: z
.enum(["horizontal", "vertical"])
.optional()
.describe("Direction of the separator line, defaults to horizontal"),
decorative: z
.boolean()
.optional()
.describe(
"If true, the separator is purely decorative and hidden from assistive technology",
),
}),
events: [],
example: { orientation: "horizontal" },
});
```
### Example Spec
```json
{
"root": "sep",
"elements": {
"sep": {
"type": "Separator",
"props": {
"orientation": "horizontal"
}
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export { Separator } from "@prototyperco/ui/components/separator";
```
# Skeleton
> An animated pulse placeholder for loading states
URL: https://prototyper-ui.com/docs/components/skeleton
Skeletons stand in for content while it's still loading. They preserve the layout of the real UI so the page doesn't jump when data arrives, and the soft pulse animation signals that something is happening without demanding attention. Use skeletons in place of spinners when you can predict the rough shape of the incoming content — a card, a row, an avatar — because matching the final layout reduces perceived wait time more than a generic loader.
```tsx
import { Skeleton } from "@/components/ui/skeleton";
export default function SkeletonDemo() {
return (
);
}
```
### When to use
- The shape of the data is known ahead of time (lists, cards, profile headers, table rows).
- The wait is long enough to be noticed (roughly 300ms or more) but short enough that a full-page state would be overkill.
- You want to avoid layout shift when content swaps in.
Prefer a spinner or progress indicator when the loading duration is unpredictable, when the layout cannot be approximated, or when the user has just triggered an explicit action like a form submission.
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/skeleton.json
```
This will add the following files to your project:
- `components/ui/skeleton.tsx`
## Usage
```tsx
import { Skeleton } from "@/components/ui/skeleton";
;
```
## Examples
### Card
```tsx
import { Skeleton } from "@/components/ui/skeleton";
export default function SkeletonCard() {
return (
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target the skeleton in CSS:
| Slot name | Element |
| ---------- | ---------------- |
| `skeleton` | The `` root |
### Customization Examples
```css
/* Change animation to a shimmer */
[data-slot="skeleton"] {
@apply animate-shimmer;
}
```
```tsx
{
/* Size and shape via className */
}
;
```
## API Reference
### Skeleton
An animated loading placeholder.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Controls dimensions and shape. Use Tailwind utilities like h-4 w-[200px] rounded-full. |
| `children` | `React.ReactNode` | - | Optional content (typically empty). |
Extends `React.ComponentProps<"div">`. All standard div props are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
Skeleton is a non-interactive presentational element and does not have keyboard interactions.
### ARIA Attributes
- Skeleton renders as a plain `
` with no implicit ARIA role.
- For screen readers, set `aria-busy="true"` on the parent container while loading and remove it when content arrives.
- If the skeleton represents content that will be announced (such as a status message), pair it with `aria-live="polite"` on the same container so assistive tech picks up the final value once it swaps in.
- Respect `prefers-reduced-motion`: the default pulse animation is subtle, but you can disable it entirely by overriding the animation utility for users who opt out.
## Full Component Source
```tsx
"use client";
export { Skeleton } from "@prototyperco/ui/components/skeleton";
```
# Slider
> A slider input for selecting values within a range
URL: https://prototyper-ui.com/docs/components/slider
Base UI reference: https://base-ui.com/react/components/slider
```tsx
import { FieldLabel } from "@/components/ui/field";
import {
Slider,
SliderControl,
SliderIndicator,
SliderOutput,
SliderThumb,
SliderTrack,
} from "@/components/ui/slider";
export default function SliderDemo() {
return (
Opacity
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/slider.json
```
This will add the following files to your project:
- `components/ui/slider.tsx`
## Usage
```tsx
import {
Slider,
SliderControl,
SliderTrack,
SliderIndicator,
SliderThumb,
} from "@/components/ui/slider";
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ----------------- | ------------------ | ---------------------------------- | -------- |
| `Slider` | `slider` | Root provider, manages value state | Yes |
| `SliderControl` | `slider-control` | Touch/pointer interaction area | Yes |
| `SliderTrack` | `slider-track` | The visible track bar | Yes |
| `SliderIndicator` | `slider-indicator` | Filled portion of the track | Yes |
| `SliderThumb` | `slider-thumb` | Draggable thumb handle | Yes |
| `SliderOutput` | `slider-output` | Displays the current value | No |
## Examples
### Disabled
```tsx
import { FieldLabel } from "@/components/ui/field";
import {
Slider,
SliderControl,
SliderIndicator,
SliderThumb,
SliderTrack,
} from "@/components/ui/slider";
export default function SliderDisabled() {
return (
Cookies to share
);
}
```
### Step Values
```tsx
import { FieldLabel } from "@/components/ui/field";
import {
Slider,
SliderControl,
SliderIndicator,
SliderThumb,
SliderTrack,
} from "@/components/ui/slider";
export default function SliderStepValues() {
return (
Amount
);
}
```
### Values
```tsx
import { FieldLabel } from "@/components/ui/field";
import {
Slider,
SliderControl,
SliderIndicator,
SliderOutput,
SliderThumb,
SliderTrack,
} from "@/components/ui/slider";
export default function SliderValues() {
return (
Cookies to Buy
);
}
```
### Vertical
```tsx
import {
Slider,
SliderControl,
SliderIndicator,
SliderThumb,
SliderTrack,
} from "@/components/ui/slider";
export default function SliderVertical() {
return (
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the slider:
| Slot name | Element |
| ------------------ | ------------------------------ |
| `slider` | Root wrapper |
| `slider-control` | Touch/pointer interaction area |
| `slider-track` | The visible track bar |
| `slider-indicator` | Filled portion of the track |
| `slider-thumb` | Draggable thumb handle |
| `slider-output` | Current value display |
### Customization Examples
```css
/* Custom track height */
[data-slot="slider-track"] {
@apply h-3 rounded-lg;
}
/* Custom indicator color */
[data-slot="slider-indicator"] {
@apply bg-green-600;
}
```
```tsx
{
/* Override styles via className */
}
;
```
## API Reference
### Slider
Root component that manages value state and layout.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `number \| number[]` | - | Controlled value |
| `defaultValue` | `number \| number[]` | - | Initial value for uncontrolled usage |
| `onValueChange` | `(value: number \| number[], event: Event) => void` | - | Callback when value changes |
| `onValueCommitted` | `(value: number \| number[], event: Event) => void` | - | Callback when interaction ends (e.g. pointer up) |
| `min` | `number` | `0` | Minimum value |
| `max` | `number` | `100` | Maximum value |
| `step` | `number` | `1` | Step increment |
| `largeStep` | `number` | `10` | Step increment for Page Up / Page Down |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Orientation of the slider |
| `disabled` | `boolean` | `false` | Whether the slider is disabled |
| `name` | `string` | - | Name attribute for form submission |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Slider props](https://base-ui.com/react/components/slider) are forwarded via `...props`.
### SliderControl
The area that receives touch and pointer events for dragging.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Slider.Control props](https://base-ui.com/react/components/slider) are forwarded via `...props`.
### SliderTrack
The visible bar that represents the full range.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Slider.Track props](https://base-ui.com/react/components/slider) are forwarded via `...props`.
### SliderIndicator
The filled portion of the track that shows the current value.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Slider.Indicator props](https://base-ui.com/react/components/slider) are forwarded via `...props`.
### SliderThumb
The draggable handle that the user interacts with.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Slider.Thumb props](https://base-ui.com/react/components/slider) are forwarded via `...props`.
### SliderOutput
Displays the current slider value as text.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Slider.Value props](https://base-ui.com/react/components/slider) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | ------------------------------------- |
| `ArrowRight` | Increases the value by one step |
| `ArrowUp` | Increases the value by one step |
| `ArrowLeft` | Decreases the value by one step |
| `ArrowDown` | Decreases the value by one step |
| `PageUp` | Increases the value by one large step |
| `PageDown` | Decreases the value by one large step |
| `Home` | Sets the value to the minimum |
| `End` | Sets the value to the maximum |
| `Tab` | Moves focus to / away from the thumb |
### ARIA Attributes
- `Slider` renders with `role="group"` via Base UI.
- `SliderThumb` renders with `role="slider"`.
- `aria-valuenow`, `aria-valuemin`, and `aria-valuemax` are set on the thumb.
- `aria-orientation` is set based on the `orientation` prop.
- `aria-disabled` is set when the slider is disabled.
- `SliderOutput` is linked to the thumb via `aria-describedby` for screen reader announcements.
- Screen readers announce the current value and range when the thumb receives focus.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="slider.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description:
"A range slider input for selecting a numeric value within a range",
props: z.object({
label: z.string().optional().describe("Label displayed above the slider"),
min: z.number().optional().describe("Minimum value"),
max: z.number().optional().describe("Maximum value"),
step: z.number().optional().describe("Step increment"),
value: z.number().optional().describe("Current slider value (bindable)"),
disabled: z.boolean().optional().describe("Whether the slider is disabled"),
}),
events: ["change"],
example: { label: "Volume", min: 0, max: 100, step: 1, value: 50 },
});
```
### Example Spec
```json
{
"root": "volumeSlider",
"elements": {
"volumeSlider": {
"type": "Slider",
"props": {
"label": "Volume",
"min": 0,
"max": 100,
"step": 1,
"value": 50
},
"$bindState": { "path": "/volume", "event": "change" }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export {
Slider,
SliderControl,
SliderTrack,
SliderIndicator,
SliderThumb,
SliderOutput,
} from "@prototyperco/ui/components/slider";
```
# Spinner
> An SVG loading spinner with size variants
URL: https://prototyper-ui.com/docs/components/spinner
```tsx
import { Spinner } from "@/components/ui/spinner";
export default function SpinnerDemo() {
return ;
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/spinner.json
```
This will add the following files to your project:
- `components/ui/spinner.tsx`
## Usage
```tsx
import { Spinner } from "@/components/ui/spinner";
;
```
## Examples
### Sizes
```tsx
import { Spinner } from "@/components/ui/spinner";
export default function SpinnerSizes() {
return (
);
}
```
### In Button
```tsx
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
export default function SpinnerButton() {
return (
Loading...
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target the spinner in CSS:
| Slot name | Element |
| --------- | ---------------- |
| `spinner` | The `` root |
### Customization Examples
```css
/* Change spinner color */
[data-slot="spinner"] {
@apply text-primary;
}
```
```tsx
{
/* Custom color via className */
}
;
```
## API Reference
### Spinner
An SVG loading spinner with rotation animation.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size of the spinner (sm=16px, md=24px, lg=32px). |
| `className` | `string` | - | Additional CSS classes. |
Extends `React.ComponentProps<"svg">`. All standard SVG props are forwarded via `...props`.
### spinnerVariants
A `cva` helper exported for use outside of the `` component.
## Accessibility
### Keyboard Interactions
Spinner is a non-interactive presentational element and does not have keyboard interactions.
### ARIA Attributes
- Add `role="status"` and `aria-label="Loading"` when the spinner indicates an ongoing operation.
- Use `aria-busy="true"` on the parent container while loading.
## Full Component Source
```tsx
"use client";
export { Spinner, spinnerVariants } from "@prototyperco/ui/components/spinner";
```
# Switch
> A toggle switch built on Base UI
URL: https://prototyper-ui.com/docs/components/switch
Base UI reference: https://base-ui.com/react/components/switch
```tsx
import { Switch, SwitchTrack, SwitchThumb } from "@/components/ui/switch";
export default function SwitchDemo() {
return (
Low power mode
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/switch.json
```
This will add the following files to your project:
- `components/ui/switch.tsx`
## Usage
```tsx
import { Switch, SwitchTrack, SwitchThumb } from "@/components/ui/switch";
Airplane Mode
;
```
## Anatomy
```tsx
{/* optional */}
{/* label text */}
```
| Sub-component | `data-slot` | Purpose | Required |
| ------------- | -------------- | -------------------------------------------- | -------- |
| `Switch` | `switch` | Root component with label and switch control | Yes |
| `SwitchTrack` | `switch-track` | The sliding track background | Yes |
| `SwitchThumb` | `switch-thumb` | The draggable thumb indicator | Yes |
| `SwitchIcon` | `switch-icon` | Optional icon wrapper inside the thumb | No |
## Examples
### Sizes
```tsx
import { Switch, SwitchTrack, SwitchThumb } from "@/components/ui/switch";
export default function SwitchSizes() {
return (
Small
Medium
Large
);
}
```
### 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`.
### SwitchTrack
The sliding track background container that holds the thumb.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Track size, defaults to size set on Switch root |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Content inside the track (typically SwitchThumb) |
All standard HTML `div` props are supported via `...props`.
### SwitchThumb
The circular thumb indicator that slides inside the track.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Content inside the thumb (typically SwitchIcon) |
All [Base UI Switch.Thumb props](https://base-ui.com/react/components/switch) are forwarded via `...props`.
### SwitchIcon
Optional icon wrapper for displaying icons inside the thumb.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Icon elements to display |
All standard HTML `span` props are supported via `...props`. The component automatically hides from screen readers with `aria-hidden="true"`.
## 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.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="switch.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A toggle switch for on/off states",
props: z.object({
label: z.string().optional().describe("Label displayed next to the switch"),
checked: z.boolean().optional().describe("Whether the switch is on"),
disabled: z.boolean().optional().describe("Whether the switch is disabled"),
size: z.enum(["sm", "md", "lg"]).optional().describe("Switch size variant"),
}),
events: ["change"],
example: { label: "Enable notifications", checked: false, size: "md" },
});
```
### Example Spec
```json
{
"root": "notifSwitch",
"elements": {
"notifSwitch": {
"type": "Switch",
"props": {
"label": "Enable notifications",
"checked": false,
"size": "md"
},
"$bindState": { "path": "/notificationsEnabled", "event": "change" }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export {
Switch,
SwitchTrack,
SwitchThumb,
SwitchIcon,
switchVariants,
} from "@prototyperco/ui/components/switch";
```
# 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 (
Account
Notifications
Billing
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 1
Tab 2
Content 1
Content 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 Settings
Keyboard Settings
Gamepad Settings
Mouse Settings
Keyboard Settings
Gamepad Settings
);
}
```
### Disabled Dynamic
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
export default function TabsDisabledDynamic() {
const 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 Settings
Keyboard Settings
Gamepad Settings
Mouse Settings
Keyboard Settings
Gamepad 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() {
const [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" },
]);
const addTab = () => {
setTabs((tabs) => [
...tabs,
{
id: tabs.length + 1,
title: `Tab ${tabs.length + 1}`,
content: `Tab body ${tabs.length + 1}`,
},
]);
};
const 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/text-field";
export default function TabsFocus() {
return (
Jane Doe
John Doe
Joe Bloggs
Leave a note for Jane:
Senatus Populusque Romanus.
Alea jacta est.
);
}
```
### Vertical
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
export default function TabsVertical() {
return (
John Doe
Jane Doe
Joe Bloggs
There 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 1
Tab 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.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="tabs.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A tabbed interface for switching between content panels",
props: z.object({
tabs: z
.array(
z.object({
label: z.string().describe("Tab button label"),
value: z.string().describe("Tab identifier value"),
}),
)
.describe("Tab definitions"),
value: z.string().optional().describe("Active tab value (bindable)"),
variant: z
.enum(["default", "line"])
.optional()
.describe("Visual style variant"),
}),
events: ["change"],
example: {
tabs: [
{ label: "Account", value: "account" },
{ label: "Settings", value: "settings" },
{ label: "Billing", value: "billing" },
],
value: "account",
},
});
```
### Example Spec
```json
{
"root": "tabsContainer",
"elements": {
"tabsContainer": {
"type": "Tabs",
"props": {
"tabs": [
{ "label": "Account", "value": "account" },
{ "label": "Settings", "value": "settings" },
{ "label": "Billing", "value": "billing" }
],
"value": "account"
},
"children": ["accountContent", "settingsContent", "billingContent"],
"$bindState": { "path": "/activeTab", "event": "change" }
},
"accountContent": {
"type": "Text",
"props": { "content": "Manage your account details here." }
},
"settingsContent": {
"type": "Text",
"props": { "content": "Configure your preferences." }
},
"billingContent": {
"type": "Text",
"props": { "content": "View and manage your billing." }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
tabsListVariants,
} from "@prototyperco/ui/components/tabs";
```
## All Examples
### tabs-line
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
export default function TabsLine() {
return (
Overview
Activity
Settings
A summary of your project status and key metrics.
Recent commits, pull requests, and team updates.
Configure repository access, webhooks, and integrations.
);
}
```
### tabs-settings
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import { FieldLabel } from "@/components/ui/field";
import { Input, TextField } from "@/components/ui/text-field";
import { Button } from "@/components/ui/button";
export default function TabsSettings() {
return (
Account
Password
Name
Email
Save changes
Current password
New password
Update password
);
}
```
### tabs-with-icons
```tsx
import { Music, ImageIcon, Video } from "lucide-react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
export default function TabsWithIcons() {
return (
Music
Photos
Videos
Browse and manage your music collection.
View, organize, and share your photo albums.
Watch and manage your saved video clips.
);
}
```
# TextField
> A text input with label and validation built on Base UI
URL: https://prototyper-ui.com/docs/components/text-field
Base UI reference: https://base-ui.com/react/components/field
```tsx
import { FieldLabel } from "@/components/ui/field";
import { Input, TextField } from "@/components/ui/text-field";
export default function TextFieldDemo() {
return (
First name
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/text-field.json
```
This will add the following files to your project:
- `components/ui/text-field.tsx`
> **Note:** This component depends on [Field](/docs/components/field). It will be installed automatically.
## Usage
```tsx
import { TextField, Input } from "@/components/ui/text-field";
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/text-field";
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/text-field";
export default function TextFieldDisabled() {
return (
Email
);
}
```
### Multiline
```tsx
import { FieldLabel } from "@/components/ui/field";
import { TextArea, TextField } from "@/components/ui/text-field";
export default function TextFieldMultiline() {
return (
Comment
);
}
```
### Read Only
```tsx
import { FieldLabel } from "@/components/ui/field";
import { Input, TextField } from "@/components/ui/text-field";
export default function TextFieldReadonly() {
return (
Email
);
}
```
### Reusable
```tsx
import { ProtoTextField } from "@/components/ui/text-field";
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/text-field";
export default function TextFieldValidation() {
return (
Email
Submit
);
}
```
## 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 `` element |
### Customization Examples
```css
/* Remove border and shadow from all inputs */
[data-slot="input"] {
@apply border-0 shadow-none;
}
/* Custom focus ring color */
[data-slot="input"]:focus {
@apply ring-2 ring-blue-500;
}
```
```tsx
{
/* Override size via className */
}
;
```
## API Reference
### TextField
Root wrapper that provides form field context. Built on Base UI `Field.Root`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Field content (label, input, etc.) |
All [Base UI Field props](https://base-ui.com/react/components/field) are forwarded via `...props`.
### Input
A styled single-line text input built on Base UI `Input`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Size of the input |
| `className` | `string` | - | Additional CSS classes |
All [Base UI Input props](https://base-ui.com/react/components/input) are forwarded via `...props`.
### TextArea
A styled multi-line text area using a native `` element.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All native `textarea` props are forwarded via `...props`.
### ProtoTextField
A convenience wrapper that composes `TextField`, `Input`/`TextArea`, `FieldLabel`, `FieldDescription`, and `FieldError` into a single component.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | - | Label text for the field |
| `description` | `string` | - | Helper description text |
| `errorMessage` | `string` | - | Validation error message |
| `textArea` | `boolean` | - | Render a TextArea instead of Input |
| `inputProps` | `InputPrimitive.Props & VariantProps` | - | Props passed to the Input sub-component |
| `textAreaProps` | `React.ComponentProps<"textarea">` | - | Props passed to the TextArea sub-component |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Custom content (replaces Input/TextArea) |
All [Base UI Field props](https://base-ui.com/react/components/field) are forwarded via `...props`.
### inputVariants
A `cva` helper exported for applying input styles outside of the ` ` component.
```tsx
import { inputVariants } from "@/components/ui/text-field";
;
```
## Accessibility
### Keyboard Interactions
| Key | Action |
| ----- | ------------------------------------ |
| `Tab` | Moves focus into or out of the input |
### ARIA Attributes
- `TextField` renders as a Base UI `Field.Root`, automatically associating the label, description, and error message with the input via `aria-labelledby`, `aria-describedby`, and `aria-errormessage`.
- When validation fails, `data-invalid` is set on the input and the field root.
- `data-disabled` is set when the field is disabled.
- Screen readers announce the label, description, and any error messages associated with the input.
## Full Component Source
```tsx
"use client";
export {
Input,
inputVariants,
TextField,
ProtoTextField,
TextArea,
} from "@prototyperco/ui/components/text-field";
```
# Textarea
> a multi-line text input with validation styling
URL: https://prototyper-ui.com/docs/components/textarea
```tsx
import { Textarea } from "@/components/ui/textarea";
export default function TextareaDemo() {
return ;
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/textarea.json
```
This will add the following files to your project:
- `components/ui/textarea.tsx`
## Usage
```tsx
import { Textarea } from "@/components/ui/textarea";
```
```tsx
```
## Styling
### Data Slots
Use `data-slot` attributes to target the textarea in CSS:
| Slot name | Element |
| ---------- | --------------------- |
| `textarea` | The `` root |
### Customization Examples
```css
/* Make all textareas taller */
[data-slot="textarea"] {
@apply min-h-32 text-lg;
}
```
```tsx
{
/* Override styles via className */
}
;
```
## API Reference
### Textarea
A styled multi-line text input.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `placeholder` | `string` | - | Placeholder text shown when empty. |
| `className` | `string` | - | Additional CSS classes. |
All native `textarea` props are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ----- | --------------------------------------- |
| `Tab` | Moves focus into or out of the textarea |
### ARIA Attributes
- Renders as a native `` element.
- `aria-invalid` styling is applied when the textarea has validation errors.
- Pair with a `` or `aria-label` to ensure the textarea is accessible to screen readers.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="textarea.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description: "A multi-line text area for longer form input",
props: z.object({
label: z.string().optional().describe("Label displayed above the textarea"),
placeholder: z.string().optional().describe("Placeholder text"),
value: z.string().optional().describe("Controlled textarea value"),
rows: z.number().optional().describe("Number of visible text rows"),
disabled: z
.boolean()
.optional()
.describe("Whether the textarea is disabled"),
}),
events: ["change", "blur", "focus"],
example: { label: "Message", placeholder: "Type your message...", rows: 3 },
});
```
### Example Spec
```json
{
"root": "messageArea",
"elements": {
"messageArea": {
"type": "Textarea",
"props": {
"label": "Message",
"placeholder": "Type your message...",
"rows": 3
},
"$bindState": { "path": "/message", "event": "change" }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export { Textarea } from "@prototyperco/ui/components/textarea";
```
# Toast
> notification system with type variants
URL: https://prototyper-ui.com/docs/components/toast
Base UI reference: https://base-ui.com/react/components/toast
```tsx
"use client";
import { toast } from "@/components/ui/toast";
import { Button } from "@/components/ui/button";
export default function ToastDemo() {
return (
toast("Event has been created", {
description: "Sunday, December 03, 2024 at 9:00 AM",
})
}
>
Show Toast
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/toast.json
```
This will add the following files to your project:
- `components/ui/toast.tsx`
### Setup
Wrap your application with `ToastProvider` in your root layout:
```tsx
import { ToastProvider } from "@/components/ui/toast";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
```
## Usage
```tsx
import { toast } from "@/components/ui/toast";
// Basic toast
toast("Event has been created");
// With description
toast("Event has been created", {
description: "Sunday, December 03, 2024 at 9:00 AM",
});
// Typed variants
toast.success("Action completed successfully");
toast.error("Something went wrong");
toast.warning("Please review before continuing");
toast.info("A new update is available");
```
## Anatomy
```tsx
{/* Your app content */}
{/* Toaster is rendered automatically inside ToastProvider */}
```
The toast system consists of three layers:
| Export | `data-slot` | Purpose | Required |
| --------------- | ----------- | ------------------------------------------------------------------ | -------- |
| `ToastProvider` | — | Root provider that wraps your app, renders `Toaster` automatically | Yes |
| `Toaster` | `toaster` | Viewport that positions and renders all active toasts | Auto |
| `toast` | — | Imperative API to create toasts from anywhere | Yes |
Each individual toast renders the following structure:
| Part | `data-slot` | Purpose |
| ------------------- | ------------------- | ----------------------------------------- |
| `Toast.Root` | `toast` | Container for a single toast notification |
| Icon | `toast-icon` | Type-specific icon (success, error, etc.) |
| Content wrapper | `toast-content` | Flex container for title and description |
| `Toast.Title` | `toast-title` | Bold title text |
| `Toast.Description` | `toast-description` | Secondary description text |
| `Toast.Action` | `toast-action` | Optional action button |
| `Toast.Close` | `toast-close` | Dismiss button |
## Examples
### Variants
```tsx
"use client";
import { toast } from "@/components/ui/toast";
import { Button } from "@/components/ui/button";
export default function ToastVariants() {
return (
toast("This is a default toast")}
>
Default
toast.success("Action completed successfully")}
>
Success
toast.error("Something went wrong")}
>
Error
toast.warning("Please review before continuing")}
>
Warning
toast.info("A new update is available")}
>
Info
);
}
```
### With Action
```tsx
"use client";
import { toast } from "@/components/ui/toast";
import { Button } from "@/components/ui/button";
export default function ToastAction() {
return (
toast("File deleted", {
description: "The file has been moved to trash.",
action: {
label: "Undo",
onClick: () => toast.success("File restored"),
},
})
}
>
Show Toast with Action
);
}
```
### Promise
```tsx
"use client";
import { toast } from "@/components/ui/toast";
import { Button } from "@/components/ui/button";
export default function ToastPromise() {
function handleClick() {
const promise = new Promise<{ name: string }>((resolve) =>
setTimeout(() => resolve({ name: "Prototyper UI" }), 2000),
);
toast.promise(promise, {
loading: { title: "Loading..." },
success: (data) => ({ title: `${data.name} has been added` }),
error: { title: "Something went wrong" },
});
}
return (
Show Promise Toast
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the toast:
| Slot name | Element |
| ------------------- | ------------------------------------- |
| `toaster` | The viewport container for all toasts |
| `toast` | Individual toast root |
| `toast-icon` | Type-specific icon |
| `toast-content` | Content wrapper (title + description) |
| `toast-title` | Title text |
| `toast-description` | Description text |
| `toast-action` | Action button |
| `toast-close` | Dismiss button |
### Customization Examples
```css
/* Change toast position to top-center */
[data-slot="toaster"] {
@apply bottom-auto top-4 right-auto left-1/2 -translate-x-1/2;
}
/* Wider toasts */
[data-slot="toaster"] {
@apply w-[420px];
}
/* Custom success icon color */
[data-slot="toast-icon"] {
@apply text-foreground;
}
```
```tsx
{
/* Custom duration */
}
toast("Quick notification", { duration: 2000 });
{
/* With action button */
}
toast("Item deleted", {
action: {
label: "Undo",
onClick: () => console.log("Undo clicked"),
},
});
```
## API Reference
### ToastProvider
Root provider that wraps your application. Renders the `Toaster` viewport automatically.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `React.ReactNode` | - | Your application content |
### toast(title, options?)
Imperative function to create a toast notification. Can be called from anywhere in your app.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` | - | The toast title text |
### toast.success / toast.error / toast.warning / toast.info
Typed variants that display a corresponding icon. Accept the same `(title, options?)` signature as `toast()`.
| Method | Icon | Color |
| --------------- | ------------------- | ----------- |
| `toast.success` | `CircleCheckIcon` | Emerald 500 |
| `toast.error` | `OctagonXIcon` | Red 500 |
| `toast.warning` | `TriangleAlertIcon` | Amber 500 |
| `toast.info` | `InfoIcon` | Blue 500 |
### toast.promise(promise, options)
Tracks a promise and shows loading, success, and error states.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `promise` | `Promise` | - | The promise to track |
### Toaster
The viewport component that renders all active toasts. Rendered automatically by `ToastProvider`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | CSS class name for the viewport container |
All [Base UI Toast Viewport props](https://base-ui.com/react/components/toast) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| -------- | --------------------------- |
| `Escape` | Dismisses all active toasts |
### ARIA Attributes
- Each toast is rendered as an ARIA live region via Base UI's `Toast.Root`.
- `Toast.Title` is used as the accessible label for the toast.
- `Toast.Description` provides additional context.
- `Toast.Close` includes `aria-label="Dismiss"` for screen readers.
- Toasts support swipe-to-dismiss gestures on touch devices.
- Motion is reduced when the user has `prefers-reduced-motion` enabled.
### Screen Reader Behavior
- New toasts are announced automatically via ARIA live regions.
- The `Toast.Action` button is focusable and announced with its label.
- The close button announces "Dismiss" to assistive technology.
## Full Component Source
```tsx
"use client";
export {
ToastProvider,
Toaster,
toast,
} from "@prototyperco/ui/components/toast";
```
# 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 { Bold } from "lucide-react";
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 { Italic } from "lucide-react";
import { Toggle } from "@/components/ui/toggle";
export default function ToggleOutline() {
return (
);
}
```
### Small
```tsx
import { Italic } from "lucide-react";
import { Toggle } from "@/components/ui/toggle";
export default function ToggleSm() {
return (
);
}
```
### Large
```tsx
import { Italic } from "lucide-react";
import { Toggle } from "@/components/ui/toggle";
export default function ToggleLg() {
return (
);
}
```
### Disabled
```tsx
import { Underline } from "lucide-react";
import { Toggle } from "@/components/ui/toggle";
export default function ToggleDisabled() {
return (
);
}
```
### With Text
```tsx
import { Italic } from "lucide-react";
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.
## Full Component Source
```tsx
"use client";
export { Toggle, toggleVariants } from "@prototyperco/ui/components/toggle";
```
# Toggle Group
> group of toggles with single or multiple selection
URL: https://prototyper-ui.com/docs/components/toggle-group
Base UI reference: https://base-ui.com/react/components/toggle-group
```tsx
import { AlignCenter, AlignLeft, AlignRight } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function ToggleGroupDemo() {
return (
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/toggle-group.json
```
This will add the following files to your project:
- `components/ui/toggle-group.tsx`
## Usage
```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
Left
Center
Right
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| ----------------- | ------------------- | ----------------------------------------- | -------- |
| `ToggleGroup` | `toggle-group` | Root container, manages selection state | Yes |
| `ToggleGroupItem` | `toggle-group-item` | Individual toggle button within the group | Yes |
## Examples
### Outline
```tsx
import { AlignCenter, AlignLeft, AlignRight } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function ToggleGroupOutline() {
return (
);
}
```
### Multiple
```tsx
import { Bold, Italic, Strikethrough, Underline } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function ToggleGroupMultiple() {
return (
);
}
```
### Vertical
```tsx
import { AlignCenter, AlignJustify, AlignLeft, AlignRight } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function ToggleGroupVertical() {
return (
);
}
```
### Small
```tsx
import { AlignCenter, AlignLeft, AlignRight } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function ToggleGroupSm() {
return (
);
}
```
### Large
```tsx
import { AlignCenter, AlignLeft, AlignRight } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function ToggleGroupLg() {
return (
);
}
```
### Disabled
```tsx
import { AlignCenter, AlignLeft, AlignRight } from "lucide-react";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
export default function ToggleGroupDisabled() {
return (
);
}
```
## Styling
### Data Slots
Use `data-slot` attributes to target specific parts of the toggle group:
| Slot name | Element |
| ------------------- | ----------------------------------- |
| `toggle-group` | Root container for all toggle items |
| `toggle-group-item` | Individual toggle button |
### Customization Examples
```css
/* Style all pressed items in a toggle group */
[data-slot="toggle-group-item"][data-state="on"] {
@apply bg-primary text-primary-foreground;
}
/* Add spacing between items */
[data-slot="toggle-group"] {
@apply gap-1;
}
```
```tsx
{
/* Override styles via className */
}
A
B
;
```
## API Reference
### ToggleGroup
Root container that manages which toggle items are pressed.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string[]` | - | Controlled pressed values |
| `defaultValue` | `string[]` | - | Initial pressed values for uncontrolled usage |
| `onValueChange` | `(value: string[]) => void` | - | Callback when pressed values change |
| `multiple` | `boolean` | `false` | Whether multiple items can be pressed simultaneously |
| `disabled` | `boolean` | `false` | Whether all toggle items are disabled |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction of the toggle group |
| `variant` | `"default" \| "outline"` | `"default"` | Visual style variant applied to all items |
| `size` | `"default" \| "sm" \| "lg"` | `"default"` | Size applied to all items |
| `spacing` | `number` | `0` | Gap between items (0 creates a joined look) |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Toggle group content |
All [Base UI ToggleGroup props](https://base-ui.com/react/components/toggle-group) are forwarded via `...props`.
### ToggleGroupItem
An individual toggle button within the group.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | - | Unique value for this toggle item (required) |
| `variant` | `"default" \| "outline"` | `"default"` | Visual style variant (overridden by group variant) |
| `size` | `"default" \| "sm" \| "lg"` | `"default"` | Size of the toggle item (overridden by group size) |
| `disabled` | `boolean` | `false` | Whether this individual item is disabled |
| `className` | `string` | - | Additional CSS classes |
| `children` | `React.ReactNode` | - | Toggle item content |
All [Base UI Toggle props](https://base-ui.com/react/components/toggle) are forwarded via `...props`.
## Accessibility
### Keyboard Interactions
| Key | Action |
| ------------ | --------------------------------------------------------- |
| `Space` | Toggles the pressed state of the focused item |
| `Enter` | Toggles the pressed state of the focused item |
| `Tab` | Moves focus into / out of the toggle group |
| `ArrowRight` | Moves focus to the next item (horizontal orientation) |
| `ArrowLeft` | Moves focus to the previous item (horizontal orientation) |
| `ArrowDown` | Moves focus to the next item (vertical orientation) |
| `ArrowUp` | Moves focus to the previous item (vertical orientation) |
| `Home` | Moves focus to the first item |
| `End` | Moves focus to the last item |
### ARIA Attributes
- `ToggleGroup` renders as a `group` with `aria-label` describing the group purpose.
- Each `ToggleGroupItem` renders as a `` with `aria-pressed` attribute.
- `aria-pressed="true"` when the item is in the pressed state, `aria-pressed="false"` when unpressed.
- `aria-disabled` is set when the group or an individual item is disabled.
- `aria-orientation` is set to match the `orientation` prop.
- Focus management follows the roving tabindex pattern so only one item is tabbable at a time.
## Full Component Source
```tsx
"use client";
export {
ToggleGroup,
ToggleGroupItem,
} from "@prototyperco/ui/components/toggle-group";
```
# Toolbar
> A toolbar 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 { Bold, Italic, Underline } from "lucide-react";
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 (
Copy
Paste
Cut
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";
Bold
Italic
Link
;
```
## 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 { MousePointer, Move, PenLine, Pencil, Wand2 } from "lucide-react";
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.
## Full Component Source
```tsx
"use client";
export {
Toolbar,
ToolbarButton,
ToolbarLink,
ToolbarGroup,
ToolbarSeparator,
ToolbarInput,
} from "@prototyperco/ui/components/toolbar";
```
# 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 { PencilIcon } from "lucide-react";
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 me
Tooltip 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 "lucide-react";
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 { PencilIcon } from "lucide-react";
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 "lucide-react";
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.
## Compose
This component is available in [`@prototyperco/compose`](/docs/compose).
### Catalog Definition
```typescript title="tooltip.catalog.ts"
import { z } from "zod";
import { defineComponent } from "@prototyperco/compose/catalog";
export default defineComponent({
description:
"A tooltip that displays informational text when hovering over its trigger content",
props: z.object({
content: z.string().describe("Tooltip text to display on hover"),
}),
events: [],
example: { content: "More information" },
});
```
### Example Spec
```json
{
"root": "tip",
"elements": {
"tip": {
"type": "Tooltip",
"props": {
"content": "More information"
},
"children": ["triggerBtn"]
},
"triggerBtn": {
"type": "Button",
"props": { "label": "Hover me", "variant": "outline" }
}
}
}
```
Learn more in the [Compose documentation](/docs/compose).
## Full Component Source
```tsx
"use client";
export {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipProvider,
} from "@prototyperco/ui/components/tooltip";
```
# Tree View
> A hierarchical tree view with collapsible nodes and keyboard navigation built on Base UI
URL: https://prototyper-ui.com/docs/components/tree-view
Base UI reference: https://base-ui.com/react/components/collapsible
```tsx
import {
TreeView,
TreeViewItem,
TreeViewGroup,
TreeViewLeaf,
} from "@/components/ui/tree-view";
export default function TreeViewDemo() {
return (
button.tsx
dialog.tsx
input.tsx
utils.ts
app.tsx
index.tsx
favicon.ico
index.html
package.json
tsconfig.json
);
}
```
## Installation
```bash
pnpm dlx shadcn@latest add https://prototyper-ui.com/r/tree-view.json
```
This will add the following files to your project:
- `components/ui/tree-view.tsx`
## Usage
```tsx
import {
TreeView,
TreeViewItem,
TreeViewGroup,
TreeViewLeaf,
} from "@/components/ui/tree-view";
index.tsx
app.tsx
package.json
;
```
## Anatomy
```tsx
```
| Sub-component | `data-slot` | Purpose | Required |
| --------------- | ----------------- | ------------------------------------------- | -------- |
| `TreeView` | `tree-view` | Root container with keyboard navigation | Yes |
| `TreeViewItem` | `tree-view-item` | Collapsible node with chevron and label | Yes |
| `TreeViewGroup` | `tree-view-group` | Animated collapsible container for children | Yes |
| `TreeViewLeaf` | `tree-view-leaf` | Terminal node (no children) | No |
## Examples
### Default expanded
```tsx
getting-started.md
api-reference.md
```
### Controlled expanded keys
```tsx
const [expandedKeys, setExpandedKeys] = useState(new Set(["src"]))
index.tsx
```
## Styling
### Data Slots
| Slot name | Element |
| ---------------------- | --------------------------------------- |
| `tree-view` | Root `div` with `role="tree"` |
| `tree-view-item` | Collapsible node row with chevron |
| `tree-view-item-icon` | Chevron icon (rotates when expanded) |
| `tree-view-item-label` | Label text |
| `tree-view-group` | Animated collapsible panel for children |
| `tree-view-leaf` | Terminal item row |
### Customization Examples
```css
/* Custom item hover color */
[data-slot="tree-view-item"]:hover,
[data-slot="tree-view-leaf"]:hover {
@apply bg-primary/10;
}
/* Custom chevron color */
[data-slot="tree-view-item-icon"] {
@apply text-primary;
}
```
## API Reference
### TreeView
Root container that provides context, keyboard navigation, and expanded state management.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `expandedKeys` | `Set` | - | Controlled set of expanded item keys |
| `onExpandedKeysChange` | `(keys: Set) => void` | - | Callback when expanded keys change |
| `defaultExpandedKeys` | `Set` | - | Default expanded keys (uncontrolled) |
| `defaultExpandAll` | `boolean` | `false` | Expand all nodes by default |
| `className` | `string` | - | Additional CSS classes |
All standard `div` props are forwarded.
### TreeViewItem
Collapsible node with a chevron icon and label. Uses Base UI `Collapsible.Root` internally.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `itemKey` | `string` | - | Unique key for this item (used for expand/collapse tracking) |
| `label` | `React.ReactNode` | - | Label content displayed next to the chevron |
| `depth` | `number` | - | Depth level (auto-managed, can override) |
| `className` | `string` | - | Additional CSS classes |
All standard `div` props are forwarded.
### TreeViewGroup
Animated collapsible container for child items. Uses Base UI `Collapsible.Panel`.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `className` | `string` | - | Additional CSS classes |
All standard `div` props are forwarded.
### TreeViewLeaf
Terminal node (no children). Renders with extra left padding to align with item labels.
| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `depth` | `number` | - | Depth level (auto-managed, can override) |
| `className` | `string` | - | Additional CSS classes |
All standard `div` props are forwarded.
## Accessibility
### Keyboard Interactions
| Key | Action |
| --------------- | ------------------------------------------------------------- |
| `ArrowDown` | Moves focus to the next visible item |
| `ArrowUp` | Moves focus to the previous visible item |
| `ArrowRight` | Expands a collapsed item, or moves to first child if expanded |
| `ArrowLeft` | Collapses an expanded item, or moves to parent |
| `Home` | Moves focus to the first visible item |
| `End` | Moves focus to the last visible item |
| `Enter`/`Space` | Toggles expand/collapse on a tree item |
### ARIA Attributes
- The root element has `role="tree"`.
- Each `TreeViewItem` and `TreeViewLeaf` has `role="treeitem"`.
- `TreeViewItem` has `aria-expanded` reflecting its expand state.
- `TreeViewGroup` has `role="group"`.
- Depth-based indentation provides visual hierarchy.
## Full Component Source
```tsx
"use client";
export {
TreeView,
TreeViewItem,
TreeViewGroup,
TreeViewLeaf,
} from "@prototyperco/ui/components/tree-view";
```