Style-prop API
motif gives you two ways to author styles — inline style props on a primitive, and a styled()
factory that bakes a configuration into a named component. Both are first-class; neither is the
one true path.
Status
Accepted.
Context
Styling libraries tend to pick a lane. Utility-prop libraries make every style an inline prop; styled-component libraries make every style a named component. Each lane is right for part of a real codebase and wrong for the rest — a one-off layout wants inline props, a button used in ninety places wants to be a component.
Forcing one model means the other use case is always slightly awkward.
Decision
motif ships both APIs, co-designed, with neither marked primary.
- Style props —
<Box p="$4" bg="$colors.surface.base" _hover={{ opacity: 0.9 }} />. Styles live at the call site. Right for layout and for anything used once. - The
styled()factory —styled('button', { base: { … }, variants: { … } }). A configuration baked into a named component, with a typedvariantsaxis. Right for anything reused.
Two supporting decisions fall out of this:
- Token references are
$-prefixed strings.$colors.surface.base,$space.4. The same syntax works in a prop and in astyled()config. The resolver, not a function call, turns the reference into a value. - Variants follow the Stitches model — a
variantsmap,compoundVariantsfor axis interactions, and boolean variants. The variant keys are type-checked against the map.
The two APIs are not separate systems. styled() is configured with the same style-prop schema
its output accepts, so a value learned in one place transfers to the other.
Consequences
- A codebase can mix freely — inline props for the layout,
styled()for the component library — without a context switch in mental model. - The style-prop schema is large, and it is the same schema in two places. That is deliberate: one schema to learn, one to maintain.
- Neither API can be deprecated without breaking half of every motif codebase. Committing to both is a permanent commitment.