web/src/components/design-system/README.md
This folder contains reusable, primitive, presentational UI components.
Do not include:
TracePanel)Design-system files may import other design-system files, shared utilities (src/utils, src/hooks), and third-party libraries. They must not import from the outer src/components tree or src/features.
design-system/
Button/
Button.tsx
Button.stories.tsx
Components with subcomponents must expose them through the single exported
component using Object.assign:
// ✅ Single component export with compound subcomponents
const Alert = Object.assign(AlertRoot, {
Title: AlertTitle,
Description: AlertDescription,
});
export { Alert };
<Alert>
<Alert.Title>Title</Alert.Title>
<Alert.Description>Description</Alert.Description>
</Alert>
// ❌ Separate component exports
<Alert>
<AlertTitle>Title</AlertTitle>
<AlertDescription>Description</AlertDescription>
</Alert>
No className or style props
No arbitrary values (e.g. #fff, 12px)
Use explicit enums:
size: "sm" | "md" | "lg";
variant: "primary" | "secondary";
Use cva for all variants
Use explicit enums (no free-form values)
Prop values must never match Tailwind class names:
size="md"size="w-5 h-5"Enforce mutually exclusive props at type level
Use positive naming:
suffix={null}noSuffixBoolean props must use is / should:
isLoading, shouldTruncateDesign-system components own visual states, not business logic.
Logic-heavy components must reuse design-system components for visuals instead of recreating UI internally.
// ✅ Button owns the loading visual state
type ButtonProps = {
isLoading: boolean;
};
const PromiseButton = (props: PromiseButtonProps) => {
const [isLoading, setIsLoading] = useState(false);
return <Button isLoading={isLoading} />;
};
// ❌ PromiseButton reimplements Button loading visuals
const PromiseButton = (props: PromiseButtonProps) => {
const [isLoading, setIsLoading] = useState(false);
if (isLoading) {
return (
<div>
<Spinner />
</div>
);
}
return <Button isLoading={isLoading} />;
};
className / stylecvasrc/components tree or from src/featuresis / shouldIf unsure: only include reusable, design-only primitives.