| name | flow-code-frontend-ui |
| description | Use when building or modifying user-facing interfaces, creating components, implementing layouts, managing state, or when UI output needs to look production-quality rather than AI-generated |
| tier | 2 |
| user-invocable | true |
Frontend UI Engineering
Startup: Follow Startup Sequence before proceeding.
flowctl Setup
FLOWCTL="$HOME/.flow/bin/flowctl"
Overview
Build production-quality user interfaces that are accessible, performant, and visually polished. The goal is UI that looks like it was built by a design-aware engineer -- not like it was generated by an AI. This means real design system adherence, proper accessibility, thoughtful interaction patterns, and no generic "AI aesthetic."
When to Use
- Building new UI components or pages
- Modifying existing user-facing interfaces
- Implementing responsive layouts
- Adding interactivity or state management
- Fixing visual or UX issues
- Worker tasks tagged with
--domain frontend
When NOT to use:
- Backend-only changes with no UI impact
- CLI tool development (use flow-code-api-design instead)
- Pure data model / schema changes
Phase 1: Audit Before Coding
Before writing any UI code:
-
Detect existing design system:
- Search for Tailwind config, CSS variables, theme files, component library imports
- Check for design tokens (
colors, spacing, typography in config)
- Identify component library (shadcn, MUI, Ant Design, Radix, custom)
-
Read existing patterns:
- Find 2-3 similar components in the codebase — match their conventions
- Check for shared layout components, wrappers, or utility classes
- Note naming patterns (PascalCase components, kebab-case files, etc.)
-
Understand the state model:
- Where does data come from? (props, context, server state, URL params)
- What state management is already in use? (don't introduce a new one)
SKIP this phase ONLY if you're fixing a single CSS property.
Phase 2: Component Architecture
File Structure
Colocate everything related to a component:
src/components/
TaskList/
TaskList.tsx # Component implementation
TaskList.test.tsx # Tests
use-task-list.ts # Custom hook (if complex state)
types.ts # Component-specific types (if needed)
Composition Over Configuration
<Card>
<CardHeader>
<CardTitle>Tasks</CardTitle>
</CardHeader>
<CardBody>
<TaskList tasks={tasks} />
</CardBody>
</Card>
<Card
title="Tasks"
headerVariant="large"
bodyPadding="md"
content={<TaskList tasks={tasks} />}
/>
Separate Data from Presentation
function TaskListContainer() {
const { tasks, isLoading, error } = useTasks();
if (isLoading) return <TaskListSkeleton />;
if (error) return <ErrorState message="Failed to load tasks" retry={refetch} />;
if (tasks.length === 0) return <EmptyState message="No tasks yet" />;
return <TaskList tasks={tasks} />;
}
function TaskList({ tasks }: { tasks: Task[] }) {
return (
<ul role="list" className="divide-y">
{tasks.map(task => <TaskItem key={task.id} task={task} />)}
</ul>
);
}
State Management Pyramid
Choose the simplest approach that works:
useState --> Component-specific UI state
Lifted state --> Shared between 2-3 sibling components
Context --> Theme, auth, locale (read-heavy, write-rare)
URL state --> Filters, pagination, shareable UI state
Server state (SWR) --> Remote data with caching
Global store --> Complex client state shared app-wide
Avoid prop drilling deeper than 3 levels. If passing props through components that don't use them, restructure or use context.
Phase 3: Design System Adherence
Defeat the AI Aesthetic
AI-generated UI has recognizable patterns. Avoid all of them:
| AI Default | Why It's a Problem | Production Quality |
|---|
| Purple/indigo everything | Every AI app looks identical | Use the project's actual color palette |
| Excessive gradients | Visual noise, clashes with design systems | Flat or subtle gradients matching the system |
Rounded everything (rounded-2xl) | Ignores corner radius hierarchy | Consistent border-radius from design tokens |
| Generic hero sections | Template-driven, no connection to content | Content-first layouts |
| Lorem ipsum copy | Hides layout problems (overflow, wrapping) | Realistic placeholder content |
| Oversized padding everywhere | Destroys visual hierarchy, wastes space | Consistent spacing scale |
| Stock card grids | Ignores information priority | Purpose-driven layouts |
| Shadow-heavy design | Competes with content, slow on low-end | Subtle or no shadows per design system |
Spacing
Use the project's spacing scale. Don't invent values:
padding: 1rem;
gap: 0.75rem;
padding: 13px;
margin-top: 2.3rem;
Typography
Respect the type hierarchy:
h1 --> Page title (one per page)
h2 --> Section title
h3 --> Subsection title
body --> Default text
small --> Secondary/helper text
Don't skip heading levels. Don't use heading styles for non-heading content.
Color
- Use semantic tokens:
text-primary, bg-surface, border-default -- not raw hex
- Ensure contrast: 4.5:1 for normal text, 3:1 for large text
- Never rely on color alone to convey information (add icons, text, or patterns)
Phase 4: Accessibility (WCAG 2.1 AA)
Every component must meet these standards:
Keyboard Navigation
<button onClick={handleClick}>Click me</button> // OK
<div onClick={handleClick}>Click me</div> // BAD
<div role="button" tabIndex={0} onClick={handleClick}
onKeyDown={e => e.key === 'Enter' && handleClick()}>
Click me
</div>
ARIA Labels
<button aria-label="Close dialog"><XIcon /></button>
<label htmlFor="email">Email</label>
<input id="email" type="email" />
Focus Management
function Dialog({ isOpen, onClose }) {
const closeRef = useRef(null);
useEffect(() => {
if (isOpen) closeRef.current?.focus();
}, [isOpen]);
return (
<dialog open={isOpen}>
<button ref={closeRef} onClick={onClose}>Close</button>
{/* content */}
</dialog>
);
}
Required States
Every data-driven component needs all three:
if (isLoading) return <Skeleton aria-busy="true" />;
if (error) return <ErrorState message={error.message} retry={refetch} />;
if (!data.length) return <EmptyState action={onCreate} />;
return <DataList items={data} />;
Phase 5: Responsive Design
Design for mobile first, then expand:
<div className="
grid grid-cols-1
sm:grid-cols-2
lg:grid-cols-3
gap-4
">
Test at these breakpoints: 320px, 768px, 1024px, 1440px.
Loading and Transitions
function ListSkeleton() {
return (
<div className="space-y-3" aria-busy="true" aria-label="Loading">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="h-12 bg-muted animate-pulse rounded" />
))}
</div>
);
}
Use optimistic updates for perceived speed on user actions (toggles, deletes).
Common Rationalizations
| Rationalization | Reality |
|---|
| "Accessibility is a nice-to-have" | Legal requirement in many jurisdictions. Engineering quality standard. |
| "We'll make it responsive later" | Retrofitting responsive design is 3x harder than building it first. |
| "The design isn't final so I'll skip styling" | Use design system defaults. Unstyled UI creates broken first impressions. |
| "This is just a prototype" | Prototypes become production code. Build the foundation right. |
| "The AI aesthetic is fine for now" | It signals low quality to every reviewer. Use the project's design system. |
| "I'll add empty/error states later" | Users hit edge cases immediately. Handle them now or ship bugs. |
| "Keyboard navigation can wait" | ~15% of users rely on keyboard. Ship accessible or ship broken. |
Red Flags
- Components over 200 lines (split them)
- Inline styles or arbitrary pixel values not on the spacing scale
- Missing error, loading, or empty states
- No keyboard navigation testing
- Color as the sole indicator of state (red/green without text/icons)
- Generic AI look: purple gradients, oversized cards, uniform shadows
- New state management library when one already exists in the project
div with onClick instead of button or a
- Skipped heading levels (h1 then h3)
- Raw hex colors instead of semantic tokens
Verification
After building UI:
See also: Accessibility Checklist for full WCAG 2.1 AA reference.