From be03883d80e84a15b4cf7e156157c10bef363e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Mon, 6 Apr 2026 18:19:25 +0400 Subject: [PATCH] Add rule for UI component props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- contrib/claude/ui.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/contrib/claude/ui.md b/contrib/claude/ui.md index 315f4bb15..849833432 100644 --- a/contrib/claude/ui.md +++ b/contrib/claude/ui.md @@ -16,6 +16,32 @@ For data loading and GraphQL on the console, see [`contrib/claude/relay.md`](rel Preview components with Storybook from `packages/ui`: `npm run dev` (Storybook on port 6006 per `package.json`). +## Props typing + +When a component renders a native HTML element as its top-level node (not a custom component), **merge the component's own props with that element's intrinsic props** via `ComponentProps`. Destructure custom props and spread the rest onto the element so callers can pass standard HTML attributes (`id`, `className`, `aria-*`, event handlers, etc.) without wrapper boilerplate. + +### Do / don't: props merging + +```tsx +// Good — own props merged with the native element's props, rest spread onto +type MyProps = ComponentProps<"span"> & { myPropName: string }; + +export function MyComponent(props: MyProps) { + const { myPropName, ...spanProps } = props; + + return {myPropName}; +} +``` + +```tsx +// Bad — only custom props accepted; callers cannot set id, className, aria-*, etc. +type MyProps = { myPropName: string }; + +export function MyComponent(props: MyProps) { + return {props.myPropName}; +} +``` + ## `tailwind-variants` and `className` In a **single component file**, do **not** mix arbitrary Tailwind utility strings on `className` with `tailwind-variants` for the same styling concerns. Put layout and look in **`tv` variants and `slots`** (and the APIs `tv` exposes for overrides). If consumers need extensibility, expose it through variant props or documented slot/class hooks—not by sprinkling raw utilities beside `tv()` output in the same file.