Build accessible, responsive, and visually intentional UI components. Production quality — not AI-generated boilerplate.
Core principle: Every component ships with: keyboard navigation, screen reader support, loading/error/empty states, and responsive behavior. Accessibility is not optional.
When to Use
Building new UI components
Reviewing existing components for quality
Implementing accessibility (WCAG 2.1 AA)
Detecting and removing AI aesthetic anti-patterns
Creating component variants with consistent API
When NOT to Use
Design system setup (color, typography, tokens) — use design-system skill
Performance optimization of components — use frontend-performance skill
Component Architecture
Atomic Design Hierarchy
Level
Examples
Atoms
Button, Input, Label, Icon, Badge, Avatar
Molecules
Search bar, Form field (label + input + error), Card
// All interactive elements must be keyboard accessible
// Tab moves focus, Enter/Space activates, Escape closes modals/dropdowns
// Modal: trap focus inside
function Modal({ isOpen, onClose, children }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!isOpen) return;
const firstFocusable = ref.current?.querySelector<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
firstFocusable?.focus();
}, [isOpen]);
return isOpen ? (
<div role="dialog" aria-modal="true" ref={ref} => {
if (e.key === 'Escape') onClose();
}}>
{children}
</div>
) : null;
}
ARIA Checklist
<!-- Icon-only button: must have label -->
<button aria-label="Close dialog"><XIcon aria-hidden="true" /></button>
<!-- Toggle button: announce state -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<!-- Live region for dynamic content -->
<div aria-live="polite" aria-atomic="true">3 items in cart</div>
<!-- Form input: label association -->
<label for="email">Email address</label>
<input id="email" type="email" aria-required="true"
aria-describedby="email-hint email-error">
<p id="email-hint">We'll never share your email.</p>
<p id="email-error" role="alert">Email is required.</p>
Full Accessibility Checklist
Keyboard navigation works (Tab through entire page)
Screen reader announces content and structure correctly
Color contrast passes (4.5:1 text, 3:1 UI elements)
Focus indicators visible on every interactive element
All form inputs have associated labels (not just placeholder)
All images have descriptive alt text (or alt="" if decorative)
Headings are hierarchical (h1→h2→h3, no skips)
Link text is descriptive ("View task details" not "click here")
Touch targets minimum 44×44px
aria-label on all icon-only buttons
Modals trap focus and restore focus on close
Responsive Design
Breakpoints:
375px — mobile portrait (minimum)
768px — tablet portrait
1024px — tablet landscape / small desktop
1440px — desktop
Rules:
- Mobile-first: start with mobile styles, add breakpoints upward
- All multi-column layouts collapse below 768px
- No horizontal scroll on any viewport (critical failure)
- Touch targets ≥44px on mobile
- Body text ≥16px on mobile
- Use min-h-[100dvh] not h-screen (iOS Safari fix)
2-column zig-zag, asymmetric grid, or horizontal scroll
Lorem ipsum in shipped UI
Filler text that will reach production
Real content or named placeholder ([Client Name Here])
Interactive State Completeness Rule
Every component must implement all four data states before it is considered complete:
State
Requirement
Loading
Skeleton layout matching component shape, or contextual spinner with aria-busy="true"
Empty
Zero-state illustration or message explaining what goes here + a call-to-action to populate it
Error
Clear human-readable message, specific to what failed, with a retry action
Success
Confirmation feedback or updated content — never silently succeed
A component missing any of these four states is incomplete and must not ship.
Content Standards
Never lorem ipsum — use realistic placeholder content
No broken image links — use picsum.photos or SVG placeholders
No generic names ("John Doe", "Acme Corp") — use realistic names
No placeholder copy that will ship (it will ship)
Component Quality Limits
Limit
Threshold
Action
Component file length
200 lines
Split into subcomponents
Props count
8
Extract to compound component pattern
Nesting depth
5 levels
Extract inner elements
Logic in JSX
Any non-trivial
Extract to custom hook
Verification Checklist
All interactive states handled (default, hover, focus, active, disabled, loading, error, empty)
Keyboard navigation works (Tab, Enter, Escape)
Screen reader announced content tested
Color contrast passes (run axe DevTools or Lighthouse)
Focus indicators visible and meet 3:1 contrast
All icon-only buttons have aria-label
All form inputs have associated <label>
Touch targets ≥44px on mobile
Responsive: works at 375px, 768px, 1024px, 1440px
No horizontal scroll at any viewport
No AI aesthetic patterns (purple gradients, excessive rounding, lorem ipsum)
Semantic color tokens used throughout (no raw hex values)
Component <200 lines
No inline styles or arbitrary pixel values
1---2name: ui-components3description: Use when building UI components, implementing accessible interfaces, creating responsive layouts, auditing UI for visual quality or AI aesthetic anti-patterns, implementing component variants, reviewing component architecture, or ensuring WCAG 2.1 AA compliance. Triggers: "build a component", "UI component", "accessible", "responsive", "modal", "form", "button", "layout", "ARIA", "keyboard navigation", "screen reader", "visual QA", "component library".4---56# UI Components78Build accessible, responsive, and visually intentional UI components. Production quality — not AI-generated boilerplate.910**Core principle:** Every component ships with: keyboard navigation, screen reader support, loading/error/empty states, and responsive behavior. Accessibility is not optional.1112## When to Use1314- Building new UI components15- Reviewing existing components for quality16- Implementing accessibility (WCAG 2.1 AA)17- Detecting and removing AI aesthetic anti-patterns18- Creating component variants with consistent API1920## When NOT to Use2122- Design system setup (color, typography, tokens) — use design-system skill23- Performance optimization of components — use frontend-performance skill2425---2627## Component Architecture2829### Atomic Design Hierarchy3031| Level | Examples |32|-------|---------|33| **Atoms** | Button, Input, Label, Icon, Badge, Avatar |34| **Molecules** | Search bar, Form field (label + input + error), Card |35| **Organisms** | Header, Sidebar, Data table, Form with submit |36| **Templates** | Dashboard layout, Settings page shell |37| **Pages** | Full user-facing pages |3839### Composition Over Configuration4041```tsx42// Good: composable — caller controls structure43<Card>44 <CardHeader>45 <CardTitle>Tasks</CardTitle>46 <CardDescription>Your pending work</CardDescription>47 </CardHeader>48 <CardContent>49 <TaskList tasks={tasks} />50 </CardContent>51</Card>5253// Avoid: over-configured — caller can't customize structure54<Card title="Tasks" subtitle="Your pending work" content={<TaskList />} />55```5657### Container/Presentation Split5859```tsx60// Container: fetches and manages data61function TaskListContainer() {62 const { tasks, isLoading, error } = useTasks();63 if (isLoading) return <TaskListSkeleton />;64 if (error) return <ErrorState message="Failed to load tasks" />;65 if (tasks.length === 0) return <EmptyState message="No tasks yet" action={<CreateTaskButton />} />;66 return <TaskList tasks={tasks} />;67}6869// Presentation: pure display, no data fetching70function TaskList({ tasks }: { tasks: Task[] }) {71 return (72 <ul role="list">73 {tasks.map(task => <TaskItem key={task.id} task={task} />)}74 </ul>75 );76}77```7879### Component Variants (class-variance-authority)8081```typescript82import { cva } from 'class-variance-authority';8384const buttonVariants = cva(85 'inline-flex items-center justify-center rounded-md font-medium transition-colors',86 {87 variants: {88 variant: {89 default: 'bg-primary text-primary-foreground hover:bg-primary/90',90 outline: 'border border-input hover:bg-accent',91 ghost: 'hover:bg-accent hover:text-accent-foreground',92 destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',93 },94 size: {95 sm: 'h-9 px-3 text-sm',96 default: 'h-10 px-4 py-2',97 lg: 'h-11 px-8 text-base',98 icon: 'h-10 w-10',99 },100 },101 defaultVariants: { variant: 'default', size: 'default' },102 }103);104```105106---107108## Required States109110Every interactive component must handle all states:111112| State | How to Handle |113|-------|--------------|114| **Default** | Normal, no interaction |115| **Hover** | Visual feedback (`hover:`) |116| **Focus** | Visible focus ring — never `outline: none` without replacement |117| **Active** | Press state for buttons |118| **Disabled** | `disabled` attribute, `aria-disabled`, reduced opacity |119| **Loading** | Skeleton or spinner, `aria-busy="true"` |120| **Error** | Error message linked via `aria-describedby` |121| **Empty** | Empty state with guidance or call-to-action |122123---124125## WCAG 2.1 AA Accessibility126127### Keyboard Navigation128129```tsx130// All interactive elements must be keyboard accessible131// Tab moves focus, Enter/Space activates, Escape closes modals/dropdowns132133// Modal: trap focus inside134function Modal({ isOpen, onClose, children }) {135 const ref = useRef<HTMLDivElement>(null);136137 useEffect(() => {138 if (!isOpen) return;139 const firstFocusable = ref.current?.querySelector<HTMLElement>(140 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'141 );142 firstFocusable?.focus();143 }, [isOpen]);144145 return isOpen ? (146 <div role="dialog" aria-modal="true" ref={ref} onKeyDown={e => {147 if (e.key === 'Escape') onClose();148 }}>149 {children}150 </div>151 ) : null;152}153```154155### ARIA Checklist156157```html158<!-- Icon-only button: must have label -->159<button aria-label="Close dialog"><XIcon aria-hidden="true" /></button>160161<!-- Toggle button: announce state -->162<button aria-expanded="false" aria-controls="menu">Menu</button>163164<!-- Live region for dynamic content -->165<div aria-live="polite" aria-atomic="true">3 items in cart</div>166167<!-- Form input: label association -->168<label for="email">Email address</label>169<input id="email" type="email" aria-required="true" 170 aria-describedby="email-hint email-error">171<p id="email-hint">We'll never share your email.</p>172<p id="email-error" role="alert">Email is required.</p>173```174175### Full Accessibility Checklist176177- [ ] Keyboard navigation works (Tab through entire page)178- [ ] Screen reader announces content and structure correctly179- [ ] Color contrast passes (4.5:1 text, 3:1 UI elements)180- [ ] Focus indicators visible on every interactive element181- [ ] All form inputs have associated labels (not just placeholder)182- [ ] All images have descriptive alt text (or `alt=""` if decorative)183- [ ] Headings are hierarchical (h1→h2→h3, no skips)184- [ ] Link text is descriptive ("View task details" not "click here")185- [ ] Touch targets minimum 44×44px186- [ ] `aria-label` on all icon-only buttons187- [ ] Modals trap focus and restore focus on close188189---190191## Responsive Design192193```194Breakpoints:195 375px — mobile portrait (minimum)196 768px — tablet portrait197 1024px — tablet landscape / small desktop198 1440px — desktop199200Rules:201 - Mobile-first: start with mobile styles, add breakpoints upward202 - All multi-column layouts collapse below 768px203 - No horizontal scroll on any viewport (critical failure)204 - Touch targets ≥44px on mobile205 - Body text ≥16px on mobile206 - Use min-h-[100dvh] not h-screen (iOS Safari fix)207```208209```tsx210// Mobile-first responsive grid211<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">212 {items.map(item => <Card key={item.id} {...item} />)}213</div>214```215216---217218## AI Slop Anti-Patterns (Banned)219220These patterns signal AI-generated UI without design intent:221222| AI Default | Production Standard |223|---|---|224| Purple/indigo gradients on everything | Project's actual color palette |225| Excessive `rounded-2xl` on everything | Consistent border-radius from design system |226| Oversized padding everywhere (`p-8` on everything) | Consistent spacing scale |227| Generic hero sections ("Elevate your workflow") | Content-first, specific copy |228| Card grids of 6 generic cards | Real content with real hierarchy |229| Emojis in production UI | Text or proper icons |230| Pure black (`#000000`) | Zinc-950 or Off-Black |231| AI copywriting clichés ("Seamless", "Unleash", "Next-Gen") | Specific, direct language |232| Neon/outer glow shadows | Subtle or no shadows per design system |233| Oversaturated accent colors (>80% saturation) | Measured accent within constraints |234235**8 specific forbidden patterns:**236| Pattern | Banned Form | Use Instead |237|---------|-------------|-------------|238| Neon glows | `box-shadow: 0 0 20px #accent` | Muted tinted `box-shadow` (hue-matched, low opacity) |239| Pure black text | `color: #000` | `zinc-900` or equivalent dark gray |240| Oversaturated accents | Saturation >80% | Desaturate accent to blend with neutrals |241| Gradient text | `background-clip: text` on large headings | Solid color or 1-stop gradient max |242| Generic stock avatars | Generic SVG "egg" user icon | Initials/monogram component or omit |243| Rounded fake numbers | "1,234" displayed as "1.2K" arbitrarily | Show real data, or label explicitly as "example" |244| Default 3-column card grid | Equal-width 3-card feature row | 2-column zig-zag, asymmetric grid, or horizontal scroll |245| Lorem ipsum in shipped UI | Filler text that will reach production | Real content or named placeholder (`[Client Name Here]`) |246247## Interactive State Completeness Rule248249Every component must implement all four data states before it is considered complete:250251| State | Requirement |252|-------|-------------|253| **Loading** | Skeleton layout matching component shape, or contextual spinner with `aria-busy="true"` |254| **Empty** | Zero-state illustration or message explaining what goes here + a call-to-action to populate it |255| **Error** | Clear human-readable message, specific to what failed, with a retry action |256| **Success** | Confirmation feedback or updated content — never silently succeed |257258A component missing any of these four states is incomplete and must not ship.259260## Content Standards261262- **Never lorem ipsum** — use realistic placeholder content263- **No broken image links** — use `picsum.photos` or SVG placeholders264- **No generic names** ("John Doe", "Acme Corp") — use realistic names265- **No placeholder copy** that will ship (it will ship)266267---268269## Component Quality Limits270271| Limit | Threshold | Action |272|-------|-----------|--------|273| Component file length | 200 lines | Split into subcomponents |274| Props count | 8 | Extract to compound component pattern |275| Nesting depth | 5 levels | Extract inner elements |276| Logic in JSX | Any non-trivial | Extract to custom hook |277278---279280## Verification Checklist281282- [ ] All interactive states handled (default, hover, focus, active, disabled, loading, error, empty)283- [ ] Keyboard navigation works (Tab, Enter, Escape)284- [ ] Screen reader announced content tested285- [ ] Color contrast passes (run axe DevTools or Lighthouse)286- [ ] Focus indicators visible and meet 3:1 contrast287- [ ] All icon-only buttons have `aria-label`288- [ ] All form inputs have associated `<label>`289- [ ] Touch targets ≥44px on mobile290- [ ] Responsive: works at 375px, 768px, 1024px, 1440px291- [ ] No horizontal scroll at any viewport292- [ ] No AI aesthetic patterns (purple gradients, excessive rounding, lorem ipsum)293- [ ] Semantic color tokens used throughout (no raw hex values)294- [ ] Component <200 lines295- [ ] No inline styles or arbitrary pixel values
Run npx skillmds@latest add thejordanleopold/ui-components in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Use when building UI components, implementing accessible interfaces, creating responsive layouts, auditing UI for visual quality or AI aesthetic anti-patterns, implementing component variants, reviewing component architecture, or ensuring WCAG 2.1 AA compliance. Triggers: "build a component", "UI component", "accessible", "responsive", "modal", "form", "button", "layout", "ARIA", "keyboard navigation", "screen reader", "visual QA", "component library". It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Capability flags: docs only. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
thejordanleopold (@thejordanleopold) published this skill. Their other Agent Skills are listed on their SkillMD profile.