```
Test at these breakpoints: 320px, 768px, 1024px, 1440px.
## Loading and Transitions
```tsx
// Skeleton loading (not spinners for content)
function TaskListSkeleton() {
return (
{Array.from({ length: 3 }).map((_, i) => (
))}
);
}
// Optimistic updates for perceived speed
function useToggleTask() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: toggleTask,
onMutate: async (taskId) => {
await queryClient.cancelQueries({ queryKey: ['tasks'] });
const previous = queryClient.getQueryData(['tasks']);
queryClient.setQueryData(['tasks'], (old: Task[]) =>
old.map(t => t.id === taskId ? { ...t, done: !t.done } : t)
);
return { previous };
},
onError: (_err, _taskId, context) => {
queryClient.setQueryData(['tasks'], context?.previous);
},
});
}
```
## Building UI Before the Backend Exists
When the feature follows an API contract (`../../references/parallel-frontend-backend.md`), the UI does not wait for the backend:
1. Write the runtime schema (for example `zod`) for each response from the contract.
2. Add a unit test that the schema parses every fixture example the screen uses.
3. Serve fixture examples in end-to-end tests through the shared fake-route helper. Never call the browser test API's route interception directly for the feature's paths.
4. Mark criteria that need the real backend with "Verify end to end in
". The UI task passes when they work against fixtures.
5. Remove a fake route when the helper reports that the backend now has it.
Treat a response that fails the schema as a load error with a retry action, so a contract drift shows up as a clear error instead of a broken screen.
## See Also
For detailed accessibility requirements and testing tools, see `../../references/accessibility-checklist.md`.
For comments, UI copy punctuation and the completion check, see `../../references/house-rules.md`. UI copy follows the same punctuation rules as code: no em dash or en dash.
## Common Rationalizations
| Rationalization | Reality |
|---|---|
| "Accessibility is a nice-to-have" | It's a legal requirement in many jurisdictions and an engineering quality standard. |
| "We'll make it responsive later" | Retrofitting responsive design is 3x harder than building it from the start. |
| "The design isn't final, so I'll skip styling" | Use the design system defaults. Unstyled UI creates a broken first impression for reviewers. |
| "This is just a prototype" | Prototypes become production code. Build the foundation right. |
| "The AI aesthetic is fine for now" | It signals low quality. Use the project's actual design system from the start. |
## Red Flags
- Components with more than 200 lines (split them)
- Inline styles or arbitrary pixel values
- Missing error states, loading states, or empty states
- No keyboard navigation testing
- Color as the sole indicator of state (red/green without text or icons)
- Generic "AI look" (purple gradients, oversized cards, stock layouts)
## Verification
After building UI:
- [ ] Component renders without console errors
- [ ] All interactive elements are keyboard accessible (Tab through the page)
- [ ] Screen reader can convey the page's content and structure
- [ ] Responsive: works at 320px, 768px, 1024px, 1440px
- [ ] Loading, empty, error, success, and permission states handled when applicable
- [ ] Follows the project's design system (spacing, colors, typography)
- [ ] The rendered result passes a final UI-specific finish-gate review
- [ ] No accessibility warnings in dev tools or axe-core
- [ ] Runtime schemas parse every contract fixture the screen uses, when the feature has an API contract
- [ ] End-to-end tests install fake routes only through the shared helper