.github/skills/tsh-reviewing-frontend/SKILL.md
Frontend-specific code review criteria, component anti-patterns, hooks quality, rendering correctness, accessibility and performance spot-checks, and module organization issues. Use when reviewing frontend pull requests, auditing component quality, or identifying UI-specific code smells beyond general code review.
npx skillsauth add thesoftwarehouse/copilot-collections tsh-reviewing-frontendInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Provides frontend-specific review criteria for evaluating component quality, hooks correctness, rendering behavior, accessibility compliance, and performance — to be used alongside the general tsh-code-reviewing skill.
Use the checklist below and track your progress:
Review progress:
- [ ] Step 1: Review component structure
- [ ] Step 2: Review hooks/composables quality
- [ ] Step 3: Review rendering correctness
- [ ] Step 4: Spot-check accessibility
- [ ] Step 5: Spot-check performance
- [ ] Step 6: Produce findings report
Step 1: Review component structure
Check each component for:
FetchAndDisplayUsers), multiple unrelated state variables.isLoading, isDisabled, isExpanded, isSelected) — consider a status enum instead.UserProfileCard is clear; DataDisplay is not. Avoid generic names like Wrapper, Container, Handler unless the component's sole purpose is layout containment.Signs a component needs splitting:
| Signal | Threshold | | ----------------- | ---------------------------------------------------- | | File length | > 300 lines | | Props count | > 7 props | | State variables | > 5 state declarations | | Effects | > 3 side effect hooks/watchers | | Nested conditions | > 2 levels of ternary/conditional rendering | | Mixed concerns | Fetching + transforming + rendering in one component |
Step 2: Review hooks/composables quality
Throughout this section, "hook" refers to any reusable logic unit — React hooks, Vue composables, or equivalent abstractions. Adapt naming conventions and dependency tracking checks to the project's framework.
For every custom hook/composable in the changeset:
use prefix in React/Vue) and describe behavior? useDebounce is good; useHelper is vague. The name should answer "what does calling this hook/composable give me?" without reading the implementation.setTimeout / setInterval without clearTimeout / clearInterval in cleanupaddEventListener without removeEventListenerAbortController not aborted on unmountuseToggle → [value, toggle]) but objects are preferred for 3+ values to avoid positional confusion.localStorage, calling fetch, or dispatching events during render are all violations.After completing the generic checks above, load the framework-specific reference (see Framework-Specific Patterns section) and apply its hooks review checklist for additional framework-specific checks.
Step 3: Review rendering correctness
Check for:
style={{ margin: 8 }} — lift to a constant or use a styling solutionoptions={[{ value: 'a' }, { value: 'b' }]} — lift to module scope or memoizeonChange={(e) => setValue(e.target.value)} — stabilize the reference if child is memoizedif (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!data.length) return <EmptyState />;
return <DataList data={data} />;
Step 4: Spot-check accessibility
Quick checks — not a full audit. Defer to tsh-ensuring-accessibility for comprehensive coverage.
<button> for actions, <a> for navigation links? Or <div onClick> / <span onClick> anti-pattern? Non-semantic interactive elements break keyboard and screen reader access — automatic critical. The <div> with an onClick has no keyboard support, no role, no focus indicator by default.<label> elements associated via for/id pairing? Placeholder text as the only label is a warning — the label disappears on input. aria-label is acceptable for icon-only buttons but not as a substitute for visible labels on text inputs.<h1> to <h3> with no <h2>)? Skipped levels break document outline for assistive technology. Each page should have exactly one <h1>.tabIndex values greater than 0 — they disrupt natural tab order and should almost never be used.tabIndex="-1".aria-hidden="true" on interactive elements removes them from the accessibility tree. role="button" on a <div> requires tabIndex="0" and onKeyDown handler — prefer <button> instead.Step 5: Spot-check performance
Quick checks — not a full audit. Defer to tsh-optimizing-frontend for deep analysis.
import _ from 'lodash') where named imports (import { debounce } from 'lodash/debounce') would suffice.index.ts) pull unused exports into the bundle? Wildcard re-exports (export *) break tree shaking. Named re-exports (export { Button } from './Button') are the safe pattern.value={{ user, setUser }} creates a new object every render, re-rendering all consumers.Step 6: Produce findings report
Group findings by severity. Each finding must include: file + line range, issue description, and recommended fix.
Critical — Must fix before merge:
Warning — Should fix, may defer with justification:
Suggestion — Consider for improvement:
Format each finding consistently:
[SEVERITY] file/path.tsx#L10-L25
Issue: <description>
Fix: <recommended action>
Example findings:
[CRITICAL] components/Modal.tsx#L45-L52
Issue: <div onClick={onClose}> used as close button — no keyboard access, no role, no focus indicator.
Fix: Replace with <button onClick={onClose} aria-label="Close modal">.
[WARNING] hooks/useUserData.ts#L18
Issue: Dependency tracking lint rule suppressed — potential stale closure.
Fix: Restructure effect to include all dependencies, or extract a stable callback.
[SUGGESTION] components/OrderList.tsx#L33
Issue: Inline style object `style={{ padding: 16 }}` passed to memoized child — defeats memo.
Fix: Lift to module-level constant or use styling solution.
Frontend Review:
- [ ] Components follow single responsibility
- [ ] Props are typed, minimal, with defaults
- [ ] Named exports (no default exports)
- [ ] Loading, error, and empty states handled
- [ ] Custom hooks/composables: proper naming, SRP, complete deps, cleanup (+ framework-specific checklist from references)
- [ ] Keys are stable and unique (not array indices)
- [ ] No inline object/array props without memoization
- [ ] No derived state in reactive state (compute instead)
- [ ] Interactive elements use semantic HTML
- [ ] Form fields have visible labels
- [ ] Focus managed on modal/dialog open/close
- [ ] No color-only state indicators
- [ ] ARIA attributes used correctly
- [ ] New routes/heavy components lazy-loaded
- [ ] No wildcard imports or bloated barrel files
- [ ] Conditional rendering is readable (no deep ternary nesting)
- [ ] Large lists (100+) virtualized
| Anti-Pattern | Severity | Why |
| --------------------------------------------------- | ---------- | ---------------------------------------- |
| <div onClick> instead of <button> | Critical | Breaks keyboard + screen reader access |
| Missing key or index-as-key on dynamic list | Critical | Causes rendering bugs, state mix-ups |
| Effect/watcher without cleanup (timers/listeners) | Critical | Memory leak |
| Side effect in render phase | Critical | Unpredictable behavior, infinite loops |
| Raw HTML insertion without sanitization | Critical | XSS vulnerability |
| Placeholder used as only label | Warning | Accessibility: label disappears on input |
| 300+ line component | Warning | Hard to maintain, likely violates SRP |
| Hook/composable with suppressed dependency tracking | Warning | Hidden stale closure or reactivity bug |
| Missing loading/error state | Warning | Broken UX for slow/failed requests |
| Derived state in reactive state | Warning | Synchronization bugs, stale data |
| Context/provider with unstable value | Warning | All consumers re-render every time |
| Inline object as prop to memo child | Suggestion | Causes unnecessary re-render |
| Deeply nested ternary (3+ levels) | Suggestion | Hard to read, extract component instead |
| Default export | Suggestion | Inconsistent imports, harder refactoring |
| Full-library import | Suggestion | Bundle bloat from unused code |
The review criteria above are framework-agnostic. For framework-specific anti-patterns and API checks, load the appropriate reference:
./references/react-patterns.md — React-specific hooks review, dangerouslySetInnerHTML, exhaustive-deps, memoization API checks.tsh-code-reviewing — the general review process; this skill provides the frontend-specific checkstsh-implementing-frontend — the patterns being reviewed againsttsh-implementing-forms — for form-specific patterns (validation schemas, field composition, multi-step flows) being reviewed againsttsh-ensuring-accessibility — for comprehensive accessibility audits beyond spot-checkstsh-optimizing-frontend — for deep performance analysis beyond spot-checkstsh-writing-hooks — for detailed hook quality patterns being reviewed againstdevelopment
Custom hook and composable patterns — naming, composition, stable return shapes, lifecycle cleanup, and testing strategies. Use when writing reusable logic units (React hooks, Vue composables), refactoring logic into hooks, debugging hook behavior, or reviewing hook implementations.
testing
UI verification criteria, structure checklists, severity definitions, and tolerance rules for comparing implementations against Figma designs. Use for verifying UI matches design, understanding what to check, and determining acceptable differences.
development
Clean raw workshop or meeting transcripts from small talk, filler words, and off-topic tangents. Extract and structure business-relevant content into a standardized format with discussion topics, key decisions, action items, and open questions.
development
Discover and establish technical context before implementing any feature. Prioritize project instructions, existing codebase patterns, and external documentation in that order. Use for any task requiring understanding of project conventions, coding standards, architecture patterns, and established practices before writing code.