skills/ui-patterns/SKILL.md
Use when implementing UI components, building interfaces, creating forms, or any frontend work. Provides opinionated constraints for accessible, performant, consistent UI based on ui-skills.com patterns.
npx skillsauth add ash4180/vorbit ui-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Opinionated constraints for building better interfaces. Based on ui-skills.com.
When to use: Any UI component implementation, form building, layout work.
| Requirement | Use | Don't Use |
|-------------|-----|-----------|
| Styling | Tailwind CSS | CSS-in-JS, plain CSS |
| Animations | motion/react (Framer Motion) | CSS transitions, other animation libs |
| Class Logic | cn() utility (clsx + tailwind-merge) | String concatenation |
| Primitives | Radix, React Aria, or Base UI | Custom implementations |
Rule: Don't mix primitive systems. Pick one and stick to it.
Use headless UI libraries for complex components:
| Component | Use Primitive | Don't Build From Scratch |
|-----------|---------------|--------------------------|
| Modal/Dialog | @radix-ui/react-dialog | <div> with click handlers |
| Dropdown | @radix-ui/react-dropdown-menu | Custom dropdown |
| Tooltip | @radix-ui/react-tooltip | Title attribute |
| Tabs | @radix-ui/react-tabs | Manual state + divs |
| Select | @radix-ui/react-select | <select> styling hacks |
// Use controlled inputs with proper labels
<label htmlFor="email">Email</label>
<input id="email" type="email" aria-describedby="email-error" />
{error && <span id="email-error" role="alert">{error}</span>}
Rules:
<label> with matching htmlForrole="alert" and aria-describedbytype="email", type="tel", etc. for mobile keyboards| Interaction | Pattern | |-------------|---------| | Destructive actions | Always use AlertDialog with confirm/cancel | | Loading states | Structural skeletons matching final layout | | Errors | Place near the field, not in toast/modal | | Empty states | Clear message + action, never blank | | Disabled buttons | Explain why (tooltip or nearby text) |
// Always confirm before delete, remove, clear
<AlertDialog>
<AlertDialogTrigger>Delete</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogTitle>Delete this item?</AlertDialogTitle>
<AlertDialogDescription>This cannot be undone.</AlertDialogDescription>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction>Delete</AlertDialogAction>
</AlertDialogContent>
</AlertDialog>
Rule: Animations should be felt, not seen.
| Constraint | Value |
|------------|-------|
| Max duration | 200ms (never more) |
| Properties | Only transform and opacity (compositor properties) |
| Motion preference | Always respect prefers-reduced-motion |
import { motion } from "motion/react"
// Fade in
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.15 }}
>
{children}
</motion.div>
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches
// Skip animations if user prefers reduced motion
<motion.div
animate={prefersReducedMotion ? {} : { opacity: 1 }}
>
| Rule | Implementation |
|------|----------------|
| Headings | Use text-balance for multi-line headings |
| Z-index | Use fixed scale: z-10, z-20, z-30, z-40, z-50 |
| Spacing | Use Tailwind scale, no arbitrary values |
z-10 - Dropdowns, tooltips
z-20 - Sticky headers, floating buttons
z-30 - Modals, dialogs
z-40 - Notifications, toasts
z-50 - Critical overlays only
Prohibited (expensive):
box-shadow animationsfilter: blur() on scrollwidth, height, top, left)Allowed:
transform (translate, scale, rotate)opacity| Rule | Why | |------|-----| | No gradients | Unless user explicitly requests | | No drop shadows on text | Accessibility issue | | Clear empty states | Never show blank areas | | Consistent iconography | One icon set per project |
Before marking UI work done:
cn() for conditional classesprefers-reduced-motionWhen sub-issue has "UI Patterns" reference:
# Install required dependencies
npm install clsx tailwind-merge motion @radix-ui/react-dialog @radix-ui/react-dropdown-menu
# cn utility (add to lib/utils.ts)
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
development
Sync design tokens and components from a codebase to a Pencil canvas (`.pen` files), or set up a Pencil canvas from a style guide when no codebase exists. Use when the user says "sync pencil", "setup pencil", "configure pencil", "pencil sync", "sync tokens to pencil", "build pencil component library", or names Pencil/`.pen` files explicitly. Also triggers when mockups generated by Pencil don't match project conventions.
development
--- name: figma version: 1.6.0 description: Use when user says "figma", "figma it", "sync figma", "figma mockup", "create figma file", "design to figma", "figma from PRD", "figma from journey", "build in figma", or "figma design system" — anything that creates, syncs, or updates Figma design systems, components, variables, mockups, or front-end-ready screens. Always enumerates the linked Figma library FIRST (library-driven discovery, not per-need search), produces a block→DS mapping table for us
development
Use when the user wants to build Webflow pages, templates, or components, with or without Figma designs as reference.
testing
Use when the user wants to verify an implementation, validate acceptance criteria, or run a Vorbit-style post-change check using shared project rules.