Prototyper UI

Skill

Usage guide for the Prototyper UI component library, built on @base-ui/react and Tailwind CSS v4 with copy-paste distribution, OKLCH colors and CVA variants: setup, theme tokens, component selection and patterns, noting render= replaces Radix asChild.

PrototyperAI/prototyper-ui · apps/docs/skill7
Voir sur GitHub
README
name: prototyper-ui
description: "Prototyper UI component library (Tailwind CSS v4 + Base UI). Use when working with Prototyper UI components, installing components, customizing themes, or accessing component documentation."
metadata:
  author: prototyper-ui
  version: "2.0.0"

Prototyper UI Development Guide

Composable, design-first React component library built on @base-ui/react primitives and Tailwind CSS v4. Copy-paste distribution (like shadcn/ui), OKLCH color system, CVA variants, compound components.

CRITICAL: This library uses @base-ui/react, NOT @radix-ui/react. Base UI is the official successor to Radix, maintained by the MUI team. Do NOT import from Radix packages. Do NOT assume shadcn/ui patterns work identically — always fetch docs first.

Dependencies

pnpm add @base-ui/react class-variance-authority clsx tailwind-merge lucide-react

Project Structure

your-app/
  components/ui/         <- component source files (copy-paste)
  lib/utils.ts           <- cn() utility (clsx + tailwind-merge)
  prototyper-tokens.css  <- design tokens
  app/globals.css        <- @import "tailwindcss"; @import "./prototyper-tokens.css";

Required Setup

  1. Tailwind CSS v4 with @tailwindcss/postcss
  2. lib/utils.ts exporting cn():
    import { clsx, type ClassValue } from "clsx";
    import { twMerge } from "tailwind-merge";
    export function cn(...inputs: ClassValue[]) {
      return twMerge(clsx(inputs));
    }
    
  3. prototyper-tokens.css imported in globals.css (run node scripts/theme.mjs to get it)
  4. next-themes for dark mode (.dark class strategy)

Hello World — Button

import { Button } from "@/components/ui/button";

export default function HelloWorld() {
  return (
    <div className="flex gap-2">
      <Button>Default</Button>
      <Button variant="destructive" size="sm">
        Delete
      </Button>
      <Button variant="outline" isPending>
        Loading...
      </Button>
    </div>
  );
}

2. Component Selection Guide

What are you building?

What are you building?
├── A clickable action?
│   ├── Standard button ──────────────── Button (6 variants, 9 sizes)
│   ├── On/off toggle button ─────────── Toggle (2 variants)
│   └── Group of related actions ─────── Toolbar (keyboard nav)
│
├── A form input?
│   ├── Free text ────────────────────── TextField (Input, TextArea)
│   ├── Number with +/- ──────────────── NumberField (8 exports)
│   ├── Pick one from a list ─────────── Select (dropdown) or RadioGroup (inline)
│   ├── Pick from filtered list ──────── Combobox (autocomplete, 24 exports)
│   ├── Yes/no toggle ────────────────── Checkbox (animated) or Switch (3 sizes)
│   ├── Value in a range ─────────────── Slider (compound, 6 exports)
│   └── Need label/description/error? ── Wrap ANY input with Field
│
├── An overlay / popup?
│   ├── Modal with actions ───────────── Dialog
│   ├── Slide-in panel ───────────────── Dialog (with side= prop — no separate Sheet)
│   ├── Info popup on click ──────────── Popover
│   ├── Quick hint on hover ──────────── Tooltip
│   └── Action list on click ─────────── Menu (DropdownMenu)
│
├── Showing progress / status?
│   ├── Task completion ──────────────── Progress (label + value)
│   └── Value in known range ─────────── Meter (color-coded gauge)
│
└── Content navigation? ──────────────── Tabs (horizontal or vertical)

Compound vs Simple

TypeComponentsExports
Simple (1-2 exports)Button, Toggle, RadioGroup2 each
Compound (3-10 exports)Checkbox (3), Tabs (5), Switch (5), Popover (6), Slider (6), Toolbar (6), Meter (7), Progress (7), NumberField (9), Field (10), Dialog (10)3-10
Large compound (11+)Select (11), Menu (15), Combobox (24)11-24

3. Copy-Paste Patterns

These six patterns cover ~95% of use cases. Copy them directly.

Pattern A: Simple Action (Button)

import { Button } from "@/components/ui/button"

// Basic variants
<Button variant="default">Primary</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Cancel</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>

// Sizes: xs, sm, default, lg, icon-xs, icon-sm, icon, icon-lg
<Button size="sm">Small</Button>
<Button size="icon"><SearchIcon /></Button>

// Loading state — disables interaction, keeps visual
<Button isPending>Saving...</Button>

// Render as link — render= replaces asChild from Radix
<Button render={<a href="/about" />}>About</Button>

Key API difference from shadcn: render= prop replaces Radix's asChild. Pass a JSX element, and Base UI merges the props onto it. When using render= on Button, the nativeButton prop is automatically handled internally.

Pattern B: Form Field (TextField + Field)

import { Field, FieldLabel, FieldDescription, FieldError } from "@/components/ui/field"
import { Input } from "@/components/ui/textfield"

// Field wraps ANY input with label, description, error
<Field>
  <FieldLabel>Email</FieldLabel>
  <FieldDescription>We'll never share your email.</FieldDescription>
  <Input placeholder="you@example.com" />
  <FieldError>Please enter a valid email address.</FieldError>
</Field>

// With validation (invalid prop on Field)
<Field invalid={!!errors.email}>
  <FieldLabel>Email</FieldLabel>
  <Input type="email" required />
  <FieldError>{errors.email}</FieldError>
</Field>

// TextArea variant
import { TextArea } from "@/components/ui/textfield"

<Field>
  <FieldLabel>Message</FieldLabel>
  <TextArea rows={4} placeholder="Your message..." />
</Field>

Field is a universal wrapper. Use it around TextField, NumberField, Select, Combobox, Checkbox, Switch, RadioGroup — any form input that needs a label, description, or error message.

Pattern C: Positioned Overlay (Popover)

import {
  Popover,
  PopoverTrigger,
  PopoverContent,
  PopoverHeader,
  PopoverTitle,
  PopoverDescription,
} from "@/components/ui/popover";
import { Button } from "@/components/ui/button";

<Popover>
  <PopoverTrigger render={<Button variant="outline" />}>
    Open Popover
  </PopoverTrigger>
  <PopoverContent side="bottom" align="center" sideOffset={4}>
    <PopoverHeader>
      <PopoverTitle>Settings</PopoverTitle>
      <PopoverDescription>Configure your preferences.</PopoverDescription>
    </PopoverHeader>
    {/* your content */}
  </PopoverContent>
</Popover>;

Positioning props (side, sideOffset, align, alignOffset) live on the Content component. Internally, they're forwarded to Base UI's Positioner layer — you don't need to think about Portal/Positioner/Popup yourself.

This same pattern applies to Tooltip, Menu, Select, and Combobox — all positioned overlays use side/align/offset. For Menu specifically:

import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
} from "@/components/ui/menu";

<DropdownMenu>
  <DropdownMenuTrigger render={<Button variant="outline" />}>
    Actions
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>
      Edit <DropdownMenuShortcut>Cmd+E</DropdownMenuShortcut>
    </DropdownMenuItem>
    <DropdownMenuItem>Duplicate</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>;

Pattern D: Compound Visual (Slider)

import {
  Slider, SliderControl, SliderTrack,
  SliderIndicator, SliderThumb, SliderOutput,
} from "@/components/ui/slider"

<Slider defaultValue={50} min={0} max={100}>
  <SliderOutput />
  <SliderControl>
    <SliderTrack>
      <SliderIndicator />
      <SliderThumb />
    </SliderTrack>
  </SliderControl>
</Slider>

// Multi-thumb range slider
<Slider defaultValue={[25, 75]}>
  <SliderControl>
    <SliderTrack>
      <SliderIndicator />
      <SliderThumb />  {/* automatically creates one per value */}
      <SliderThumb />
    </SliderTrack>
  </SliderControl>
</Slider>

Compound visual components have a strict nesting order. The sub-components must be nested correctly — they communicate via React context.

Pattern E: Selection Input (Select + Field)

import { Field, FieldLabel, FieldError } from "@/components/ui/field";
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectGroup,
  SelectLabel,
  SelectItem,
  SelectSeparator,
} from "@/components/ui/select";

<Field>
  <FieldLabel>Country</FieldLabel>
  <Select>
    <SelectTrigger size="default">
      <SelectValue placeholder="Select a country" />
    </SelectTrigger>
    <SelectContent>
      <SelectGroup>
        <SelectLabel>North America</SelectLabel>
        <SelectItem value="us">United States</SelectItem>
        <SelectItem value="ca">Canada</SelectItem>
      </SelectGroup>
      <SelectSeparator />
      <SelectGroup>
        <SelectLabel>Europe</SelectLabel>
        <SelectItem value="uk">United Kingdom</SelectItem>
        <SelectItem value="de">Germany</SelectItem>
      </SelectGroup>
    </SelectContent>
  </Select>
  <FieldError>Please select a country.</FieldError>
</Field>;

Pattern F: Dialog & Sheet (Unified)

import {
  Dialog, DialogTrigger, DialogContent,
  DialogHeader, DialogTitle, DialogDescription,
  DialogFooter, DialogClose,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"

// Standard center dialog
<Dialog>
  <DialogTrigger render={<Button />}>Edit Profile</DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Edit Profile</DialogTitle>
      <DialogDescription>Make changes to your profile.</DialogDescription>
    </DialogHeader>
    {/* form content */}
    <DialogFooter>
      <DialogClose render={<Button variant="outline" />}>Cancel</DialogClose>
      <Button type="submit">Save</Button>
    </DialogFooter>
  </DialogContent>
</Dialog>

// Sheet variant — just add side= prop
<Dialog>
  <DialogTrigger render={<Button variant="outline" />}>Open Sheet</DialogTrigger>
  <DialogContent side="right">  {/* "top" | "bottom" | "left" | "right" */}
    <DialogHeader>
      <DialogTitle>Navigation</DialogTitle>
    </DialogHeader>
    {/* sheet content */}
  </DialogContent>
</Dialog>

There is no separate Sheet component. Dialog handles both center modals and side sheets via the side prop. When side is set, the dialog slides in from that edge. When omitted, it's a standard centered modal.


4. Design System Reference

Token Selection: "I need a background — which token?"

What kind of element needs a background?
├── The page itself ────────────────── bg-background
├── A card / raised surface ────────── bg-surface (or bg-card)
├── A nested surface ───────────────── bg-surface-secondary / bg-surface-tertiary
├── An overlay (popover/dialog/menu) ── bg-overlay
├── A form input ───────────────────── bg-field-background
├── A primary action ───────────────── bg-primary
├── A destructive action ───────────── bg-destructive
├── A hover state (interactive) ────── hover-only:hover:bg-accent
├── A selected/active state ────────── bg-accent
├── Muted / secondary text area ────── bg-muted
└── A subtle tint of primary ───────── bg-primary-soft

Color Format (OKLCH)

Tokens store raw OKLCH channels. Tailwind utilities are pre-registered via @theme:

/* Token definition */
--primary: 55% 0.24 264;              /* lightness chroma hue */

/* In Tailwind — just use the utility */
className="bg-primary text-primary-foreground"

/* In raw CSS — wrap with oklch() */
background: oklch(var(--primary));

Never use hsl(var(--primary)), hardcoded hex, or bg-[oklch(55%_0.24_264)].

Token Categories

CategoryTokens
Basebackground, foreground, border, border-light, border-dark, input, ring, radius
Primary rampprimary, primary-light, primary-middle, primary-dark, primary-foreground
Semanticsecondary, muted, accent, destructive (each with -foreground)
Statussuccess, warning, info (each with -foreground)
Surfacessurface, surface-secondary, surface-tertiary, card, popover, overlay (each with -foreground)
Fieldsfield-background, field-border, field-border-hover, field-border-invalid
Derived hoversprimary-hover, destructive-hover, success-hover, warning-hover, accent-hover
Derived softprimary-soft, primary-soft-hover, destructive-soft, destructive-soft-hover

Derived Colors (Auto-Generated)

Hover states use color-mix() — no manual darkening:

--primary-hover: color-mix(
  in oklab,
  oklch(var(--primary)) 90%,
  oklch(var(--primary-foreground)) 10%
);
--primary-soft: color-mix(in oklab, oklch(var(--primary)) 15%, transparent);

Use as: hover-only:hover:bg-primary-hover, bg-primary-soft.

Shadow Tiers

TierTokenUse forDark mode
Surfaceshadow-surfaceCards, raised sectionsnone (tonal contrast)
Fieldshadow-fieldForm inputsnone (tonal contrast)
Overlayshadow-overlayPopovers, dialogs, menusSubtle border + deep shadow

Dark Mode

Dark mode activates via .dark class (next-themes). Token values swap automatically — you never write dark:bg-* classes. Shadows for surface and field become none in dark mode (depth comes from tonal contrast instead). Overlay shadows get a subtle white border ring.

Easing Tokens

TokenUtilityWhen to use
--ease-smoothease-smoothState transitions (color, bg, border changes)
--ease-out-fluidease-out-fluidElements entering (overlays, sheets, switch thumb)
--ease-in-quartease-in-quartElements exiting (overlay dismiss)
--ease-out-quadease-out-quadAvailable, currently unused
--ease-out-quartease-out-quartAvailable, currently unused
--ease-in-quadease-in-quadAvailable, currently unused

CSS Utilities

UtilityPurposeUse on
focus-ring2px ring, 2px offsetButtons, toggles, links (focus-visible:focus-ring)
focus-field-ring2px ring, -1px offset (sits on border)Form inputs (focus-within:focus-field-ring)
invalid-field-ringDestructive-colored ringInvalid fields (data-invalid:invalid-field-ring)
status-disabledopacity 0.5 + no pointer eventsDisabled elements (disabled:status-disabled)
status-pendingNo pointer events onlyLoading states (pending:status-pending)
no-highlightRemove mobile tap highlightAll interactive elements

Custom Tailwind Variants

VariantMeaningUse for
motion-reduce:prefers-reduced-motion: reduceDisable transitions/animations
motion-safe:prefers-reduced-motion: no-preferenceGate press feedback (motion-safe:active:scale-[0.97])
hover-only:@media (hover: hover)Prevent sticky hover on touch devices

5. Component Architecture

Sub-component Patterns

PatternStructureUsed by
Root + Trigger + ContentOverlay with trigger/popup pairDialog, Popover, Tooltip, Menu, Select, Combobox
Visual compoundRoot + Track + Indicator + ThumbSlider, Switch, Meter, Progress
Positioned overlayPortal → Positioner → Popup (internal)Popover, Tooltip, Menu, Select, Combobox
Internal contextRoot provides size/state via React contextSwitch (size), Slider (value)
Presentational wrapperPlain <div> with stylingDialogHeader, DialogFooter, PopoverHeader

Component File Header

Every component file follows this strict pattern:

"use client"; // Always first line

import * as React from "react"; // Only when needed
import { Component as ComponentPrimitive } from "@base-ui/react/component-name";
import { cva, type VariantProps } from "class-variance-authority";
import { SomeIcon } from "lucide-react"; // Icons

import { cn } from "@/lib/utils"; // Internal utilities last

Always use deep path Base UI imports (@base-ui/react/dialog), never barrel imports. Alias primitives with Primitive suffix to avoid naming conflicts with your wrapper components.

Props Type Conventions

Components extend Base UI primitive props + CVA VariantProps:

// Simple component
function Button({
  className, variant, size, isPending, render, ...props
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants> & {
  isPending?: boolean
}) { ... }

// Overlay content — merge Popup + Positioner positioning props
function PopoverContent({
  className, side = "bottom", sideOffset = 4, align = "center", ...props
}: PopoverPrimitive.Popup.Props &
  Pick<PopoverPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset">
) { ... }

The render= Prop (Base UI's asChild Replacement)

Base UI does not use Radix's asChild. Instead, it provides render= which takes a JSX element:

// Render trigger as a Button
<DialogTrigger render={<Button variant="outline" />}>
  Open Dialog
</DialogTrigger>

// Render close as a Button
<DialogClose render={<Button variant="ghost" />}>
  Cancel
</DialogClose>

// Render as a link
<Button render={<a href="/about" />}>About Us</Button>

Base UI merges all primitive props (event handlers, ARIA attributes, refs) onto your element. The render= prop can also accept a function render={(props) => <Custom {...props} />} but the JSX element form is preferred.

data-slot Convention

Every rendered element gets a data-slot attribute for CSS targeting:

<button data-slot="button" />
<div data-slot="dialog-header" />
<div data-slot="select-content" />

Pattern: root = component name (button), sub-components = {root}-{role} (dialog-header, select-item).


6. Styling & Customization

className Overrides

All components accept className. It's merged via cn() (tailwind-merge) — your classes win over defaults:

<Button className="w-full rounded-full">Full Width Pill</Button>

CSS Targeting via data-slot

Style components from parent CSS without prop drilling:

[data-slot="button"] {
  /* targets all buttons */
}
[data-slot="dialog-content"] {
  /* targets dialog panels */
}

Reusing CVA Variants

Use variant functions on non-component elements (e.g., making a link look like a button):

import { buttonVariants } from "@/components/ui/button";

<a href="/about" className={buttonVariants({ variant: "outline", size: "sm" })}>
  About
</a>;

Token Overrides

Override any design token in your globals.css:

:root {
  --primary: 60% 0.2 150; /* green primary instead of blue */
  --radius: 0.75rem; /* larger border radius */
}

Custom Tailwind Variants

// Only apply hover on devices that support it (prevents sticky hover on touch)
"hover-only:hover:bg-accent";

// Only animate for users who haven't requested reduced motion
"motion-safe:active:scale-[0.97]";

// Disable animation for users who prefer reduced motion
"motion-reduce:transition-none";
"motion-reduce:animate-none";

Animation Conventions

Prototyper UI uses CSS-first animation — no framer-motion dependency.

  • State transitions: transition-[color,background-color,border-color,box-shadow,opacity] duration-150 ease-smooth + motion-reduce:transition-none
  • Press feedback: motion-safe:active:scale-[0.97] (buttons), scale-[0.98] (menu items), scale-[0.95] (small controls like checkbox)
  • Overlay enter/exit: data-open:animate-in / data-closed:animate-out with fade-in/zoom-in-95/directional slides
  • Signature easing: --ease-out-fluid (cubic-bezier(0.32, 0.72, 0, 1))
  • Split timing: colors 100-150ms, transforms 250ms, enter > exit (300ms→200ms)
  • Never: transition-all, bare active:scale-*, animations without motion-reduce: alternative

For detailed animation patterns, invoke the /animate-ui skill.


7. Accessibility Checklist

Every component should address:

  • Motion reduction: Add motion-reduce:transition-none on elements with transition-* classes. Add motion-reduce:animate-none on elements with animate-in/animate-out.
  • Touch safety: Use hover-only:hover: instead of bare hover: for background/color changes. Add no-highlight utility on interactive elements.
  • Press feedback: Always prefix with motion-safe: — never bare active:scale-*.
  • Focus management: Use focus-visible:focus-ring for buttons/toggles/links. Use focus-within:focus-field-ring for form field groups.
  • Keyboard navigation: Handled by Base UI automatically — Arrow keys for RadioGroup, Tabs, Menu, Toolbar, Select. Escape to close overlays. Enter/Space to activate.

8. Common Mistakes

Import Mistakes

#WrongCorrectWhy
1import * as Dialog from "@radix-ui/react-dialog"import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"Base UI, not Radix. Deep path import, aliased.
2export default Buttonexport { Button, buttonVariants }Named exports only, never default.
3const Button = React.forwardRef(...)function Button({ className, ...props })No forwardRef — Base UI handles refs internally.

Styling Mistakes

#WrongCorrectWhy
4bg-[hsl(var(--primary))]bg-primaryOKLCH tokens registered via @theme — use Tailwind utilities directly.
5transition-all duration-200transition-[color,background-color,border-color,box-shadow,opacity] duration-150Never transition-all — enumerate properties explicitly.
6hover:bg-accenthover-only:hover:bg-accentGate hovers with hover-only: to prevent sticky hover on touch.
7active:scale-95motion-safe:active:scale-[0.97]Always motion-safe: prefix, and use exact scale values (0.97/0.98/0.95).
8No data-slot attributedata-slot="component-name" on every elementRequired on every rendered element in every sub-component.
9shadow-lg / shadow-mdshadow-surface / shadow-field / shadow-overlayUse semantic shadow tiers, not generic Tailwind shadows.
10focus:ring-2 focus:ring-ringfocus-visible:focus-ring (buttons) or focus-within:focus-field-ring (fields)Use utility classes — they handle ring width, color, and offset.

Architecture Mistakes

#WrongCorrectWhy
11<Dialog.Close asChild><DialogClose render={<Button />}>Base UI uses render=, not asChild.
12data-[state=open]:animate-indata-open:animate-inBase UI uses boolean data attributes, not data-state values.
13tv({ variants: {...} })cva("base classes", { variants: {...} })CVA (class-variance-authority), never Tailwind Variants.
14dark:bg-gray-800Token swap via .dark class in prototyper-tokens.cssDark mode is token-based — values swap automatically, no dark: needed.
15Transition without motion-reduce:motion-reduce:transition-none alongside every transition-*Always provide reduced-motion alternative.

Component-Specific Mistakes

#WrongCorrectWhy
16import { Sheet } from "@/components/ui/sheet"<DialogContent side="right">No separate Sheet — Dialog's side prop creates sheet behavior.
17<Input /> without Field wrapper<Field><FieldLabel>Name</FieldLabel><Input /><FieldError>...</FieldError></Field>Always wrap form inputs with Field for label/description/error.
18Bare <Popover.Content> without positioning<PopoverContent side="bottom" sideOffset={4}>Positioning props are part of the Content component's API.

9. Component Reference

ComponentKey ExportsTypeVariantsNotable
ButtonButton, buttonVariantsSimplevariant (6), size (9)isPending, render=
ToggleToggle, toggleVariantsSimplevariant (2), size (3)aria-pressed state
ToolbarToolbar, ToolbarButton, ToolbarLink, ToolbarGroup, ToolbarSeparator, ToolbarInputCompound (6)—Keyboard nav, orientation
TextFieldInput, inputVariants, TextField, TextAreaCompound (4)size (3)Integrated Field wrapper
NumberFieldNumberField, NumberFieldGroup, NumberFieldInput, NumberFieldIncrement, NumberFieldDecrement, NumberFieldSteppers, NumberFieldScrubArea, NumberFieldScrubAreaCursorCompound (9)size (3)Scrub area for drag-increment
CheckboxCheckbox, CheckboxControl, CheckboxIndicatorCompound (3)—Animated SVG draw-on
SwitchSwitch, SwitchTrack, SwitchThumb, SwitchIcon, switchVariantsCompound (5)size (sm/md/lg)Context-driven sizing
RadioGroupRadioGroup, RadioGroupItemCompound (2)—Inline radio buttons
SliderSlider, SliderControl, SliderTrack, SliderIndicator, SliderThumb, SliderOutputCompound (6)—Multi-thumb, animated fill
SelectSelect, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGroup, SelectLabel, SelectSeparator, SelectScrollUpButton, SelectScrollDownButton, selectTriggerVariantsCompound (11)size (3)Portal + Positioner pattern
ComboboxCombobox, ComboboxInput, ComboboxContent, ComboboxList, ComboboxItem, ComboboxEmpty, + 18 moreCompound (24)—Chips/multi-select, filter hook
MenuDropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, + 11 moreCompound (15)item variant (2)Submenus, checkbox/radio items
DialogDialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose, DialogOverlay, DialogPortalCompound (10)side (4)Unified dialog + sheet
PopoverPopover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverTitle, PopoverDescriptionCompound (6)—Positioned overlay
TooltipTooltip, TooltipTrigger, TooltipContent, TooltipProviderCompound (4)—Delay, built-in arrow
TabsTabs, TabsList, TabsTrigger, TabsContent, tabsListVariantsCompound (5)variant (2), orientationHorizontal/vertical
FieldField, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitleSuper-component (10)orientation (3)Universal form wrapper
MeterMeter, MeterTrack, MeterIndicator, MeterLabel, MeterValue, meterIndicatorVariants, meterTrackVariantsCompound (7)color (4)Gauge display
ProgressProgress, ProgressTrack, ProgressIndicator, ProgressLabel, ProgressValue, progressIndicatorVariants, progressTrackVariantsCompound (7)color (4)Indeterminate state

10. Documentation Access

Scripts

# List all available components
node scripts/list.mjs

# Get component documentation (full docs + source + examples)
node scripts/docs.mjs button
node scripts/docs.mjs button dialog select    # multiple at once

# Get component source code only
node scripts/source.mjs button

# Get design tokens CSS
node scripts/theme.mjs

Direct URLs

URLContent
https://prototyper-ui.com/llms.txtFull component index
https://prototyper-ui.com/llms/{component}Component docs (e.g., /llms/button)
https://prototyper-ui.com/llms-full.txtAll docs in one file
https://prototyper-ui.com/llms-components.txtComponent listing
https://prototyper-ui.com/prototyper-tokens.cssDesign tokens CSS

MCP Server

claude mcp add prototyper-ui -- npx -y @prototyperco/mcp@latest

Tools: list_components, get_component_docs, get_component_source, get_theme, search_docs.

Always Fetch First

Before implementing any component, always fetch its documentation using the scripts or URLs above. Do not assume Radix/shadcn patterns work identically. Component APIs, data attributes, and animation conventions all differ from what you might expect.