| name | frontend-design-standards |
| description | Use when generating React/TypeScript UI components, forms, layouts, or pages - prevents generic AI aesthetic, enforces accessibility, semantic HTML, theme compliance, minimum text sizes, proper states, and brand differentiation |
Frontend Design Standards
Overview
AI-generated UI suffers from systematic failures: generic aesthetics, accessibility violations, div soup, small text, missing states, and brand blindness. This skill enforces production-quality standards that prevent the "AI look."
Core principle: Every component must pass accessibility, theming, responsiveness, and semantic HTML checks BEFORE considering the task complete.
Mandatory Checklist
Run through EVERY category for EVERY component. No exceptions.
1. Theme Compliance (Zero Hardcoded Colors)
FORBIDDEN - Tailwind color classes:
className="bg-gray-50 text-gray-900 border-gray-200"
className="bg-blue-600 text-white hover:bg-blue-700"
className="text-green-500 bg-red-100"
REQUIRED - Theme variables:
className="bg-background text-foreground border-border"
className="bg-primary text-primary-foreground hover:bg-primary/90"
className="text-success bg-destructive/10"
FORBIDDEN - Hardcoded Hex Values:
className="bg-[#F3F4F6] text-[#111827]"
style={{ backgroundColor: '#ff0000' }}
Verification:
grep -rn "bg-gray\|bg-blue\|bg-green\|bg-red\|text-gray\|text-blue" --include="*.tsx"
grep -rn "#[0-9a-fA-F]\{3,6\}" --include="*.tsx"
If a semantic color doesn't exist, ADD IT to the theme system - don't use raw Tailwind colors or Hex values.
2. Typography Standards (16px Minimum Body Text)
FORBIDDEN:
className="text-sm"
className="text-xs"
ALLOWED uses of small text:
- Timestamps, metadata, captions:
text-sm acceptable
- Legal disclaimers:
text-xs acceptable
- Labels for inputs:
text-sm acceptable
Body text, paragraphs, descriptions MUST be:
className="text-base"
className="text-lg"
Line height requirement:
className="leading-relaxed"
className="leading-normal"
3. Semantic HTML (No Div Soup)
FORBIDDEN - Generic divs for semantic content:
<div className="navigation">
<div onClick={handleClick}>Home</div>
</div>
REQUIRED - Proper semantic elements:
| Content Type | Required Element |
|---|
| Navigation | <nav aria-label="Main"> |
| Main content area | <main> |
| Article/post | <article> |
| Section with heading | <section aria-labelledby="id"> |
| Side content | <aside> |
| Page footer | <footer> |
| Clickable action | <button type="button"> |
| Form submission | <button type="submit"> |
| Navigation link | <a href=""> or Next.js <Link> |
| List of items | <ul>/<ol> with <li> |
| Headings | <h1> through <h6> in order |
Heading hierarchy rule: Never skip levels. After <h2>, use <h3>, not <h4>.
4. Accessibility Requirements
Skip link for keyboard users (on page layouts):
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-background focus:text-foreground focus:ring-2 focus:ring-ring"
>
Skip to main content
</a>
<main id="main-content">
Every interactive element MUST have:
<button
aria-label="Close dialog"
type="button"
>
<X className="h-5 w-5" />
</button>
<button
aria-expanded={isOpen} // REQUIRED for expandable controls
aria-controls="menu-id" // REQUIRED - ID of controlled element
>
// Form inputs
<label htmlFor="email">Email</label> // REQUIRED - visible label
<input
id="email"
aria-describedby="email-error" // REQUIRED when error present
aria-invalid={hasError} // REQUIRED when validation fails
/>
<p id="email-error" role="alert">{error}</p> // Announced to screen readers
Focus management:
className="focus:ring-2 focus:ring-ring focus:ring-offset-2"
className="focus-visible:outline-none focus-visible:ring-2"
Color contrast: Verify all text meets WCAG AA (4.5:1 for normal text, 3:1 for large text).
Images require alt text:
<img src="chart.png" alt="Sales increased 40% in Q3" />
<img src="decorative-swoosh.svg" alt="" role="presentation" />
<img src="product.jpg" />
5. Component States (No Happy Path Only)
Every component that loads data or accepts input MUST handle:
| State | Required Implementation |
|---|
| Loading | Skeleton or spinner with aria-busy="true" |
| Error | Error message with role="alert", retry action |
| Empty | Helpful empty state with action suggestion |
| Success | Confirmation with role="status" |
FORBIDDEN - Silent failures:
} catch (error) {
console.error(error);
}
REQUIRED - User-visible feedback:
} catch (error) {
setError("Failed to load. Please try again.");
}
{error && (
<div role="alert" className="text-destructive">
{error}
<button onClick={retry}>Retry</button>
</div>
)}
6. Responsive Design Requirements
Touch target minimum:
className="min-h-[44px] min-w-[44px]"
className="p-3"
Mobile-first breakpoints:
className="flex flex-col md:flex-row"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3"
className="flex flex-row sm:flex-col"
Required responsive considerations:
- Does navigation collapse to hamburger menu?
- Do touch targets meet 44px minimum?
- Does text remain readable without horizontal scroll?
- Do images have responsive sizing?
7. Brand Differentiation (Anti-Generic)
Before generating UI, ASK or DETERMINE:
- Color personality: What emotions should colors evoke?
- Typography character: Playful, professional, technical, luxurious?
- Shape language: Sharp/angular or soft/rounded?
- Density: Spacious/breathing or compact/efficient?
- Motion: Bouncy/playful or subtle/refined?
If brand requirements are vague, DO NOT default to:
- Gray backgrounds with blue accents
- Inter/system font with slate text
- Standard 3-column feature grids
- Purple gradients
Instead, ask: "What makes this brand/product unique? What should users FEEL?"
8. Motion and Micro-interactions (Anti-Dead Interface)
AI-generated UIs often feel "static" or "dead." Consider:
className="transition-colors duration-200"
className="transition-transform hover:scale-105"
className="animate-pulse"
className="animate-spin"
Motion principles:
- Hover states on all interactive elements
- Transitions between states (not instant jumps)
- Loading indicators for async operations
- Success/error feedback animations
Don't overdo it: Subtle > flashy. Respect prefers-reduced-motion.
9. Code Validity (No Hallucinations)
AI sometimes generates non-existent CSS properties or imports libraries/components that aren't installed.
FORBIDDEN - Hallucinated/obsolete CSS:
width: fit-parent;
text-fill-color: transparent;
-webkit-border-radius: 8px;
FORBIDDEN - Hallucinated Imports:
import { Sparkles } from '@heroicons/react/solid';
import { useMagicHook } from 'react-use';
Rule:
- Check
package.json before importing a 3rd party library.
- Check file structure before importing local components (don't assume
@/components/ui/card exists).
- Check MDN if unsure about a CSS property.
10. Project Structure (Anti-Monolith)
AI tends to dump everything into a single file. This is unmaintainable.
FORBIDDEN - The "God Component":
- Single file > 250 lines (unless it's a complex data table).
- Defining multiple complex sub-components in the same file.
- Mixing heavy business logic (
useEffect, data fetching) with UI rendering.
REQUIRED - Separation of Concerns:
- Extract Types: Move interfaces to
types.ts if shared, or top of file.
- Extract Hooks: Move complex logic to
useFeatureName.ts.
- Extract Components: If a sub-component renders > 20 lines of JSX, move it to its own file.
- Colocation: Keep related files together (e.g.,
components/Feature/Feature.tsx, components/Feature/useFeature.ts).
Verification Checklist
Before marking any UI work complete, verify:
Common Rationalizations
| Excuse | Reality |
|---|
| "text-sm is fine for labels" | Labels yes, but body text/descriptions must be 16px+ |
| "Gray is neutral and professional" | Gray is also generic and forgettable. Ask about brand. |
| "I'll add accessibility later" | Later never comes. 80% of sites fail WCAG. Do it now. |
| "Loading states weren't requested" | Real apps have latency. Always include loading states. |
| "The design looks clean" | "Clean" often means "generic." Ask what makes it unique. |
| "Tailwind gray matches the theme" | Use theme variables. bg-muted exists for this. |
| "Mobile can be handled later" | 60%+ traffic is mobile. Design mobile-first. |
| "Div works fine here" | Divs have no semantic meaning. Use proper elements. |
| "Client is waiting, no time to ask" | 5 min asking saves hours of rework. Ask about brand. |
| "Just console.error is fine" | Users see nothing. Always show visible error feedback. |
| "Skip link is overkill" | Keyboard users can't use your site without it. Required. |
| "I'll make it unique with colors" | Unique = structure + typography + spacing, not just hue. |
| "aria-label on every button is tedious" | Accessibility is not optional. Screen readers need it. |
| "Alt text can be added later" | Missing alt = broken for screen readers. Add it now. |
| "This CSS property should work" | If MDN doesn't have it, it doesn't exist. Check first. |
| "Animations are nice-to-have" | Static UIs feel dead. Basic transitions are required. |
Red Flags - Stop and Reconsider
If you catch yourself doing ANY of these, STOP:
- Using
bg-gray-*, text-gray-*, or Hex codes (#F3F4F6)
- Importing libraries without checking
package.json
- Creating "God Components" (>250 lines) without splitting
- Body text smaller than 16px (
text-sm for paragraphs)
- Interactive elements without accessible names
- No loading or error state handling
- All buttons/cards look identical
- Can't explain what makes this design unique
- No semantic landmarks (
<nav>, <main>, <article>)
- Skip link not implemented for keyboard users
- Assuming brand requirements instead of asking
- Using
console.error() without user-visible feedback
- Missing
aria-expanded on toggle buttons
- Using
onClick on divs instead of buttons
- Images without alt attributes
- Using CSS properties you haven't verified exist
- No hover states or transitions on buttons/links
Quick Reference
Semantic Color Mapping
| Concept | Theme Variable | NOT This |
|---|
| Page background | bg-background | bg-white, bg-gray-50 |
| Card background | bg-card | bg-white, bg-gray-100 |
| Primary action | bg-primary | bg-blue-600, bg-indigo-500 |
| Destructive | bg-destructive | bg-red-500, bg-red-600 |
| Muted/secondary | bg-muted | bg-gray-100, bg-slate-100 |
| Borders | border-border | border-gray-200 |
| Main text | text-foreground | text-gray-900, text-black |
| Secondary text | text-muted-foreground | text-gray-500, text-gray-600 |
Text Size Guide
| Usage | Class | Size | Line Height |
|---|
| Hero headline | text-4xl to text-6xl | 36-60px | leading-tight |
| Section heading | text-2xl to text-3xl | 24-30px | leading-snug |
| Card title | text-lg to text-xl | 18-20px | leading-normal |
| Body text | text-base | 16px | leading-relaxed |
| Metadata only | text-sm | 14px | leading-normal |
Essential ARIA Patterns
<button aria-expanded={open} aria-controls="content-id">
<div id="content-id" hidden={!open}>
// Form error
<input aria-invalid={!!error} aria-describedby="error-id" />
<p id="error-id" role="alert">{error}</p>
// Loading content
<div aria-busy={loading} aria-live="polite">
// Dynamic announcements
<div role="status" aria-live="polite">{message}</div>