component-conventions
Conventions for creating or editing components under src/components/** — file structure, props/types, composition, and when to reach for a Radix primitive vs a plain element.
SKILL.md.claude/skills/component-conventions/SKILL.md
GitHub- One component per file, named exports,
PascalCasefilename matching the component name (seesrc/components/Button.tsx,src/components/SubscribeDialog.tsx). - Props interface named
<Component>Props, extending the relevant HTML attributes interface when the component wraps a native element (React.ButtonHTMLAttributes<HTMLButtonElement>, etc.) so consumers keep native props likeonClick,aria-*,disabledfor free. - Style with Tailwind utility classes via the
cn()helper (src/lib/utils.ts,clsx+tailwind-merge) — never string-concatenate classes. - Variant-based components (buttons, badges, alerts) use
class-variance-authority(cva), not a manualif/elsechain of class strings — seeButton.tsxfor the pattern. - Interactive/composite components (dialogs, dropdowns, tooltips, popovers) are built on
Radix UI primitives (
@radix-ui/react-*), not raw DOM + manual ARIA — Radix owns focus management, keyboard interaction, and portal/positioning correctly by default.- Upgrade path (ADR-006): the template installs Radix because
@base-ui-components/reactis still a release candidate (1.0.0-rc.*) as of this template's generation date. Once it publishes a real1.0.0(check withnpm view @base-ui-components/react dist-tags), Base UI is the documented swap indocs/CUSTOMIZING.md— the component file structure below does not change, only the primitive import.
- Upgrade path (ADR-006): the template installs Radix because
- Every interactive element needs a visible focus style (
focus-visible:ring-*, seebuttonVariantsinButton.tsx) — neveroutline-nonewithout a replacement. - A component that wraps
asChild/Slot(seeButton.tsx) must forwardrefwithReact.forwardRef— Radix'sasChildpattern depends on ref forwarding to work.