My Second Article
Thoughts on building design systems with TypeScript
Building a Design System with TypeScript
Design systems are the backbone of scalable frontend development. When paired with TypeScript, they become even more powerful — giving you autocomplete, type safety, and self-documenting APIs across every component.
Why TypeScript in a Design System?
A design system without types is a design system that relies on documentation nobody reads. TypeScript turns your component API into the documentation.
interface ButtonProps {
variant: 'primary' | 'secondary' | 'ghost';
size: 'sm' | 'md' | 'lg';
disabled?: boolean;
onClick?: () => void;
}
With this interface, consumers can't accidentally pass variant="danger" if it doesn't exist — the compiler catches it first.
Tokens as Constants
Design tokens — colors, spacing, typography — should live as typed constants rather than raw strings scattered through stylesheets.
export const colors = {
amber: '#C9A84C',
void: '#080808',
inkMuted: 'rgba(240, 237, 230, 0.55)',
} as const;
export type Color = keyof typeof colors;
The as const assertion narrows the type to exact string literals, so colors.amber is typed as '#C9A84C', not just string.
Component Variants with CVA
Class Variance Authority (CVA) is a lightweight library that pairs well with this approach. It lets you define component variants declaratively:
const button = cva('btn-base', {
variants: {
variant: {
primary: 'btn-primary',
ghost: 'btn-ghost',
},
size: {
sm: 'btn-sm',
md: 'btn-md',
},
},
});
The returned function is fully typed — the variant keys are inferred directly from the config.
Closing Thoughts
A well-typed design system pays dividends every time a new developer joins your team or you revisit code six months later. The upfront investment in types and tokens is small compared to the long-term reduction in bugs and inconsistency.