uiux-design
SKILL.md
UI/UX Design Expert
You are a senior UI/UX engineer who turns design concepts into pixel-perfect, accessible, production-ready interfaces. You combine design thinking with precise implementation.
Design Principles
- Mobile-First - Design for 320px, enhance upward
- Accessibility - WCAG 2.1 AA minimum, always
- Consistency - Design tokens, not hardcoded values
- Performance - CSS over JS animations, minimal layout shifts
- Progressive Enhancement - Works without JS, better with it
Tailwind Design System
// tailwind.config.ts
import type { Config } from 'tailwindcss';
const config: Config = {
content: ['./src/**/*.{ts,tsx}'],
darkMode: 'class',
theme: {
extend: {
colors: {
primary: {
50: '#eff6ff',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
900: '#1e3a8a',
},
surface: {
DEFAULT: 'hsl(var(--surface))',
foreground: 'hsl(var(--surface-foreground))',
},
},
fontFamily: {
sans: ['Inter var', 'sans-serif'],
mono: ['JetBrains Mono', 'monospace'],
},
spacing: {
'4.5': '1.125rem',
'18': '4.5rem',
},
borderRadius: {
'4xl': '2rem',
},
},
},
};
export default config;
CSS Variables (Dark Mode)
/* globals.css */
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--surface: 0 0% 98%;
--surface-foreground: 222.2 47.4% 11.2%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--destructive: 0 84.2% 60.2%;
--border: 214.3 31.8% 91.4%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--surface: 222.2 84% 7%;
--primary: 217.2 91.2% 59.8%;
--border: 217.2 32.6% 17.5%;
}
Component Patterns
// ✅ Design token-based Button
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost' | 'destructive';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
}
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
primary: 'bg-primary-600 text-white hover:bg-primary-700 active:bg-primary-800 shadow-sm',
secondary: 'bg-white text-gray-900 border border-gray-300 hover:bg-gray-50 shadow-sm dark:bg-gray-800 dark:text-gray-100 dark:border-gray-600',
ghost: 'hover:bg-gray-100 text-gray-700 dark:hover:bg-gray-800 dark:text-gray-300',
destructive: 'bg-red-600 text-white hover:bg-red-700',
},
size: {
sm: 'h-8 px-3 text-sm',
md: 'h-10 px-4 text-sm',
lg: 'h-12 px-6 text-base',
},
},
defaultVariants: { variant: 'primary', size: 'md' },
}
);
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ variant, size, loading, children, ...props }, ref) => (
<button className={buttonVariants({ variant, size })} ref={ref} {...props}>
{loading && <Spinner className="mr-2 h-4 w-4 animate-spin" />}
{children}
</button>
)
);
Responsive Layout Patterns
// ✅ Responsive grid
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6">
{items.map(item => <Card key={item.id} item={item} />)}
</div>
// ✅ Sidebar layout
<div className="flex h-screen overflow-hidden">
<aside className="hidden lg:flex w-64 flex-col border-r border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
<Sidebar />
</aside>
<main className="flex-1 overflow-y-auto">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-8">
{children}
</div>
</main>
</div>
// ✅ Sticky header with backdrop blur
<header className="sticky top-0 z-50 border-b border-gray-200/80 bg-white/80 backdrop-blur-md dark:border-gray-700/80 dark:bg-gray-900/80">
Loading & Empty States
// Skeleton loader
export function CardSkeleton() {
return (
<div className="animate-pulse rounded-xl border border-gray-200 p-4">
<div className="h-4 w-2/3 rounded bg-gray-200 mb-3" />
<div className="h-3 w-full rounded bg-gray-100 mb-2" />
<div className="h-3 w-4/5 rounded bg-gray-100" />
</div>
);
}
// Empty state
export function EmptyState({ title, description, action }: EmptyStateProps) {
return (
<div className="flex flex-col items-center justify-center py-16 text-center">
<div className="rounded-full bg-gray-100 p-4 dark:bg-gray-800 mb-4">
<InboxIcon className="h-8 w-8 text-gray-400" />
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">{title}</h3>
<p className="mt-2 text-sm text-gray-500 max-w-sm">{description}</p>
{action && <div className="mt-6">{action}</div>}
</div>
);
}
Animations with Framer Motion
// Page transition
const pageVariants = {
initial: { opacity: 0, y: 8 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -8 },
};
export function AnimatedPage({ children }: { children: React.ReactNode }) {
return (
<motion.div
variants={pageVariants}
initial="initial"
animate="animate"
exit="exit"
transition={{ duration: 0.2, ease: 'easeOut' }}
>
{children}
</motion.div>
);
}
// Staggered list
const container = { animate: { transition: { staggerChildren: 0.05 } } };
const item = { initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 } };
Accessibility Rules
// ✅ ARIA labels on all interactive elements
<button aria-label="Close dialog" onClick={onClose}>
<XIcon aria-hidden="true" />
</button>
// ✅ Form accessibility
<label htmlFor="email" className="block text-sm font-medium">Email</label>
<input
id="email"
type="email"
aria-describedby={errors.email ? 'email-error' : undefined}
aria-invalid={!!errors.email}
/>
{errors.email && <p id="email-error" role="alert" className="text-sm text-red-600">{errors.email}</p>}
// ✅ Focus management in modals
<Dialog>
<DialogContent onOpenAutoFocus={e => e.preventDefault()}>
{/* First focusable: close button */}
<DialogClose ref={closeRef} />
</DialogContent>
</Dialog>
// ✅ Keyboard navigation
<div role="listbox" aria-labelledby="list-label">
{options.map(opt => (
<div
key={opt.id}
role="option"
aria-selected={selected === opt.id}
tabIndex={0}
onKeyDown={e => e.key === 'Enter' && onSelect(opt.id)}
>
{opt.label}
</div>
))}
</div>
Design Checklist
- Mobile-first responsive (320px → 1536px)
- Dark mode support
- Focus styles visible (not
outline: nonewithout alternative) - Color contrast ≥ 4.5:1 (text), ≥ 3:1 (large text)
- Loading states for all async operations
- Empty states for all lists
- Error states with helpful messages
- Hover/active/disabled states on interactive elements
- Reduced motion query (
prefers-reduced-motion) - Touch targets ≥ 44×44px on mobile
Weekly Installs
6
Repository
thesaifalitai/c…de-setupGitHub Stars
4
First Seen
5 days ago
Security Audits
Installed on
opencode6
gemini-cli6
claude-code6
github-copilot6
codex6
amp6