|
| 1 | +--- |
| 2 | +name: flow-code-frontend-ui |
| 3 | +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" |
| 4 | +tier: 2 |
| 5 | +user-invocable: true |
| 6 | +--- |
| 7 | +<!-- SKILL_TAGS: frontend,ui,design,accessibility,components --> |
| 8 | + |
| 9 | +# Frontend UI Engineering |
| 10 | + |
| 11 | +> **Startup:** Follow [Startup Sequence](../_shared/preamble.md) before proceeding. |
| 12 | +
|
| 13 | +## flowctl Setup |
| 14 | + |
| 15 | +```bash |
| 16 | +FLOWCTL="$HOME/.flow/bin/flowctl" |
| 17 | +``` |
| 18 | + |
| 19 | +## Overview |
| 20 | + |
| 21 | +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." |
| 22 | + |
| 23 | +## When to Use |
| 24 | + |
| 25 | +- Building new UI components or pages |
| 26 | +- Modifying existing user-facing interfaces |
| 27 | +- Implementing responsive layouts |
| 28 | +- Adding interactivity or state management |
| 29 | +- Fixing visual or UX issues |
| 30 | +- Worker tasks tagged with `--domain frontend` |
| 31 | + |
| 32 | +**When NOT to use:** |
| 33 | +- Backend-only changes with no UI impact |
| 34 | +- CLI tool development (use flow-code-api-design instead) |
| 35 | +- Pure data model / schema changes |
| 36 | + |
| 37 | +## Phase 1: Audit Before Coding |
| 38 | + |
| 39 | +Before writing any UI code: |
| 40 | + |
| 41 | +1. **Detect existing design system:** |
| 42 | + - Search for Tailwind config, CSS variables, theme files, component library imports |
| 43 | + - Check for design tokens (`colors`, `spacing`, `typography` in config) |
| 44 | + - Identify component library (shadcn, MUI, Ant Design, Radix, custom) |
| 45 | + |
| 46 | +2. **Read existing patterns:** |
| 47 | + - Find 2-3 similar components in the codebase — match their conventions |
| 48 | + - Check for shared layout components, wrappers, or utility classes |
| 49 | + - Note naming patterns (PascalCase components, kebab-case files, etc.) |
| 50 | + |
| 51 | +3. **Understand the state model:** |
| 52 | + - Where does data come from? (props, context, server state, URL params) |
| 53 | + - What state management is already in use? (don't introduce a new one) |
| 54 | + |
| 55 | +``` |
| 56 | +SKIP this phase ONLY if you're fixing a single CSS property. |
| 57 | +``` |
| 58 | + |
| 59 | +## Phase 2: Component Architecture |
| 60 | + |
| 61 | +### File Structure |
| 62 | + |
| 63 | +Colocate everything related to a component: |
| 64 | + |
| 65 | +``` |
| 66 | +src/components/ |
| 67 | + TaskList/ |
| 68 | + TaskList.tsx # Component implementation |
| 69 | + TaskList.test.tsx # Tests |
| 70 | + use-task-list.ts # Custom hook (if complex state) |
| 71 | + types.ts # Component-specific types (if needed) |
| 72 | +``` |
| 73 | + |
| 74 | +### Composition Over Configuration |
| 75 | + |
| 76 | +```tsx |
| 77 | +// Good: Composable |
| 78 | +<Card> |
| 79 | + <CardHeader> |
| 80 | + <CardTitle>Tasks</CardTitle> |
| 81 | + </CardHeader> |
| 82 | + <CardBody> |
| 83 | + <TaskList tasks={tasks} /> |
| 84 | + </CardBody> |
| 85 | +</Card> |
| 86 | + |
| 87 | +// Avoid: Over-configured |
| 88 | +<Card |
| 89 | + title="Tasks" |
| 90 | + headerVariant="large" |
| 91 | + bodyPadding="md" |
| 92 | + content={<TaskList tasks={tasks} />} |
| 93 | +/> |
| 94 | +``` |
| 95 | + |
| 96 | +### Separate Data from Presentation |
| 97 | + |
| 98 | +```tsx |
| 99 | +// Container: handles data |
| 100 | +function TaskListContainer() { |
| 101 | + const { tasks, isLoading, error } = useTasks(); |
| 102 | + if (isLoading) return <TaskListSkeleton />; |
| 103 | + if (error) return <ErrorState message="Failed to load tasks" retry={refetch} />; |
| 104 | + if (tasks.length === 0) return <EmptyState message="No tasks yet" />; |
| 105 | + return <TaskList tasks={tasks} />; |
| 106 | +} |
| 107 | + |
| 108 | +// Presentation: handles rendering |
| 109 | +function TaskList({ tasks }: { tasks: Task[] }) { |
| 110 | + return ( |
| 111 | + <ul role="list" className="divide-y"> |
| 112 | + {tasks.map(task => <TaskItem key={task.id} task={task} />)} |
| 113 | + </ul> |
| 114 | + ); |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +### State Management Pyramid |
| 119 | + |
| 120 | +Choose the simplest approach that works: |
| 121 | + |
| 122 | +``` |
| 123 | +useState --> Component-specific UI state |
| 124 | +Lifted state --> Shared between 2-3 sibling components |
| 125 | +Context --> Theme, auth, locale (read-heavy, write-rare) |
| 126 | +URL state --> Filters, pagination, shareable UI state |
| 127 | +Server state (SWR) --> Remote data with caching |
| 128 | +Global store --> Complex client state shared app-wide |
| 129 | +``` |
| 130 | + |
| 131 | +Avoid prop drilling deeper than 3 levels. If passing props through components that don't use them, restructure or use context. |
| 132 | + |
| 133 | +## Phase 3: Design System Adherence |
| 134 | + |
| 135 | +### Defeat the AI Aesthetic |
| 136 | + |
| 137 | +AI-generated UI has recognizable patterns. Avoid all of them: |
| 138 | + |
| 139 | +| AI Default | Why It's a Problem | Production Quality | |
| 140 | +|---|---|---| |
| 141 | +| Purple/indigo everything | Every AI app looks identical | Use the project's actual color palette | |
| 142 | +| Excessive gradients | Visual noise, clashes with design systems | Flat or subtle gradients matching the system | |
| 143 | +| Rounded everything (`rounded-2xl`) | Ignores corner radius hierarchy | Consistent border-radius from design tokens | |
| 144 | +| Generic hero sections | Template-driven, no connection to content | Content-first layouts | |
| 145 | +| Lorem ipsum copy | Hides layout problems (overflow, wrapping) | Realistic placeholder content | |
| 146 | +| Oversized padding everywhere | Destroys visual hierarchy, wastes space | Consistent spacing scale | |
| 147 | +| Stock card grids | Ignores information priority | Purpose-driven layouts | |
| 148 | +| Shadow-heavy design | Competes with content, slow on low-end | Subtle or no shadows per design system | |
| 149 | + |
| 150 | +### Spacing |
| 151 | + |
| 152 | +Use the project's spacing scale. Don't invent values: |
| 153 | + |
| 154 | +```css |
| 155 | +/* Good: on-scale */ |
| 156 | +padding: 1rem; /* 16px */ |
| 157 | +gap: 0.75rem; /* 12px */ |
| 158 | + |
| 159 | +/* Bad: arbitrary */ |
| 160 | +padding: 13px; |
| 161 | +margin-top: 2.3rem; |
| 162 | +``` |
| 163 | + |
| 164 | +### Typography |
| 165 | + |
| 166 | +Respect the type hierarchy: |
| 167 | + |
| 168 | +``` |
| 169 | +h1 --> Page title (one per page) |
| 170 | +h2 --> Section title |
| 171 | +h3 --> Subsection title |
| 172 | +body --> Default text |
| 173 | +small --> Secondary/helper text |
| 174 | +``` |
| 175 | + |
| 176 | +Don't skip heading levels. Don't use heading styles for non-heading content. |
| 177 | + |
| 178 | +### Color |
| 179 | + |
| 180 | +- Use semantic tokens: `text-primary`, `bg-surface`, `border-default` -- not raw hex |
| 181 | +- Ensure contrast: 4.5:1 for normal text, 3:1 for large text |
| 182 | +- Never rely on color alone to convey information (add icons, text, or patterns) |
| 183 | + |
| 184 | +## Phase 4: Accessibility (WCAG 2.1 AA) |
| 185 | + |
| 186 | +Every component must meet these standards: |
| 187 | + |
| 188 | +### Keyboard Navigation |
| 189 | + |
| 190 | +```tsx |
| 191 | +// Focusable by default |
| 192 | +<button onClick={handleClick}>Click me</button> // OK |
| 193 | + |
| 194 | +// NOT focusable -- never do this |
| 195 | +<div onClick={handleClick}>Click me</div> // BAD |
| 196 | + |
| 197 | +// If you must use a div, add role + tabIndex + keyboard handler |
| 198 | +<div role="button" tabIndex={0} onClick={handleClick} |
| 199 | + onKeyDown={e => e.key === 'Enter' && handleClick()}> |
| 200 | + Click me |
| 201 | +</div> |
| 202 | +``` |
| 203 | + |
| 204 | +### ARIA Labels |
| 205 | + |
| 206 | +```tsx |
| 207 | +// Icon-only buttons need labels |
| 208 | +<button aria-label="Close dialog"><XIcon /></button> |
| 209 | + |
| 210 | +// Form inputs need labels |
| 211 | +<label htmlFor="email">Email</label> |
| 212 | +<input id="email" type="email" /> |
| 213 | +``` |
| 214 | + |
| 215 | +### Focus Management |
| 216 | + |
| 217 | +```tsx |
| 218 | +// Move focus when content changes (modals, dialogs) |
| 219 | +function Dialog({ isOpen, onClose }) { |
| 220 | + const closeRef = useRef(null); |
| 221 | + useEffect(() => { |
| 222 | + if (isOpen) closeRef.current?.focus(); |
| 223 | + }, [isOpen]); |
| 224 | + |
| 225 | + return ( |
| 226 | + <dialog open={isOpen}> |
| 227 | + <button ref={closeRef} onClick={onClose}>Close</button> |
| 228 | + {/* content */} |
| 229 | + </dialog> |
| 230 | + ); |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +### Required States |
| 235 | + |
| 236 | +Every data-driven component needs all three: |
| 237 | + |
| 238 | +```tsx |
| 239 | +if (isLoading) return <Skeleton aria-busy="true" />; |
| 240 | +if (error) return <ErrorState message={error.message} retry={refetch} />; |
| 241 | +if (!data.length) return <EmptyState action={onCreate} />; |
| 242 | +return <DataList items={data} />; |
| 243 | +``` |
| 244 | + |
| 245 | +## Phase 5: Responsive Design |
| 246 | + |
| 247 | +Design for mobile first, then expand: |
| 248 | + |
| 249 | +```tsx |
| 250 | +<div className=" |
| 251 | + grid grid-cols-1 |
| 252 | + sm:grid-cols-2 |
| 253 | + lg:grid-cols-3 |
| 254 | + gap-4 |
| 255 | +"> |
| 256 | +``` |
| 257 | + |
| 258 | +Test at these breakpoints: **320px, 768px, 1024px, 1440px**. |
| 259 | + |
| 260 | +### Loading and Transitions |
| 261 | + |
| 262 | +```tsx |
| 263 | +// Skeleton loading (not spinners for content areas) |
| 264 | +function ListSkeleton() { |
| 265 | + return ( |
| 266 | + <div className="space-y-3" aria-busy="true" aria-label="Loading"> |
| 267 | + {Array.from({ length: 3 }).map((_, i) => ( |
| 268 | + <div key={i} className="h-12 bg-muted animate-pulse rounded" /> |
| 269 | + ))} |
| 270 | + </div> |
| 271 | + ); |
| 272 | +} |
| 273 | +``` |
| 274 | + |
| 275 | +Use optimistic updates for perceived speed on user actions (toggles, deletes). |
| 276 | + |
| 277 | +## Common Rationalizations |
| 278 | + |
| 279 | +| Rationalization | Reality | |
| 280 | +|---|---| |
| 281 | +| "Accessibility is a nice-to-have" | Legal requirement in many jurisdictions. Engineering quality standard. | |
| 282 | +| "We'll make it responsive later" | Retrofitting responsive design is 3x harder than building it first. | |
| 283 | +| "The design isn't final so I'll skip styling" | Use design system defaults. Unstyled UI creates broken first impressions. | |
| 284 | +| "This is just a prototype" | Prototypes become production code. Build the foundation right. | |
| 285 | +| "The AI aesthetic is fine for now" | It signals low quality to every reviewer. Use the project's design system. | |
| 286 | +| "I'll add empty/error states later" | Users hit edge cases immediately. Handle them now or ship bugs. | |
| 287 | +| "Keyboard navigation can wait" | ~15% of users rely on keyboard. Ship accessible or ship broken. | |
| 288 | + |
| 289 | +## Red Flags |
| 290 | + |
| 291 | +- Components over 200 lines (split them) |
| 292 | +- Inline styles or arbitrary pixel values not on the spacing scale |
| 293 | +- Missing error, loading, or empty states |
| 294 | +- No keyboard navigation testing |
| 295 | +- Color as the sole indicator of state (red/green without text/icons) |
| 296 | +- Generic AI look: purple gradients, oversized cards, uniform shadows |
| 297 | +- New state management library when one already exists in the project |
| 298 | +- `div` with `onClick` instead of `button` or `a` |
| 299 | +- Skipped heading levels (h1 then h3) |
| 300 | +- Raw hex colors instead of semantic tokens |
| 301 | + |
| 302 | +## Verification |
| 303 | + |
| 304 | +After building UI: |
| 305 | + |
| 306 | +- [ ] Component renders without console errors or warnings |
| 307 | +- [ ] All interactive elements are keyboard accessible (Tab through the page) |
| 308 | +- [ ] Screen reader can convey page content and structure (`aria-label`, roles) |
| 309 | +- [ ] Responsive: works at 320px, 768px, 1024px, 1440px |
| 310 | +- [ ] Loading, error, and empty states all handled |
| 311 | +- [ ] Follows project's design system (spacing, colors, typography, border-radius) |
| 312 | +- [ ] No accessibility warnings from axe-core or browser dev tools |
| 313 | +- [ ] No AI aesthetic patterns (check the table in Phase 3) |
| 314 | +- [ ] Components under 200 lines each |
| 315 | +- [ ] State management matches existing project patterns |
0 commit comments