ワンクリックで
distilling-components
Component design patterns - compound components, composition, props API
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Component design patterns - compound components, composition, props API
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Creative direction for AI image generation — distill a codebase's FEEL canon (taste tokens, TDRs, persona files) into prompt-ready material vocabulary, map product architecture to scene systems, and enforce visual discipline across banner sets and scene stacks.
Analyze feedback logs to detect design preference patterns. Auto-contributes HIGH confidence patterns upstream.
Motion design and animation patterns for UI based on Emil Kowalski's principles
Touch, keyboard, and form interaction patterns for accessible UI
Design physics system for UI interactions - sync strategies, timing, confirmations
Convert vague "feel" feedback into specific actionable fixes via decomposition questions
SOC 職業分類に基づく
| name | distilling-components |
| description | Component design patterns - compound components, composition, props API |
| user-invocable | true |
| allowed-tools | Read, Write, Glob, Grep, Edit |
Best practices for designing React components that are flexible, composable, and maintainable.
/distill
This skill provides guidance on component architecture and API design. Use it when:
Does the component have multiple related parts sharing state?
└── YES → Use compound components
Is the structure fixed with few variations?
└── YES → Use simple props
Do consumers need to control element rendering?
└── YES → Use asChild pattern
Does the component wrap a DOM element?
└── YES → Forward refs and spread props
Use when a component has multiple related parts sharing implicit state:
// Good - compound components
<Dialog>
<Dialog.Trigger>Open</Dialog.Trigger>
<Dialog.Content>
<Dialog.Title>Are you sure?</Dialog.Title>
<Dialog.Description>This action cannot be undone.</Dialog.Description>
<Dialog.Close>Cancel</Dialog.Close>
</Dialog.Content>
</Dialog>
// Bad - prop drilling everything
<Dialog
trigger="Open"
title="Are you sure?"
description="This action cannot be undone."
closeText="Cancel"
/>
When to use compound components:
When NOT to use:
For straightforward components with minimal variation:
<Button variant="primary" size="md">
Click me
</Button>
Find the balance between too rigid and too flexible:
// Too rigid - no customization
<Button>Click me</Button>
// Too flexible - overwhelming API
<Button
backgroundColor="#000"
hoverBackgroundColor="#333"
activeBackgroundColor="#111"
borderRadius={4}
paddingX={16}
paddingY={8}
fontSize={14}
fontWeight={500}
// ... 30 more props
>
Click me
</Button>
// Just right - variants + escape hatch
<Button variant="primary" size="md" className="custom-override">
Click me
</Button>
// Good - consistent patterns
<Input disabled />
<Button disabled />
<Select disabled />
// Bad - inconsistent
<Input disabled />
<Button isDisabled />
<Select readonly />
Use positive names, avoid double negatives:
// Good
<Input disabled />
<Modal open />
// Bad
<Input notEnabled />
<Modal isNotClosed />
Prefix with on, use past tense for after-the-fact:
// Good
<Input onChange={} onBlur={} />
<Dialog onOpenChange={} onClose={} />
// Bad
<Input handleChange={} blurHandler={} />
// Good - composable
<Card>
<CardHeader>
<CardTitle>Title</CardTitle>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>Content here</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
// Bad - configuration object
<Card
header={{ title: "Title", description: "Description" }}
content="Content here"
footer={{ actions: [{ label: "Action", onClick: () => {} }] }}
/>
For components with optional sections:
function Card({ children, header, footer }) {
return (
<div className="card">
{header && <div className="card-header">{header}</div>}
<div className="card-content">{children}</div>
{footer && <div className="card-footer">{footer}</div>}
</div>
);
}
// Usage
<Card
header={<h2>Title</h2>}
footer={<Button>Save</Button>}
>
Main content
</Card>
Prefer children for simple cases, render props for complex:
// Simple - use children
<Card>
<CardContent />
</Card>
// Complex with data - render prop
<List
items={users}
renderItem={(user) => <UserCard user={user} />}
/>
Allow rendering as a different element:
// Render as button (default)
<Button>Click me</Button>
// Render as link
<Button asChild>
<a href="/page">Click me</a>
</Button>
// Render as Next.js Link
<Button asChild>
<Link href="/page">Click me</Link>
</Button>
Implementation using Radix Slot:
import { Slot } from "@radix-ui/react-slot";
function Button({ asChild, ...props }) {
const Comp = asChild ? Slot : "button";
return <Comp {...props} />;
}
Always forward refs for components wrapping DOM elements:
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, ...props }, ref) => {
return (
<button ref={ref} {...props}>
{children}
</button>
);
}
);
Allow passing arbitrary HTML attributes:
function Button({ variant, size, className, ...props }) {
return (
<button
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}
// Now these work:
<Button data-testid="submit" aria-label="Submit form">
Submit
</Button>
Support both patterns:
function Input({
value: controlledValue,
defaultValue,
onChange,
...props
}) {
const [internalValue, setInternalValue] = useState(defaultValue ?? "");
const isControlled = controlledValue !== undefined;
const value = isControlled ? controlledValue : internalValue;
function handleChange(e) {
if (!isControlled) {
setInternalValue(e.target.value);
}
onChange?.(e);
}
return <input value={value} onChange={handleChange} {...props} />;
}
// Uncontrolled
<Input defaultValue="hello" />
// Controlled
<Input value={value} onChange={setValue} />
Use sensible defaults that work for 80% of cases:
function Button({
variant = "primary",
size = "md",
type = "button", // Not "submit" - safer default
...props
}) {
// ...
}
components/
├── button/
│ ├── button.tsx # Main component
│ ├── button.test.tsx # Tests
│ └── index.ts # Public exports
├── card/
│ ├── card.tsx
│ ├── card-header.tsx
│ ├── card-content.tsx
│ └── index.ts
// components/card/index.ts
export { Card } from "./card";
export { CardHeader } from "./card-header";
export { CardContent } from "./card-content";
// Or as compound component
export { Card, CardHeader, CardContent } from "./card";
// Bad - too many props
<Button
leftIcon={<Icon />}
rightIcon={<Arrow />}
iconSpacing={8}
iconSize={16}
>
Click
</Button>
// Good - use children/composition
<Button>
<Icon /> Click <Arrow />
</Button>
// Bad - boolean soup
<Button primary large rounded>Click</Button>
// Good - explicit variants
<Button variant="primary" size="lg" radius="full">Click</Button>
Don't create a component until you've copy-pasted it 2-3 times. Wait until patterns emerge.
PATTERN SELECTION:
├── Multiple parts + shared state → Compound components
├── Fixed structure, few props → Simple props
├── Need custom element → asChild pattern
└── Wraps DOM element → Forward refs
PROPS API:
├── Use variants for predefined options
├── Use className as escape hatch
├── Consistent naming (disabled, not isDisabled)
├── Boolean props = positive names
└── Event handlers = on + verb (onChange)
COMPOSITION:
├── Prefer children over config objects
├── Use slots for optional sections
├── Render props for complex data
└── Spread remaining props
ALWAYS:
├── Forward refs on DOM wrappers
├── Support controlled + uncontrolled
├── Sensible defaults (80% case)
└── Export from index.ts
API Design:
├── [ ] Props use consistent naming
├── [ ] Boolean props are positive
├── [ ] Event handlers prefixed with on
├── [ ] Sensible defaults provided
└── [ ] className escape hatch available
Flexibility:
├── [ ] asChild pattern if element varies
├── [ ] Refs forwarded for DOM wrappers
├── [ ] Remaining props spread
└── [ ] Both controlled/uncontrolled supported
Organization:
├── [ ] Clear file structure
├── [ ] Clean exports from index
├── [ ] Compound components properly composed
└── [ ] No premature abstraction