Build foundational UI primitives from scratch with strong ARIA, keyboard, focus, and state handling.
AccessibilityInteractionSystemsTesting
No visual demo for this skill
Tooling or audit guidance without a UI surface to embed.
Skill markdown
# 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<typeof avatarVariants>) {
return (
<span
data-slot="avatar"
className={cn(avatarVariants({ size }), className)}
{...props}
/>
);
}
// --- Image (with fallback handling) ---
function AvatarImage({
className,
onError,
...props
}: React.ComponentProps<"img">) {
return (
<img
data-slot="avatar-image"
className={cn("aspect-square size-full object-cover", className)}
onError={onError}
{...props}
/>
);
}
// --- Fallback ---
function AvatarFallback({ className, ...props }: React.ComponentProps<"span">) {
return (
<span
data-slot="avatar-fallback"
className={cn(
"flex size-full items-center justify-center bg-muted font-medium",
className,
)}
{...props}
/>
);
}
export { Avatar, AvatarImage, AvatarFallback, avatarVariants };
```
### Presentational Rules
- Use semantic HTML elements (`<nav>`, `<ol>`, `<figure>`, `<time>`, `<hr>`) — never a `<div>` when
…