From frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontend-design:frontend-designThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
Automatically activate for:
Before coding, understand the context and commit to a BOLD aesthetic direction:
CRITICAL: Choose a clear conceptual direction and execute it with precision.
Typography Slop:
Color Slop:
Layout Slop:
Component Slop:
Animation Slop:
Typography That Stands Out:
Display fonts: Clash Display, Cabinet Grotesk, Satoshi, Space Grotesk (sparingly),
Instrument Serif, Fraunces, Playfair Display, Editorial New
Body fonts: Geist, Plus Jakarta Sans, DM Sans, Source Serif Pro, Literata
Monospace: JetBrains Mono, Fira Code, IBM Plex Mono
Color With Intent:
Layouts That Break the Grid:
Components With Character:
Design components like you are the creator of React. Think in composition, reusability, and elegance.
Small, Focused Components:
Composition Over Configuration:
// BAD: Monolithic component with many props
<Card
title="User Profile"
subtitle="Settings"
avatar={user.avatar}
showBadge={true}
badgeColor="green"
actions={[...]}
/>
// GOOD: Composable components
<Card>
<Card.Header>
<Avatar src={user.avatar} />
<Card.Title>User Profile</Card.Title>
<Badge variant="success" />
</Card.Header>
<Card.Content>...</Card.Content>
<Card.Actions>...</Card.Actions>
</Card>
Meaningful, Typed Props:
// Generic, reusable props
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'ghost' | 'destructive';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
disabled?: boolean;
children: React.ReactNode;
}
// State props that tell a story
interface DataTableProps<T> {
data: T[];
columns: Column<T>[];
isLoading?: boolean;
isEmpty?: boolean;
onRowClick?: (row: T) => void;
selectedRows?: Set<string>;
}
Prop Patterns:
children for content (not content prop)renderItem, renderEmptyLocal State First:
// Keep state as close to where it's used as possible
function SearchInput({ onSearch }: { onSearch: (query: string) => void }) {
const [query, setQuery] = useState('');
const debouncedSearch = useDebouncedCallback(onSearch, 300);
return (
<Input
value={query}
onChange={(e) => {
setQuery(e.target.value);
debouncedSearch(e.target.value);
}}
/>
);
}
Lift State Only When Needed:
1. Container/Presenter Pattern:
// Container: handles data fetching, state
function UserProfileContainer({ userId }: { userId: string }) {
const { data: user, isLoading } = useUser(userId);
if (isLoading) return <UserProfileSkeleton />;
return <UserProfile user={user} />;
}
// Presenter: pure UI, receives props
function UserProfile({ user }: { user: User }) {
return (
<Card>
<Avatar src={user.avatar} />
<h2>{user.name}</h2>
</Card>
);
}
2. Compound Components:
// Parent provides context
const TabsContext = createContext<TabsContextValue>(null);
function Tabs({ children, defaultValue }: TabsProps) {
const [active, setActive] = useState(defaultValue);
return (
<TabsContext.Provider value={{ active, setActive }}>
<div className="tabs">{children}</div>
</TabsContext.Provider>
);
}
Tabs.List = TabsList;
Tabs.Tab = Tab;
Tabs.Panel = TabPanel;
// Usage
<Tabs defaultValue="overview">
<Tabs.List>
<Tabs.Tab value="overview">Overview</Tabs.Tab>
<Tabs.Tab value="settings">Settings</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="overview">...</Tabs.Panel>
</Tabs>
3. Render Props for Flexibility:
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
renderEmpty?: () => React.ReactNode;
keyExtractor: (item: T) => string;
}
function List<T>({ items, renderItem, renderEmpty, keyExtractor }: ListProps<T>) {
if (items.length === 0 && renderEmpty) return renderEmpty();
return (
<ul>
{items.map((item, i) => (
<li key={keyExtractor(item)}>{renderItem(item, i)}</li>
))}
</ul>
);
}
components/
├── ui/ # Primitive components (Button, Input, Card)
│ ├── button.tsx
│ ├── input.tsx
│ └── card.tsx
├── patterns/ # Composed patterns (DataTable, Form, Modal)
│ ├── data-table/
│ │ ├── data-table.tsx
│ │ ├── data-table-header.tsx
│ │ ├── data-table-row.tsx
│ │ └── index.ts
│ └── form/
├── features/ # Feature-specific components
│ ├── dashboard/
│ └── settings/
└── layouts/ # Page layouts
├── sidebar-layout.tsx
└── centered-layout.tsx
Component Slop:
useEffect for everythingany types on propsInstead:
Core Philosophy: shadcn/ui is NOT a component library—it's how you build your component library. You get actual component code that you own and can modify.
# Initialize shadcn/ui
npx shadcn@latest init
# Add components
npx shadcn@latest add button card dialog
# Search registry
npx shadcn@latest search @shadcn -q "button"
| Category | Components |
|---|---|
| Form & Input | Form, Field, Button, Input, Textarea, Checkbox, Radio, Select, Switch, Slider, Calendar, Date Picker, Combobox |
| Layout & Navigation | Accordion, Breadcrumb, Navigation Menu, Sidebar, Tabs, Separator, Scroll Area, Resizable |
| Overlays & Dialogs | Dialog, Alert Dialog, Sheet, Drawer, Popover, Tooltip, Hover Card, Context Menu, Dropdown Menu, Command |
| Feedback & Status | Alert, Toast, Progress, Spinner, Skeleton, Badge, Empty |
| Display & Media | Avatar, Card, Table, Data Table, Chart, Carousel, Aspect Ratio, Typography |
// Color convention: background + foreground
<div className="bg-background text-foreground">Hello</div>
<div className="bg-primary text-primary-foreground">Primary</div>
<div className="bg-muted text-muted-foreground">Muted</div>
Customize the theme - Don't use defaults
:root {
--radius: 0.5rem; /* or 0 for sharp corners */
--primary: 220 13% 10%; /* custom primary */
}
Extend components - Add custom variants, modify animations, adjust spacing
Combine primitives - Layer components for unique effects
See references/shadcn.md for:
When creating charts:
Style the chart to match the UI
<ResponsiveContainer>
<LineChart data={data}>
<Line
type="monotone"
strokeWidth={2}
dot={false}
stroke="hsl(var(--primary))"
/>
<XAxis
tickLine={false}
axisLine={false}
tick={{ fill: 'hsl(var(--muted-foreground))' }}
/>
</LineChart>
</ResponsiveContainer>
Remove visual clutter
Add meaningful interactions
Before considering frontend work complete:
When implementing frontend:
Remember: Claude is capable of extraordinary creative work. Commit fully to a distinctive vision that could only have been designed for this specific context.
The difference between good and exceptional interfaces lies in microscopic details that users feel but don't consciously notice. These patterns make interfaces feel "expensive" and polished.
Text Wrapping for Headlines:
/* Prevents awkward widows in headlines */
.hero-title {
text-wrap: balance; /* Optimizes line breaks for headlines */
}
/* For multi-line text where you want pretty breaks */
.description {
text-wrap: pretty; /* Last line minimum 4 characters */
}
balance for headlines, titles, and short text blockspretty for descriptions, summaries, and body text where you want to avoid orphansFont Smoothing:
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body or typography containersTabular Numbers:
.price, .metric, .stat {
font-variant-numeric: tabular-nums;
}
Concentric Formula:
/* Outer radius = Inner radius + padding */
.card {
padding: 1.5rem;
border-radius: 16px;
}
.card-inner {
border-radius: calc(16px - 1.5rem); /* Concentric borders align perfectly */
}
Contextual Icon States:
.icon {
transition: all 0.2s ease;
opacity: 0.6;
transform: scale(1);
}
.icon:hover {
opacity: 1;
transform: scale(1.1);
}
.icon.active {
opacity: 1;
transform: scale(1);
filter: drop-shadow(0 0 8px currentColor);
}
filter: drop-shadow() instead of box-shadow for icons (respects shape)Interruptible Animations:
/* GOOD: CSS transitions—user can interrupt */
.button {
transition: transform 0.2s ease, opacity 0.2s ease;
}
.button:hover {
transform: translateY(-2px);
}
/* AVOID: Keyframes for interactions—can't be interrupted */
@keyframes slideUp {
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
Split and Stagger Enter:
/* Elements enter from different directions */
.card:nth-child(3n+1) { animation: slideFromLeft 0.4s ease forwards; }
.card:nth-child(3n+2) { animation: slideFromBottom 0.4s ease forwards; }
.card:nth-child(3n+3) { animation: slideFromRight 0.4s ease forwards; }
/* Stagger with delay */
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 50ms; }
.card:nth-child(3) { animation-delay: 100ms; }
Subtle Exit Animations:
.modal.closing {
animation: fadeOut 0.15s ease forwards;
}
@keyframes fadeOut {
to { opacity: 0; transform: scale(0.98); }
}
Optical vs Geometric Alignment:
/* Geometric center looks "off" with triangle icons */
.icon-triangle {
transform: translateY(-1px); /* Nudge down for optical center */
}
/* Circles appear smaller than squares at same size */
.icon-circle {
transform: scale(1.1); /* Slight scale for visual balance */
}
Shadows Over Borders:
.card {
/* Instead of: border: 1px solid #e5e5e5; */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08),
0 1px 2px rgba(0, 0, 0, 0.04);
}
.card-elevated {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
Image Outlines:
.image-container {
position: relative;
}
.image-container::after {
content: '';
position: absolute;
inset: 0;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: inherit;
pointer-events: none;
}
/* Fast interactions feel responsive */
.fast-interaction { transition-duration: 150ms; }
/* Standard interactions feel smooth */
.standard-interaction { transition-duration: 200ms; }
/* Slow animations feel deliberate */
.deliberate-motion { transition-duration: 400ms; }
Before shipping UI:
text-wrap: balanceantialiased)tabular-numsCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub duyet/codex-claude-plugins --plugin frontend-design