← Agent SkillsCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to Agent Skills
Snapshot Sep 30, 2026 · 23:17 UTC · version 0.6.10
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"name": "frontend-ui-engineering",
"description": "Use when building or modifying interfaces and pages, creating components, implementing layouts, meeting WCAG accessibility requirements, managing state, or when the output needs to look and feel production-quality rather than AI-generated.",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 272
}
],
"skill_md_contents": "---\nname: frontend-ui-engineering\ndescription: Use when building or modifying interfaces and pages, creating components, implementing layouts, meeting WCAG\n accessibility requirements, managing state, or when the output needs to look and feel production-quality rather than AI-generated.\n---\n\n# Frontend UI Engineering\n\n## Overview\n\nBuild 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 at a top company — not like it was generated by an AI. This means real design system adherence, proper accessibility, thoughtful interaction patterns, and no generic \"AI aesthetic.\"\n\n## When to Use\n\n- Building new UI components or pages\n- Modifying existing user-facing interfaces\n- Implementing responsive layouts\n- Adding interactivity or state management\n- Fixing visual or UX issues\n\n## Component Architecture\n\n### File Structure\n\nColocate everything related to a component:\n\n```\nsrc/components/\n TaskList/\n TaskList.tsx # Component implementation\n TaskList.test.tsx # Tests\n TaskList.stories.tsx # Storybook stories (if using)\n use-task-list.ts # Custom hook (if complex state)\n types.ts # Component-specific types (if needed)\n```\n\n### Component Patterns\n\n**Prefer composition over configuration:**\n\n```tsx\n// Good: Composable\n<Card>\n <CardHeader>\n <CardTitle>Tasks</CardTitle>\n </CardHeader>\n <CardBody>\n <TaskList tasks={tasks} />\n </CardBody>\n</Card>\n\n// Avoid: Over-configured\n<Card\n title=\"Tasks\"\n headerVariant=\"large\"\n bodyPadding=\"md\"\n content={<TaskList tasks={tasks} />}\n/>\n```\n\n**Keep components focused:**\n\n```tsx\n// Good: Does one thing\nexport function TaskItem({ task, onToggle, onDelete }: TaskItemProps) {\n return (\n <li className=\"flex items-center gap-3 p-3\">\n <Checkbox checked={task.done} onChange={() => onToggle(task.id)} />\n <span className={task.done ? 'line-through text-muted' : ''}>{task.title}</span>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onDelete(task.id)}>\n <TrashIcon />\n </Button>\n </li>\n );\n}\n```\n\n**Separate data fetching from presentation:**\n\n```tsx\n// Container: handles data\nexport function TaskListContainer() {\n const { tasks, isLoading, error } = useTasks();\n\n if (isLoading) return <TaskListSkeleton />;\n if (error) return <ErrorState message=\"Failed to load tasks\" retry={refetch} />;\n if (tasks.length === 0) return <EmptyState message=\"No tasks yet\" />;\n\n return <TaskList tasks={tasks} />;\n}\n\n// Presentation: handles rendering\nexport function TaskList({ tasks }: { tasks: Task[] }) {\n return (\n <ul role=\"list\" className=\"divide-y\">\n {tasks.map(task => <TaskItem key={task.id} task={task} />)}\n </ul>\n );\n}\n```\n\n## State Management\n\n**Choose the simplest approach that works:**\n\n```\nLocal state (useState) → Component-specific UI state\nLifted state → Shared between 2-3 sibling components\nContext → Theme, auth, locale (read-heavy, write-rare)\nURL state (searchParams) → Filters, pagination, shareable UI state\nServer state (React Query, SWR) → Remote data with caching\nGlobal store (Zustand, Redux) → Complex client state shared app-wide\n```\n\n**Avoid prop drilling deeper than 3 levels.** If you're passing props through components that don't use them, introduce context or restructure the component tree.\n\n## Design System Adherence\n\n### Avoid the AI Aesthetic\n\nAI-generated UI has recognizable patterns. Avoid all of them:\n\n| AI Default | Why It Is a Problem | Production Quality |\n|---|---|---|\n| Purple/indigo everything | Models default to visually \"safe\" palettes, making every app look identical | Use the project's actual color palette |\n| Excessive gradients | Gradients add visual noise and clash with most design systems | Flat or subtle gradients matching the design system |\n| Rounded everything (rounded-2xl) | Maximum rounding signals \"friendly\" but ignores the hierarchy of corner radii in real designs | Consistent border-radius from the design system |\n| Generic hero sections | Template-driven layout with no connection to the actual content or user need | Content-first layouts |\n| Lorem ipsum-style copy | Placeholder text hides layout problems that real content reveals (length, wrapping, overflow) | Realistic placeholder content |\n| Oversized padding everywhere | Equal generous padding destroys visual hierarchy and wastes screen space | Consistent spacing scale |\n| Stock card grids | Uniform grids are a layout shortcut that ignores information priority and scanning patterns | Purpose-driven layouts |\n| Shadow-heavy design | Layered shadows add depth that competes with content and slows rendering on low-end devices | Subtle or no shadows unless the design system specifies |\n\n### Spacing and Layout\n\nUse a consistent spacing scale. Don't invent values:\n\n```css\n/* Use the scale: 0.25rem increments (or whatever the project uses) */\n/* Good */ padding: 1rem; /* 16px */\n/* Good */ gap: 0.75rem; /* 12px */\n/* Bad */ padding: 13px; /* Not on any scale */\n/* Bad */ margin-top: 2.3rem; /* Not on any scale */\n```\n\n### Typography\n\nRespect the type hierarchy:\n\n```\nh1 → Page title (one per page)\nh2 → Section title\nh3 → Subsection title\nbody → Default text\nsmall → Secondary/helper text\n```\n\nDon't skip heading levels. Don't use heading styles for non-heading content.\n\n### Color\n\n- Use semantic color tokens: `text-primary`, `bg-surface`, `border-default` — not raw hex values\n- Ensure sufficient contrast (4.5:1 for normal text, 3:1 for large text)\n- Don't rely solely on color to convey information (use icons, text, or patterns too)\n\n## Accessibility (WCAG 2.1 AA)\n\nEvery component must meet these standards:\n\n### Keyboard Navigation\n\n```tsx\n// Every interactive element must be keyboard accessible\n<button onClick={handleClick}>Click me</button> // ✓ Focusable by default\n<div onClick={handleClick}>Click me</div> // ✗ Not focusable\n<div role=\"button\" tabIndex={0} onClick={handleClick} // ✓ But prefer <button>\n onKeyDown={e => {\n if (e.key === 'Enter') handleClick();\n if (e.key === ' ') e.preventDefault();\n }}\n onKeyUp={e => {\n if (e.key === ' ') handleClick();\n }}>\n Click me\n</div>\n```\n\n### ARIA Labels\n\n```tsx\n// Label interactive elements that lack visible text\n<button aria-label=\"Close dialog\"><XIcon /></button>\n\n// Label form inputs\n<label htmlFor=\"email\">Email</label>\n<input id=\"email\" type=\"email\" />\n\n// Or use aria-label when no visible label exists\n<input aria-label=\"Search tasks\" type=\"search\" />\n```\n\n### Focus Management\n\n```tsx\n// Move focus when content changes\nfunction Dialog({ isOpen, onClose }: DialogProps) {\n const closeRef = useRef<HTMLButtonElement>(null);\n\n useEffect(() => {\n if (isOpen) closeRef.current?.focus();\n }, [isOpen]);\n\n // Trap focus inside dialog when open\n return (\n <dialog open={isOpen}>\n <button ref={closeRef} onClick={onClose}>Close</button>\n {/* dialog content */}\n </dialog>\n );\n}\n```\n\n### Meaningful Empty and Error States\n\n```tsx\n// Don't show blank screens\nfunction TaskList({ tasks }: { tasks: Task[] }) {\n if (tasks.length === 0) {\n return (\n <div role=\"status\" className=\"text-center py-12\">\n <TasksEmptyIcon className=\"mx-auto h-12 w-12 text-muted\" />\n <h3 className=\"mt-2 text-sm font-medium\">No tasks</h3>\n <p className=\"mt-1 text-sm text-muted\">Get started by creating a new task.</p>\n <Button className=\"mt-4\" onClick={onCreateTask}>Create Task</Button>\n </div>\n );\n }\n\n return <ul role=\"list\">...</ul>;\n}\n```\n\n## Responsive Design\n\nDesign for mobile first, then expand:\n\n```tsx\n// Tailwind: mobile-first responsive\n<div className=\"\n grid grid-cols-1 /* Mobile: single column */\n sm:grid-cols-2 /* Small: 2 columns */\n lg:grid-cols-3 /* Large: 3 columns */\n gap-4\n\">\n```\n\nTest at these breakpoints: 320px, 768px, 1024px, 1440px.\n\n## Loading and Transitions\n\n```tsx\n// Skeleton loading (not spinners for content)\nfunction TaskListSkeleton() {\n return (\n <div className=\"space-y-3\" aria-busy=\"true\" aria-label=\"Loading tasks\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"h-12 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n );\n}\n\n// Optimistic updates for perceived speed\nfunction useToggleTask() {\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: toggleTask,\n onMutate: async (taskId) => {\n await queryClient.cancelQueries({ queryKey: ['tasks'] });\n const previous = queryClient.getQueryData(['tasks']);\n\n queryClient.setQueryData(['tasks'], (old: Task[]) =>\n old.map(t => t.id === taskId ? { ...t, done: !t.done } : t)\n );\n\n return { previous };\n },\n onError: (_err, _taskId, context) => {\n queryClient.setQueryData(['tasks'], context?.previous);\n },\n });\n}\n```\n\n## See Also\n\nFor detailed accessibility requirements and testing tools, see `../../references/accessibility-checklist.md`.\n\n## Common Rationalizations\n\n| Rationalization | Reality |\n|---|---|\n| \"Accessibility is a nice-to-have\" | It's a legal requirement in many jurisdictions and an engineering quality standard. |\n| \"We'll make it responsive later\" | Retrofitting responsive design is 3x harder than building it from the start. |\n| \"The design isn't final, so I'll skip styling\" | Use the design system defaults. Unstyled UI creates a broken first impression for reviewers. |\n| \"This is just a prototype\" | Prototypes become production code. Build the foundation right. |\n| \"The AI aesthetic is fine for now\" | It signals low quality. Use the project's actual design system from the start. |\n\n## Red Flags\n\n- Components with more than 200 lines (split them)\n- Inline styles or arbitrary pixel values\n- Missing error states, loading states, or empty states\n- No keyboard navigation testing\n- Color as the sole indicator of state (red/green without text or icons)\n- Generic \"AI look\" (purple gradients, oversized cards, stock layouts)\n\n## Verification\n\nAfter building UI:\n\n- [ ] Component renders without console errors\n- [ ] All interactive elements are keyboard accessible (Tab through the page)\n- [ ] Screen reader can convey the page's content and structure\n- [ ] Responsive: works at 320px, 768px, 1024px, 1440px\n- [ ] Loading, error, and empty states all handled\n- [ ] Follows the project's design system (spacing, colors, typography)\n- [ ] No accessibility warnings in dev tools or axe-core\n"
}SHA-256: 8658740468971bb22f8675831e5460a5be67a0f92c0a66a12b7f41430d6ac2de