--- name: build-primitive description: > Guide for building foundational Prototyper UI components from scratch when no Base UI primitive exists. Use when creating components that need custom ARIA contracts, keyboard navigation, focus management, controlled/uncontrolled state, form integration, or animation lifecycle — all without an existing Base UI wrapper. tools: [Read, Glob, Grep, Write, Edit, Bash] --- # Prototyper UI — Building Primitive Components How to build a 10/10 component from scratch when no Base UI primitive exists. ARIA contracts first, then keyboard navigation, then state management, then styling hooks, then form integration. **When does this apply?** When you need a component that Base UI does not provide — Breadcrumb, Avatar, Badge, Card, Skeleton, Rating, Pagination, Stepper, Tag Input, File Upload, Calendar, Data Table, Carousel, Command Palette, Resizable Panels, Tree View, etc. **Philosophy:** A primitive-quality component is invisible infrastructure. It handles every edge case so consumers never have to. Every interactive element must be keyboard-operable, every state must be announced to assistive technology, every animation must respect `prefers-reduced-motion`, and every form control must participate in native form submission. --- ## 1. Decision Gate Before building from scratch, verify that no existing solution handles the hard parts. ``` Does a Base UI primitive exist for this component? │ ├── Yes → Use /create-component skill (wrap the primitive) │ ├── Partially (e.g., Collapsible exists but not Accordion) │ └── Compose from existing primitives + custom logic │ (Accordion = Collapsible + custom keyboard nav + ARIA) │ └── No primitive exists │ ├── Is it purely presentational? (Avatar, Badge, Card, Skeleton) │ └── Section 2 — Presentational Template (simple, no hooks) │ ├── Is it a simple interactive? (Breadcrumb, Pagination, Rating) │ └── Section 3 — Interactive Template (ARIA + keyboard) │ ├── Is it a form control? (Tag Input, File Upload, Color Picker) │ └── Section 4 — Form Control Template (ARIA + keyboard + form) │ └── Is it a complex composite? (Calendar, Data Table, Tree View) └── Section 5 — Composite Template (all patterns combined) ``` ### Can a third-party library handle the hard parts? Before building complex interactivity from scratch, check if a headless library provides the state machine: | Component | Consider | Why | | ---------------------- | ------------------------------ | ---------------------------------------------- | | Calendar / Date Picker | `react-aria` (date primitives) | Date math, locale, time zones | | Data Table | `@tanstack/react-table` | Sorting, filtering, pagination, virtualization | | Command Palette | `cmdk` | Fuzzy search, keyboard nav, scoring | | Carousel | `embla-carousel-react` | Snap points, drag physics, loop | | Resizable Panels | `react-resizable-panels` | Drag, constraints, persistence | | Virtual Lists | `@tanstack/react-virtual` | Windowing, dynamic heights | | DnD / Sortable | `@dnd-kit/core` | Drag physics, collision, accessibility | If a headless library exists, wrap it with Prototyper UI styling patterns (data-slot, cn(), tokens, animation) rather than reimplementing the state machine. --- ## 2. Presentational Template For components with no interactive behavior — just semantic HTML, styling, and slots. Used by: Avatar, Badge, Card, Skeleton, Separator, AspectRatio ```tsx "use client"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; // --- Variants --- const avatarVariants = cva( [ "relative inline-flex items-center justify-center overflow-hidden", "rounded-full bg-muted text-muted-foreground", "select-none", ], { variants: { size: { sm: "size-8 text-xs", default: "size-10 text-sm", lg: "size-12 text-base", }, }, defaultVariants: { size: "default", }, }, ); // --- Root --- function Avatar({ className, size, ...props }: React.ComponentProps<"span"> & VariantProps) { return ( ); } // --- Image (with fallback handling) --- function AvatarImage({ className, onError, ...props }: React.ComponentProps<"img">) { return ( ); } // --- Fallback --- function AvatarFallback({ className, ...props }: React.ComponentProps<"span">) { return ( ); } export { Avatar, AvatarImage, AvatarFallback, avatarVariants }; ``` ### Presentational Rules - Use semantic HTML elements (`