Why Component Structure Matters for LLMs
Composable primitives lead to better LLM-generated code
LLMs are non-deterministic, so design consistency relies on solid foundations. You can’t expect an agent to generate a component that matches your codebase without primitives to build on.
Ask an agent to create a dialog. It generates something that looks good and works. But as requirements grow like needing different sizes, headers, sticky elements, centered lists the model piles on props instead of rethinking the foundation.
Here’s some pseudo-code to illustrate:
<Dialog
open={true}
size="small"
hasHeader={true}
hasStickyElements={false}
centerList={true}
className="my-custom-class"
headerClassName="my-custom-header-class"
bodyClassName="my-custom-body-class"
// it continues...
>
As requirements increase the model slops on more properties and you get a Frankenstein component with a bad API that’s hard to maintain, and you end up spending more time adjusting it than building features.
Composable structures like shadcn/ui solve this. Instead of one component with every possible prop, each piece is a primitive with a focused API that you assemble as needed:
<Dialog open={true}>
<DialogTrigger>Open Dialog</DialogTrigger>
<DialogContent className="my-custom-class">
<DialogHeader className="my-custom-header-class">
My Header
</DialogHeader>
<DialogBody centerList className="my-custom-body-class">
My Body
</DialogBody>
<DialogFooter sticky>
My Footer
</DialogFooter>
</DialogContent>
</Dialog>
This is another example of how measuring lines of code is a poor metric for quality. The first example is longer, but the second is more maintainable and flexible.
Need a sticky footer? Add sticky to <DialogFooter>. No header? Omit <DialogHeader>. The component doesn’t need to know about every variant, it just needs to compose well and look good in those different compositions. This is what needs to be fine-tuned by a human. It’s important not to just focus on the component itself, but rather on its composition, and seeing what can be abstracted into a primitive and what should be left.
It’s also better for other developers and agents. The model can then make its own primitive instead of chaining another prop, and you can be the one to decide whether it stays or not. You build the foundations and guardrails, then guide agents to build using those primitives.