areas/software/frontend/skills/component-design/SKILL.md
Design reusable React components with compound patterns, controlled/uncontrolled hybrids, typed prop APIs, async state handling, and ARIA accessibility. Use when the user creates, refactors, or reviews React components, or mentions props, hooks, .tsx files, component APIs, or accessible UI patterns.
npx skillsauth add sawrus/agent-guides component-designInstall 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.
Expertise: Compound components, controlled/uncontrolled, render props, component API design, accessibility requirements.
When creating, refactoring, or reviewing React components — especially when choosing between compound, controlled/uncontrolled, or headless patterns, designing typed prop APIs, or implementing accessible interactive widgets.
--strictMultiple visual zones in one component? → Slot / Children Props
Coordinated subcomponents sharing state? → Compound Components
Works with react-hook-form / external control? → Controlled/Uncontrolled Hybrid
Self-contained widget with internal state? → Uncontrolled with defaults
Highly customizable rendering? → Render Props / Headless
Use when: a component has multiple coordinated parts sharing implicit state.
// Context shared between sub-components
const MenuContext = createContext<{ open: boolean; toggle: () => void } | null>(null);
const Menu = ({ children }: { children: React.ReactNode }) => {
const [open, setOpen] = useState(false);
return (
<MenuContext.Provider value={{ open, toggle: () => setOpen(o => !o) }}>
<div role="menu" aria-expanded={open}>{children}</div>
</MenuContext.Provider>
);
};
Menu.Trigger = function MenuTrigger({ children }: { children: React.ReactNode }) {
const ctx = useContext(MenuContext)!;
return (
<button onClick={ctx.toggle} aria-haspopup="true" aria-expanded={ctx.open}>
{children}
</button>
);
};
Menu.Items = function MenuItems({ children }: { children: React.ReactNode }) {
const { open } = useContext(MenuContext)!;
if (!open) return null;
return <ul role="listbox">{children}</ul>;
};
// Usage — caller controls structure
<Menu>
<Menu.Trigger>Options</Menu.Trigger>
<Menu.Items>
<li role="option">Edit</li>
<li role="option">Delete</li>
</Menu.Items>
</Menu>
Use when: component works standalone OR integrates with external form libraries.
interface InputProps {
value?: string; // controlled mode if provided
defaultValue?: string; // uncontrolled mode
onChange?: (value: string) => void;
label: string;
error?: string;
}
const Input = ({ value, onChange, defaultValue, label, error }: InputProps) => {
const [internal, setInternal] = useState(defaultValue ?? '');
const isControlled = value !== undefined;
const current = isControlled ? value : internal;
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (!isControlled) setInternal(e.target.value);
onChange?.(e.target.value);
};
const id = useId(); // stable ID for label association
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} value={current} onChange={handleChange}
aria-invalid={!!error} aria-describedby={error ? `${id}-error` : undefined} />
{error && <span id={`${id}-error`} role="alert">{error}</span>}
</div>
);
};
Every component that fetches or receives async data must handle all states:
interface DataComponentProps {
userId: string;
}
const UserCard = ({ userId }: DataComponentProps) => {
const { data, isLoading, isError, error } = useUser(userId);
// 1. Loading state — skeleton or spinner
if (isLoading) return <UserCardSkeleton />;
// 2. Error state — meaningful message, not blank
if (isError) return (
<div role="alert">
<p>Failed to load user data.</p>
<button onClick={() => refetch()}>Try again</button>
</div>
);
// 3. Empty state — explicit, not silent blank area
if (!data) return <p>No user found.</p>;
// 4. Success state — the happy path
return <div>{data.name}</div>;
};
// ✅ Good: explicit, typed, small surface area
interface ButtonProps {
variant: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
onClick?: () => void;
children: React.ReactNode;
'aria-label'?: string; // Allow a11y override
}
// ❌ Bad: too many booleans (boolean explosion)
interface ButtonProps {
isPrimary?: boolean;
isSecondary?: boolean;
isSmall?: boolean;
isLarge?: boolean;
// Can set isPrimary + isSecondary simultaneously — ambiguous
}
// ❌ Bad: style overrides passed as strings
interface ButtonProps {
className?: string; // Breaks component encapsulation
style?: CSSProperties; // Creates leaky styling contract
}
| Component | Required ARIA | Keyboard | Notes |
|---|---|---|---|
| Dialog/Modal | role="dialog", aria-modal, aria-labelledby | Trap focus; Escape closes | Focus returns to trigger on close |
| Dropdown/Select | role="listbox", aria-expanded | Arrow keys navigate; Enter selects | Announce selection |
| Toggle/Switch | role="switch", aria-checked | Space toggles | |
| Alert/Toast | role="alert" or aria-live="polite" | — | Screen reader announces immediately |
| Tab panel | role="tablist", role="tab", role="tabpanel" | Arrow keys between tabs | |
| Form field | <label> with htmlFor | — | Never skip label |
testing
QA Expert for writing E2E tests, test scenarios, test plans, and ensuring test coverage quality.
development
Expert UI/UX design intelligence for creating distinctive, high-craft, and mobile-first interfaces. Focuses on premium aesthetics, touch-first ergonomics, and Flutter performance.
development
Code Review Expert for static analysis, security auditing, architecture review, and ensuring code quality standards.
development
Babysit a GitHub pull request after creation by continuously polling review comments, CI checks/workflow runs, and mergeability state until the PR is merged/closed or user help is required. Diagnose failures, retry likely flaky failures up to 3 times, auto-fix/push branch-related issues when appropriate, and keep watching open PRs so fresh review feedback is surfaced promptly. Use when the user asks Codex to monitor a PR, watch CI, handle review comments, or keep an eye on failures and feedback on an open PR.