skills/typescript-react-standards/SKILL.md
Opinionated TypeScript and React development standards from Avant Media. Use when scaffolding new components, reviewing code, writing TypeScript interfaces or types, setting up project structure, creating React hooks, or working on any TypeScript/React codebase. Also use when the user asks about best practices, patterns, or conventions for TypeScript or React projects, even if they don't explicitly mention "standards."
npx skillsauth add avantmedialtd/skills typescript-react-standardsInstall 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.
Opinionated standards for building TypeScript and React applications. These are the patterns we use at Avant Media across client projects and internal products. They prioritize readability, maintainability, and developer experience over cleverness.
Every project uses strict: true in tsconfig.json. No exceptions. This catches entire categories of bugs at compile time. If the types are hard to write, the code is probably too complex.
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
Use interface for anything that describes an object shape. Use type for unions, intersections, mapped types, and utility types. The distinction matters because interfaces are extensible and produce better error messages.
// Object shapes → interface
interface User {
id: string;
email: string;
role: UserRole;
}
// Unions, utilities → type
type UserRole = 'admin' | 'editor' | 'viewer';
type PartialUser = Partial<User>;
// Good
interface CreateUserRequest { ... }
interface UserListResponse { ... }
// Bad
interface IUserData { ... } // Hungarian notation
interface UserPayload { ... } // vague
No I prefix on interfaces. No T prefix on types. No E prefix on enums. These are Java/C# conventions that add noise without information.
as const objects insteadTypeScript enums have well-documented quirks (numeric enums reverse-map, const enum has build tool issues). Use as const objects — they're type-safe, tree-shakeable, and have no runtime surprises.
// Prefer this
const UserRole = {
Admin: 'admin',
Editor: 'editor',
Viewer: 'viewer',
} as const;
type UserRole = (typeof UserRole)[keyof typeof UserRole];
// Over this
enum UserRole {
Admin = 'admin',
Editor = 'editor',
Viewer = 'viewer',
}
Internal helpers can rely on inference. Anything exported gets an explicit return type. This catches accidental API changes and makes the contract clear.
// Exported → explicit return type
export function formatCurrency(amount: number, currency: string): string {
return new Intl.NumberFormat('en-GB', { style: 'currency', currency }).format(amount);
}
// Internal → inference is fine
const double = (n: number) => n * 2;
any — use unknown and narrowany disables the type system. unknown forces you to narrow before use. If you genuinely don't know the type, use unknown and add a type guard.
function processInput(input: unknown): string {
if (typeof input === 'string') return input;
if (typeof input === 'number') return String(input);
throw new Error(`Unexpected input type: ${typeof input}`);
}
If you're wrapping a third-party library with bad types, isolate the any in a thin adapter layer and type the boundary.
index.ts re-exports are fine for public API surfaces (a component library, a shared package). Don't use them inside application code — they create circular dependency traps and make tree-shaking harder.
// Fine: package public API
src/components/index.ts → re-exports Button, Input, Modal
// Avoid: deep application barrel files
src/features/auth/index.ts → re-exports everything in auth
No class components. No React.FC (it implicitly includes children in older versions and adds no value). Just typed props and a function.
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary';
onClick: () => void;
}
export function Button({ label, variant = 'primary', onClick }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} onClick={onClick}>
{label}
</button>
);
}
One component per file. The file name matches the component name exactly (PascalCase). Co-locate the component's types, hooks, and styles.
Button/
├── Button.tsx # Component
├── Button.test.tsx # Tests
├── Button.module.css # Styles (if CSS modules)
├── useButton.ts # Component-specific hook (if needed)
└── index.ts # Re-export (optional)
Custom hooks extract reusable logic. Name them use<Thing>. They must call at least one React hook internally — otherwise it's just a function, not a hook.
// This is a hook — it uses React state
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue((v) => !v), []);
return [value, toggle] as const;
}
// This is NOT a hook — just call it formatDate()
function useDateFormat(date: Date) {
return date.toLocaleDateString('en-GB');
}
Use the simplest tool that works:
useState) — component-scoped, default choiceDon't reach for global state managers for problems that useState and prop drilling solve cleanly. "Prop drilling" is only a problem at 4+ levels — and even then, composition (passing components as children) often fixes it better than context.
Use TanStack Query (React Query) for server state. It handles caching, deduplication, background refetching, and error/loading states. Don't reinvent this with useEffect + useState.
function useUsers() {
return useQuery({
queryKey: ['users'],
queryFn: () => api.get<User[]>('/users'),
staleTime: 5 * 60 * 1000, // 5 minutes
});
}
For mutations, use useMutation with optimistic updates where the UX demands it.
Wrap major UI sections in error boundaries. A broken sidebar shouldn't crash the whole page. Use react-error-boundary — it's maintained, well-typed, and supports reset.
Don't create a <GenericTable> component before you have three concrete tables. Don't write a useForm hook before you have three forms. Let the pattern emerge from real usage, then extract.
The rule of three: duplicate first, abstract second.
For application projects (not libraries), organize by feature, not by type:
src/
├── app/ # Route definitions, layouts
├── features/ # Feature modules
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── api/
│ │ └── types.ts
│ └── dashboard/
│ ├── components/
│ ├── hooks/
│ ├── api/
│ └── types.ts
├── shared/ # Cross-cutting concerns
│ ├── components/ # Generic UI components
│ ├── hooks/ # Generic hooks
│ ├── utils/ # Pure utility functions
│ └── types/ # Shared type definitions
├── lib/ # Third-party adapters, API client setup
└── config/ # Environment, feature flags
The features/ directory is the heart of the application. Each feature is self-contained. If deleting a feature folder breaks imports outside that folder, you have a coupling problem.
See references/testing-standards.md for the full testing approach. The short version:
Test behavior, not implementation. If refactoring the internals breaks your tests but not the user experience, your tests are wrong.
When reviewing TypeScript/React code, check for:
any escapesReact.FC, no class componentsdevelopment
Manage Bitbucket Cloud pull requests, comments, tasks, and pipelines from the command line. Use when working with PRs, reviewing code, leaving inline comments, creating PR tasks, triggering or inspecting Bitbucket Pipelines, or looking up reviewer account IDs.
tools
Assign a Jira issue to yourself and convert it into an OpenSpec proposal. Use when the user says "start work", "pick up an issue", "take the next ticket", or provides a Jira key and wants to begin work on it. Handles issue selection from the backlog, assignment, transition to In Progress, and scaffolding an OpenSpec change with Jira context embedded in the proposal.
development
Bun/Elysia/React/MUI monorepo stack blueprint. Use when scaffolding a new project, adding a new app or package to the monorepo, choosing dependencies, or making architectural decisions. Contains the full tech stack, conventions, and wiring patterns.
tools
Manage Jira issues from the command line. Use when working with Jira issues, creating tasks, updating status, assigning work, linking issues, managing versions, setting or reading custom fields (Story Points, Sprint, Severity, etc.), or searching for issues.