・If you define “context-dependent styles” such as margins, padding, and heading sizes within a shared component, the resulting design may not align with the user's intentions. Responsibility for layout should be delegated to the component caller as much as possible.
Bad example
export const BadCard: React.FC<{ className?: string }> = ({ className, children }) => (
<section
className={[
"w-full rounded-md bg-white p-6 shadow-md",
"mt-8", // Apply style that should be applied from caller
"[&_h3]:mt-6",
"[&_footer>button+button]:ml-3",
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</section>
);
・If margins or the styles of internal elements are fixed, unexpected layout issues may occur in different layout contexts, such as when “cards are arranged side by side” or “the gap property is used on the parent element.”
Good example
・Components should be limited to minimal visual elements such as background color, borders, and rounded corners. To allow the caller to determine the external layout, components should be structured using layout systems such as grid or flex.
type CardProps = React.HTMLAttributes<HTMLElement> & {
tone?: "neutral" | "elevated";
};
export const Card: React.FC<CardProps> = ({ className, tone = "neutral", ...rest }) => {
const base = "rounded-md bg-white";
const elevation = tone === "elevated" ? "shadow-md" : "shadow-none";
return <section className={[base, elevation, className].filter(Boolean).join(" ")} {...rest} />;
};
// Caller: Paddin and margin is caller's responsibility
<div className="grid grid-cols-2 gap-4">
<Card tone="elevated" className="p-6 space-y-3">
<h3 className="text-lg font-bold">Title</h3>
<p>Context</p>
<footer className="flex gap-2">
<Button>Cancel</Button>
<Button variant="primary">Save</Button>
</footer>
</Card>
<Card className="p-6">Abitrary padding should be aoolyied by caller</Card>
</div>
Top comments (0)