UI/UX Builder Skill Overview
This skill helps you build beautiful, accessible, and responsive user interfaces using modern React patterns, Tailwind CSS, and component libraries like Shadcn/ui.
Core Principles 1. Mobile-First Design Start with mobile layout Use responsive breakpoints: sm (640px), md (768px), lg (1024px), xl (1280px) Test on multiple screen sizes 2. Accessibility Use semantic HTML Add ARIA labels where needed Ensure keyboard navigation works Maintain color contrast ratios Support screen readers 3. Performance Lazy load heavy components Optimize images Use CSS animations over JS when possible Minimize re-renders 4. Consistency Use design tokens (colors, spacing, typography) Follow established patterns Maintain visual hierarchy Tailwind CSS Patterns Layout // Flex layouts
// Grid layouts
// Container
// Centering
Responsive Design // Mobile first
// Hide/show by breakpoint
// Responsive padding/margin
Colors & Themes // Background colors
// Text colors
Text
// Hover states
// Focus states
Spacing // Margin/Padding scale: 0, 1(4px), 2(8px), 4(16px), 6(24px), 8(32px), 12(48px), 16(64px)
Common Component Patterns Button // components/ui/button.tsx import { cn } from '@/lib/utils'
interface ButtonProps extends React.ButtonHTMLAttributes
export function Button({ className, variant = 'default', size = 'md', ...props }: ButtonProps) { return ( ) }
Card // components/ui/card.tsx export function Card({ children, className }: { children: React.ReactNode; className?: string }) { return (
export function CardHeader({ children }: { children: React.ReactNode }) { return
export function CardTitle({ children }: { children: React.ReactNode }) { return
{children}
}export function CardContent({ children }: { children: React.ReactNode }) { return
Input
// components/ui/input.tsx
export function Input({ className, ...props }: React.InputHTMLAttributes
Modal/Dialog 'use client' import { useEffect } from 'react' import { X } from 'lucide-react'
interface DialogProps { open: boolean onClose: () => void title?: string children: React.ReactNode }
export function Dialog({ open, onClose, title, children }: DialogProps) { useEffect(() => { if (open) { document.body.style.overflow = 'hidden' } else { document.body.style.overflow = 'unset' } return () => { document.body.style.overflow = 'unset' } }, [open])
if (!open) return null
return (
{/* Dialog */}
<div className="relative z-50 w-full max-w-lg rounded-lg bg-white p-6 shadow-xl">
{title && (
<div className="mb-4 flex items-center justify-between">
<h2 className="text-xl font-semibold">{title}</h2>
<button
onClick={onClose}
className="rounded-md p-1 hover:bg-gray-100"
>
<X className="h-5 w-5" />
</button>
</div>
)}
{children}
</div>
</div>
) }
Dropdown Menu 'use client' import { useState, useRef, useEffect } from 'react'
export function DropdownMenu({ trigger, children }: {
trigger: React.ReactNode
children: React.ReactNode
}) {
const [open, setOpen] = useState(false)
const ref = useRef
useEffect(() => { function handleClickOutside(event: MouseEvent) { if (ref.current && !ref.current.contains(event.target as Node)) { setOpen(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, [])
return (
export function DropdownMenuItem({ children, onClick }: { children: React.ReactNode onClick?: () => void }) { return ( ) }
Form // components/forms/contact-form.tsx 'use client' import { useState } from 'react' import { Input } from '@/components/ui/input' import { Button } from '@/components/ui/button'
export function ContactForm() {
const [loading, setLoading] = useState(false)
const [errors, setErrors] = useState
async function handleSubmit(e: React.FormEvent
const formData = new FormData(e.currentTarget)
const email = formData.get('email') as string
const message = formData.get('message') as string
// Validation
const newErrors: Record<string, string> = {}
if (!email) newErrors.email = 'Email is required'
if (!message) newErrors.message = 'Message is required'
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors)
setLoading(false)
return
}
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, message }),
})
if (!response.ok) throw new Error('Failed to send')
// Success
e.currentTarget.reset()
alert('Message sent!')
} catch (error) {
setErrors({ submit: 'Failed to send message' })
} finally {
setLoading(false)
}
}
return (