Implementing Frontend
Implementing Frontend
Implement real, working frontend functionality inside existing project conventions. Code must render AND behave correctly: integrate with actual data, preserve unrelated functionality, handle all relevant states, and stay maintainable. Never fabricate file contents, APIs, or behavior — inspect first.
Before writing any code, answer these from actual inspection (not assumption):
Framework/version: [e.g., React 18, Vue 3, Svelte 5 — check package.json]
Styling system: [e.g., Tailwind config, CSS modules, styled-components]
State management: [e.g., Redux, Zustand, Context, framework built-ins]
Existing components: [search for similar components to reuse/extend]
API client pattern: [e.g., fetch wrapper, React Query, Apollo — check existing calls]
Design spec available: [yes -> follow it exactly / no -> preserve existing conventions]
Test setup: [test runner, existing test patterns for similar components]
If any of these can't be determined from the repo, inspect further before assuming. Never guess at a component's props or an API's shape — read the source or the contract.
Step 1: Classify the task size
- Small/clear change (style tweak, bug fix, prop addition) → inspect relevant files, implement, targeted verification. Skip the full checklist.
- New component/feature → full workflow below.
- Cross-component or breaking interface change → full workflow + coordinate with architecture/backend/testing as needed.
Step 2: Full workflow checklist (for non-trivial work)
Progress:
- [ ] Inspect framework, conventions, and relevant existing code/components/tests
- [ ] Identify design spec or existing conventions to follow (no freelance redesign)
- [ ] Identify API contract / data shape if integration is involved
- [ ] Decide component boundaries and state ownership
- [ ] Implement behavior: all interactions, not just visual shell
- [ ] Handle loading, empty, error, success, disabled, validation states
- [ ] Apply semantic HTML + accessibility (labels, keyboard, focus, ARIA only if needed)
- [ ] Check for downstream consumers if changing shared interfaces
- [ ] Verify: tests, types, lint, build, and manual/browser interaction check
- [ ] Report exactly what changed, what was tested, what passed/failed, what's unverified
Step 3: When assumptions break
If implementation reveals the original plan was wrong (API doesn't match expectation, component behaves differently than assumed), stop. Don't layer speculative fixes. Re-investigate, then adjust the plan.
Reuse vs. new component
- Search for an existing component/pattern that does 80%+ of what's needed. Extend or compose it.
- Only create a new abstraction when reuse would force unrelated concerns together or break single responsibility.
- Don't fragment into trivial sub-components with no independent reuse or cohesion value.
State ownership
- A piece of state lives in the lowest common owner of everything that needs it.
- If a value can be derived (filtered list, total count, "is valid") — derive it, don't store it.
- Before adding a new state variable, check whether it duplicates something already available (props, context, query cache, route params).
Design fidelity
- If a design spec exists: implement it as specified, using existing design tokens/components.
- If no spec exists: match existing product conventions (spacing, components, interaction patterns already in the codebase). Do not introduce a new visual language.
- If ambiguous and it materially changes implementation (e.g., unclear error-state behavior), state a limited, explicit assumption or ask — don't silently invent a design.
- Never swap in generic UI-library demo layouts "to get something working."
New dependencies/frameworks
- Default: no new library, state manager, or styling system.
- Justify explicitly if proposing one: what existing tooling can't do, and why the gap matters for this task.
Real vs. fake functionality
- A button must do the real thing (submit, navigate, mutate state/data) unless the task explicitly asks for a stub, and any stub must be clearly labeled as such (e.g.,
// TODO: wire to POST /orders once endpoint exists). - Never present mock data or simulated persistence as done integration.
Accessibility baseline (apply by default, not optionally)
- Use native elements (
button,a,label,input,dialog) before custom ARIA widgets. - Every interactive control: keyboard operable, visible focus, accessible name.
- Forms: associated labels, inline validation messages tied via
aria-describedbyor framework equivalent. - Custom widgets (menus, modals, comboboxes): correct roles, focus trapping/restoration, Escape/Arrow key behavior matching the relevant ARIA pattern.
- State explicitly: "implemented keyboard/focus/label behavior for X" — not "fully accessible" without an audit.
Performance
- Don't memoize, virtualize, lazy-load, or cache speculatively.
- Apply these techniques only when there's an identified workload reason (large list, expensive computation, measured re-render cost, large bundle).
- For a vague performance complaint with no evidence, first identify what's actually slow (profiler, network tab, render count) before changing code. Escalate to a performance specialist for systemic/unclear cases rather than guessing.
Example 1 — Small styling change
Input: "The save button should be full-width on mobile."
Output: Locate the button's existing component and responsive breakpoint conventions (e.g., existing sm: usage or media query pattern). Apply the minimal class/style change using the project's existing responsive system. Do not touch unrelated buttons, do not introduce a new breakpoint system, do not refactor the component. Verify at the relevant viewport width.
Example 2 — New interactive component
Input: "Add a dropdown filter for order status."
Output: Check for an existing Select/Dropdown/Menu component before building one. Use it if present, following its API. Own the selected-status state at the parent that needs to filter the list (don't duplicate it in the dropdown and the list). Implement keyboard navigation (arrow keys, Enter, Escape) if building a custom listbox, or rely on native <select> semantics if a custom widget isn't justified. Handle the "no results for this filter" empty state. Verify: selecting an option actually filters rendered data, keyboard-only use works, screen-reader label present.
Example 3 — API-dependent feature Input: "Show the user's recent orders on the dashboard." Output: Find the actual API client pattern and endpoint/contract (don't invent the response shape — read the API spec, types, or existing similar calls). Implement loading skeleton/spinner, empty state ("no orders yet"), error state (retry affordance), and success state (rendered list). Guard against race conditions if the user can trigger repeated fetches (e.g., abort stale requests, ignore out-of-order responses). Verify with the real or mocked-per-contract API across all four states.
Example 4 — Performance complaint
Input: "The product list feels slow."
Output: Do not immediately wrap everything in React.memo/useMemo. First check: render count, list size, what's actually re-rendering, whether it's network-bound or render-bound. If it's a large unvirtualized list, virtualization is justified — apply it. If it's unclear or systemic, flag for the performance specialist with the evidence gathered rather than guessing further.
Example 5 — Shared component change
Input: "Add a new required prop to the shared Modal component to change close behavior."
Output: Search the codebase for all Modal usages first. Determine whether the prop can be optional with a backward-compatible default instead of required. If a breaking change is unavoidable, update all call sites, check/update existing tests for Modal and its consumers, and report which consumers were touched and verified.
- Read before writing: open the actual component/API/test files involved, don't infer from naming alone.
- Keep diffs scoped to the request; resist the urge to "clean up" unrelated code in the same file.
- Prefer composition over configuration flags that branch component behavior heavily.
- Name state and handlers for what they represent, not implementation details (
isSubmitting, notflag1). - When integrating with an API, check error shape as carefully as success shape.
- Write or update tests alongside behavior changes when the project has an existing test pattern for similar code.
- State verification honestly: "ran unit tests (pass), did not test in Safari" is more useful than silence.
- Rewriting a whole file/component for a one-line fix.
- Adding Redux/Zustand/a new CSS framework because it's familiar, not because it's needed.
- Building a visually complete component where clicking/submitting does nothing.
- Storing the same data in two state variables that can drift out of sync.
- Using
<div onClick>where<button>would give free semantics and keyboard support for free. - Shipping a form with no error/validation/loading state because "the happy path works."
- Changing a shared component's prop or exported interface without grepping for consumers.
- Reaching for
useMemo/React.memo/virtualization before measuring anything. - Declaring "fully tested" or "accessible" without having actually run tests or checked keyboard/screen-reader behavior.
- Redesigning spacing/colors/layout because it "looks better," when no design change was requested.