Three Files Per Button: Prim's Component Architecture Explained
Prim UI Team
Prim UI · Feb 8, 2026 · 9 min read
Every component in Prim has three files. This sounds like overengineering until you understand the problem it solves: the editor's live preview and the exported production code have fundamentally different requirements, and conflating them creates a mess.
The Definition file is pure data — a TypeScript object that declares the component's variants, overrides, animation slots, and metadata. No React, no CSS. It's the contract that both the preview and code generation layers consume. When you add a new variant to a button, you add it here and both layers pick it up.
The Preview file renders the component in Prim's editor. It reads CSS custom properties from the Zustand store and updates in real-time as you adjust tokens. This layer prioritizes interactivity: hover states respond to your animation settings, colors update as you drag the picker, border radius changes are instant. It uses runtime CSS variables because that's what makes live editing possible.
The Code Generation layer produces the component you'll actually ship. It resolves CSS variables to concrete Tailwind classes or vanilla CSS values, inlines animation configurations, and generates self-contained React components. This layer prioritizes portability: the output should work in any React project with zero dependencies on Prim's runtime.
The key insight is that these layers evolve independently. You can add a new preview interaction (like a hover effect preview) without changing what gets exported. You can add a new export format (say, Vue components) without touching the editor. And the definition layer ensures both sides stay synchronized on what variants and overrides exist.
Same source of truth, two different consumption patterns. The preview layer reads from the store reactively. The code generation layer reads at export time, resolving everything to static values.