frontend-page
tadoEng/web-starter/.claude/skills/frontend-page/SKILL.md
Add or change a page, screen, form, table or component in this product's React frontend, with the five async states and the accessibility floor. Use when asked to build a page, add a route, create a form, show a list or table, or change the UI.
Skill2 starsChanged 50 days ago
--- name: frontend-page description: > Add or change a page, screen, form, table or component in this product's React frontend, with the five async states and the accessibility floor. Use when asked to build a page, add a route, create a form, show a list or table, or change the UI. --- # Frontend page or component Read `AGENTS.md` first. The frontend owns presentation and client state. It does not own business rules, authorization, or data shapes. ## Steps 1. **Route** in `frontend/src/routes/`, registered with React Router. 2. **Data** via a TanStack Query hook in `frontend/src/api/`, built on the **generated** client. If the endpoint doesn't exist yet, use the `api-endpoint` skill first — do not work around a missing endpoint from the client side. 3. **UI** from shadcn/ui primitives in `frontend/src/components/ui/`, composed in `frontend/src/components/`. Prefer an unmodified primitive over a bespoke one. 4. **Forms**: React Hook Form + the **generated** Zod schema from `src/generated/api-client/zod.gen.ts`. Zod is generated precisely so the shape is never written twice — never hand-write one that mirrors the request type. For presentation-only constraints (confirm-password, a client-side max), `.extend()` or `.refine()` the generated schema rather than copying its fields. 5. **Tests**: Vitest + React Testing Library. Query by role and label. 6. `./scripts/verify.sh`, then verify in the browser and attach a screenshot. ## The five states — all of them, every async surface | State | Requirement | |---|---| | Loading | Skeleton sized like the real content, so layout doesn't jump | | Empty | Says what would appear here and how to create the first one | | Error | The message from the error contract, plus retry where retry helps | | Success | The content; for mutations, a confirmation the user can notice | | Disabled | Buttons disable while a mutation is in flight — no double submits | A screen that renders only the happy path is not finished. Optimistic updates need a rollback path or they are a bug. ## Forms - Field errors map from **422 responses**, not just client validation. A form that silently fails a server rejection is worse than a crash. - Every input has a real `<label>`. A placeholder is not a label. ## Accessibility floor Cheap to get right, expensive to retrofit: - Every interactive element keyboard-reachable and operable, in sensible order. - Visible focus ring — never `outline: none` without a replacement. - `aria-label` on icon-only buttons. - Contrast 4.5:1 body, 3:1 large text and UI borders. - Dialogs trap focus, close on Escape, return focus to the trigger. - Never convey state by colour alone. Testing by role and label keeps most of this honest automatically. ## Responsive — a hard gate, not a finish Every screen must work at **375px** and at desktop width. - The **page body never scrolls horizontally.** Wide content — tables, charts, code, long unbroken strings — scrolls inside its own `overflow-x: auto` container. - Decide desktop → tablet → mobile per surface. Navigation, tables, forms, toolbars, dialogs and charts each need an actual decision, not whatever flexbox does by default. - A six-column table becomes **compact record rows** on mobile, or scrolls in its own container. Both are legitimate; a horizontally crushed table is not. - Use relative units and `max-width: 100%` on media. Test with a long title and a null optional field, not just the comfortable middle. `frontend/e2e/smoke.spec.ts` enforces the no-overflow rule at 375px mechanically. **Do not weaken that test to make a layout pass** — that inverts the gate into a rubber stamp. Ship visual work with a desktop screenshot **and** a 375px screenshot. For layout-led work, use the `frontend-craft` skill instead of this one. ## Stop Hiding a control because of a role is **UX only** — the backend must reject the action independently and have a test proving it. If you find yourself writing a business rule in a component, it belongs on the backend.
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

