ui-design-system
Original:🇺🇸 English
Translated
Generates consistent UI components, layouts, and design tokens following a design system. Enforces spacing, color, typography, and accessibility standards across React/TypeScript projects. Use when creating new UI components, building page layouts, choosing colors or typography, setting up design tokens, or reviewing UI code for design consistency. Covers 8pt spacing grid, Tailwind CSS token usage, shadcn/ui primitives, WCAG 2.1 AA compliance, responsive breakpoints, semantic HTML structure, and TypeScript component interfaces. Does NOT cover backend implementation (use python-backend-expert), testing (use react-testing-patterns), or deployment (use deployment-pipeline).
24installs
Sourcehieutrtr/ai1-skills
Added on
NPX Install
npx skill4agent add hieutrtr/ai1-skills ui-design-systemTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →UI Design System
When to Use
Activate this skill when:
- Creating new UI components that must follow a design system
- Building page layouts with consistent spacing and structure
- Setting up or extending design tokens (colors, typography, spacing)
- Choosing colors, fonts, or spacing values for a project
- Reviewing UI code for design consistency and accessibility
- Integrating shadcn/ui components into existing layouts
Do NOT use this skill for:
- Backend API implementation (use )
python-backend-expert - Component or hook testing (use )
react-testing-patterns - E2E browser testing (use )
e2e-testing - General React patterns unrelated to design system (use )
react-frontend-expert - Deployment or CI/CD (use )
deployment-pipeline
Instructions
Step 0: Read Existing Design Tokens
Before generating any UI code, check the project for existing tokens:
- Read (or
tailwind.config.ts) for custom theme extensions.js - Read or
src/styles/globals.cssfor CSS custom propertiesapp/globals.css - Read if shadcn/ui is configured
components.json
If no design tokens exist, generate a starter set and ask the user to confirm before proceeding (see Edge Cases).
Design Tokens
Color Tokens
Define colors as CSS custom properties consumed by Tailwind. Never use hardcoded hex/rgb values in components.
CSS custom properties (HSL format for shadcn/ui compatibility):
css
/* globals.css */
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222 47% 11%;
--primary: 221 83% 53%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222 47% 11%;
--muted: 210 40% 96%;
--muted-foreground: 215 16% 47%;
--accent: 210 40% 96%;
--accent-foreground: 222 47% 11%;
--destructive: 0 84% 60%;
--destructive-foreground: 210 40% 98%;
--border: 214 32% 91%;
--input: 214 32% 91%;
--ring: 221 83% 53%;
--radius: 0.5rem;
}
.dark {
--background: 222 47% 11%;
--foreground: 210 40% 98%;
--primary: 217 91% 60%;
--primary-foreground: 222 47% 11%;
--secondary: 217 33% 17%;
--secondary-foreground: 210 40% 98%;
--muted: 217 33% 17%;
--muted-foreground: 215 20% 65%;
--accent: 217 33% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--border: 217 33% 17%;
--input: 217 33% 17%;
--ring: 224 76% 48%;
}
}Tailwind config mapping:
ts
// tailwind.config.ts
export default {
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
},
},
},
} satisfies Config;Color usage rules:
- Always use semantic token classes: ,
bg-primary,text-foregroundborder-border - Never use raw Tailwind palette colors () in component code
bg-blue-500 - Every color must have a dark mode variant defined
- Use variants for text on colored backgrounds
foreground
Typography Scale
Define a typographic scale using Tailwind's font-size utilities:
| Token | Size | Line Height | Usage |
|---|---|---|---|
| 12px | 16px | Captions, helper text |
| 14px | 20px | Secondary text, labels |
| 16px | 24px | Body text (default) |
| 18px | 28px | Subheadings |
| 20px | 28px | Section headings |
| 24px | 32px | Page headings |
| 30px | 36px | Hero headings |
Typography rules:
- Set a base font in :
tailwind.config.tsfontFamily: { sans: ["Inter", "system-ui", "sans-serif"] } - Use (500) for headings and labels,
font-medium(400) for bodyfont-normal - Use for headings
tracking-tightand abovetext-2xl - Limit line length with (65ch) for readability
max-w-prose
Spacing (8pt Grid)
All spacing values follow an 8pt base grid:
| Tailwind Class | Value | Use Case |
|---|---|---|
| 4px | Inline icon padding, tight gaps |
| 8px | Compact element spacing |
| 12px | Input padding, small card padding |
| 16px | Standard component padding |
| 24px | Card padding, section gaps |
| 32px | Section padding |
| 48px | Page section spacing |
| 64px | Major layout spacing |
Spacing rules:
- Use for flex/grid children instead of individual margins
gap-* - Prefer for vertical stacking of sibling elements
space-y-* - Cards: padding with
p-6between internal elementsgap-4 - Page sections: or
py-12vertical paddingpy-16 - Never mix spacing systems (no )
margin: 13px
Component Structure
Hierarchy: Container > Layout > Content
Every component follows a three-layer structure:
tsx
// Container: outer wrapper with spacing, background, border
<Card className="p-6">
{/* Layout: flex/grid arrangement */}
<div className="flex items-center gap-4">
{/* Content: actual UI elements */}
<Avatar src={user.avatar} alt={user.name} />
<div className="space-y-1">
<h3 className="text-sm font-medium">{user.name}</h3>
<p className="text-sm text-muted-foreground">{user.role}</p>
</div>
</div>
</Card>Semantic HTML
Use the correct HTML element for every purpose:
| Element | Use For | Not |
|---|---|---|
| Clickable actions | |
| Navigation links | |
| Navigation regions | |
| Primary page content | |
| Self-contained content (card, post) | |
| Thematic grouping with heading | |
| Sidebar or tangential content | |
| Introductory content for a section | |
| Footer content for a section | |
| Lists of items | |
shadcn/ui Primitives
Prefer shadcn/ui components over custom implementations:
| Need | Use | Not |
|---|---|---|
| Buttons | | Custom |
| Modals | | Custom modal with portal |
| Dropdowns | | Custom dropdown |
| Cards | | Styled |
| Inputs | | Styled |
| Selects | | Native |
| Tooltips | | Custom tooltip |
| Tabs | | Custom tab component |
| Tables | | Plain |
| Alerts | | Custom banner div |
If shadcn/ui is not installed, fall back to plain Tailwind with equivalent patterns and consistent class ordering.
TypeScript Component Interfaces
Export props as TypeScript interfaces with JSDoc descriptions:
tsx
/** Props for the UserProfileCard component. */
interface UserProfileCardProps {
/** User data to display. */
user: User;
/** Called when the edit button is clicked. */
onEdit?: (userId: string) => void;
/** Visual variant of the card. */
variant?: "default" | "compact";
/** Additional CSS classes applied to the root element. */
className?: string;
}
export function UserProfileCard({
user,
onEdit,
variant = "default",
className,
}: UserProfileCardProps) {
// ...
}Props rules:
- Name interface
{ComponentName}Props - Include on every component for composition
className?: string - Use prop for visual variations, not separate components
variant - Default optional props in destructuring, not in interface
- Use union types for constrained string values:
"sm" | "md" | "lg"
Responsive Design
Breakpoints
Use Tailwind's mobile-first breakpoints:
| Prefix | Min Width | Target |
|---|---|---|
| (none) | 0px | Mobile (default) |
| 640px | Large phones / small tablets |
| 768px | Tablets |
| 1024px | Desktops |
| 1280px | Large desktops |
Responsive rules:
- Design mobile-first: base styles for mobile, then add breakpoint overrides
- Use for responsive grids
grid-cols-1 md:grid-cols-2 lg:grid-cols-3 - Stack navigation vertically on mobile:
flex-col md:flex-row - Hide non-essential elements on mobile:
hidden md:block - Set max container width:
max-w-7xl mx-auto px-4 sm:px-6 lg:px-8
Responsive Layout Pattern
tsx
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{items.map((item) => (
<ItemCard key={item.id} item={item} />
))}
</div>
</div>Accessibility (WCAG 2.1 AA)
Contrast Ratios
- Normal text (< 18px or < 14px bold): minimum 4.5:1 contrast ratio
- Large text (>= 18px or >= 14px bold): minimum 3:1 contrast ratio
- UI components and graphical objects: minimum 3:1 contrast ratio
Interactive Element Requirements
Every interactive element must have:
- Visible label or aria-label:
<Button aria-label="Close dialog">X</Button> - Focus indicator: Tailwind's utilities:
ringfocus-visible:ring-2 focus-visible:ring-ring - Keyboard access: Reachable via Tab, activatable via Enter/Space
- Disabled state: Both visual and or
aria-disabledattributedisabled
ARIA Patterns
- Icon-only buttons:
aria-label="Delete item" - Loading states: on the loading container
aria-busy="true" - Dynamic content updates: on the container
aria-live="polite" - Form errors: on the input,
aria-invalid="true"on the messagerole="alert" - Modals: , focus trap, Escape to close
aria-modal="true" - Navigation landmarks:
<nav aria-label="Main navigation">
Examples
User Profile Card
tsx
interface UserProfileCardProps {
user: { id: string; name: string; role: string; avatarUrl: string };
onEdit?: (userId: string) => void;
className?: string;
}
export function UserProfileCard({ user, onEdit, className }: UserProfileCardProps) {
return (
<Card className={cn("p-6", className)}>
<div className="flex items-center gap-4">
<Avatar className="h-12 w-12">
<AvatarImage src={user.avatarUrl} alt={user.name} />
<AvatarFallback>{user.name.charAt(0)}</AvatarFallback>
</Avatar>
<div className="space-y-1">
<h3 className="text-sm font-medium leading-none">{user.name}</h3>
<p className="text-sm text-muted-foreground">{user.role}</p>
</div>
</div>
{onEdit && (
<div className="mt-4">
<Button
variant="outline"
size="sm"
onClick={() => onEdit(user.id)}
aria-label={`Edit ${user.name}'s profile`}
>
Edit Profile
</Button>
</div>
)}
</Card>
);
}SaaS Dashboard Design Tokens Setup
Tailwind config extension:
ts
// tailwind.config.ts
import type { Config } from "tailwindcss";
export default {
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
},
fontFamily: {
sans: ["Inter", "system-ui", "sans-serif"],
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
},
},
} satisfies Config;Responsive Dashboard Layout
tsx
export function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-background">
<nav className="border-b border-border" aria-label="Main navigation">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center">
{/* nav content */}
</div>
</nav>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="grid grid-cols-1 lg:grid-cols-[240px_1fr] gap-8">
<aside className="hidden lg:block" aria-label="Sidebar">
{/* sidebar content */}
</aside>
<main>{children}</main>
</div>
</div>
</div>
);
}Edge Cases
-
No existing design tokens: Generate a starter token set (see) and present it to the user for confirmation before writing any component code. Ask: "No design tokens found. Here's a starter set — should I apply these?"
references/design-tokens-reference.md -
shadcn/ui not installed: Fall back to plain Tailwind with equivalent patterns. Useinstead of
<button className="...">, styled<Button>instead of<div>. Maintain the same spacing and color token approach.<Card> -
Component overlap: If a requested component duplicates an existing one, flag it: "A similarcomponent exists at
UserCard. Should I extend it or create a separate component?"src/components/UserCard.tsx -
Dark mode tokens missing: Iftokens exist but
:rootvariants are absent, generate matching dark variants before proceeding. Every semantic color token must have both light and dark values..dark -
Custom brand colors: When the user provides specific brand hex values, convert them to HSL and integrate into the token system. Never use the hex values directly in components.
-
Inconsistent spacing in existing code: Flag the inconsistency, suggest the closest 8pt grid values, and ask whether to normalize existing components or only apply the grid to new code.
See for starter token sets, color palette guide, and typography scales.
references/design-tokens-reference.md