Skip to content

Managing Prop Relationships with Conditional and Template Literal Types

Building a design system or smart UI components requires precision when mapping relationships between props.

2 min read
Conditional Types
Template Literal Types
TypeScript
React Props
Design Tokens

Building a design system or smart UI components requires precision when mapping relationships between props. Problems usually crop up when components have logic dependencies—like when an href prop should turn a component into a link, or when asChild shifts a ref to a child element. If you only use basic interfaces, these relationships break, letting developers pass invalid prop combinations that eventually trigger runtime errors.

Conditional types and template literal types are the way to encode these "rules of the game" directly into your component API without making the code a mess.

When implementing them, use conditional types wisely. If a simpler discriminated union solves the problem, go with that. Conditional types are really only necessary when you're mapping over complex sets of keys using keyof. Also, don't over-engineer a complex type if it’s only used once; a simple union is usually enough.

The infer keyword is a lifesaver when wrapping third-party libraries, especially when you need to grab function types like onChange without manually rewriting generics. However, if you actually own both components, just export the original types instead—it's more efficient.

For design tokens or patterned CSS classes, template literal types are the right tool. They ensure a prop only accepts values with specific prefixes. But don't try to shove an entire ecosystem like Tailwind into template literals, or you'll tank your compile times. The best strategy is to create types for your public semantic tokens and just leave the rest as string.

Keep in mind that advanced typing shouldn't necessarily bleed into your application screens. If a type is only used in one place, stick to a basic interface. Conditional types are really meant for shared packages or component libraries that get reused constantly.

Watch out for "false confidence" in your types, too. Telling a developer that href is required when the component actually renders a <button> at runtime is actually worse than just using any.

Finally, don't force every single rule into the type system. Dynamic product rules—like marketing character limits or color contrast requirements—are better handled via runtime validation or a CMS. Use specific types to protect the stable parts of your API.

Happy coding!