Jacco Meijer
Orange oil

Typescript interface for React UI components

How to define an interface for React UI components that prevents breaking changes.

Improve over time

Setting up a library with React UI components is simply done. The hard part is maintaining the library. Over time breaking changes are require and the broader the library is used, the more impact breaking changes will have.

The Typescript React interface described here was designed to prevent breaking changes. So that components remain flexible and can improve over time.

Interface and style factory

The interface has a separate style factory so that component styling easily can be extracted.


export type StyleObject = CSSObject | CSSObject[] export type SubComponents = Record<string, (props: any) => JSX.Element | null> export type ComponentProps< Model = any, Variant extends string = string, Option extends string = string > = { [K in keyof AnyProps]: { /** component data, all models fields must be optional */ model?: Model /** multiple options can be set, all options are optional */ options?: Partial<Record<Option, boolean>> /** override css for specific options */ optionsCss?: Partial<Record<Option, StyleObject>> /** override subcomponents */ subComponents?: SubComponents /** only one variant can be set, a default variant exists */ variant?: Variant /** override css for specific variants */ variantCss?: Partial<Record<Variant, StyleObject>> } & AnyProps[K] }Copied

Style factory

The style factory allows styling to be extracted from a component. For e. g. typography, the styling is defined as a component and can also be used separately.

export type StyleFactory< Variant extends string = string, Option extends string = string > = (args: { options?: Partial<Record<Option, boolean>> optionsCss?: Partial<Record<Option, StyleObject>> variant?: Variant variantCss?: Partial<Record<Variant, StyleObject>> }) => anyCopied

Example component

How to use the interface and the style factory is shown in this example. The full code is available upon request.

export type Example = { content?: string } export type ExampleVariant = 'horizontal' | 'vertical' export type ExampleOption = 'roundedCorners' | 'strongBorder' export type ExampleProps = ComponentProps< Example, ExampleVariant, ExampleOption >['div'] const baseStyle: StyleObject = { ... } const horizontalVariantStyle: StyleObject = { ... } const verticalVariantStyle: StyleObject = { ... } const roundedCornersOptionStyle: StyleObject = { ... } const strongBorderOptionStyle: StyleObject = { ... } export const emptyStyleFactory: StyleFactory<ExampleVariant, ExampleOption> = ( args ) => { const useVariant = args.variant || 'vertical' ... // Combine styles here based on variant and options return { ... } } export const Example = ({ anyCss, model, options, optionsCss, variant, variantCss, ...props }: ExampleProps) => { const cssFromFactory = emptyStyleFactory({ options, optionsCss, variant, variantCss, }) return ( <Div anyCss={[cssFromFactory, anyCss]} {...props}> {model?.content || 'No content'} </Div> ) }Copied

Data, variants and options

The component interface separates model data, variants and options and does not allow other properties. It is always clear which properties a component supports and how they are used.

Model data

Model data is always passed by the property 'model'.

All model fields are optional. As can be learned from versionless API's defined in Graphql, optional fields help in preventing breaking changes.


Variants define how component data is displayed. A default variant is used when no variant is specified. Variants are exclusive, only one variant can be selected.


Options are booleans that define changes in display behaviour. Generally each option works across all variants. Contrary to the variant property, multiple options can be selected.


Adding a 'variant' attribute to the interface reduces the number of models needed. The post below explains why.

Content modeling with variants

The efficiency of a variant field in a content model.

NPM7 and @npmcli/arborist

@npmcli/arborist is a powerful library that handles the new npm 7 workspaces. This blog is about a simple make tool that uses the library.

Comparing React app, Nextjs and Gatsby

A new React project starts with a React toolchain. Main tools in the chains are SSR, React server components and GraphQL.

Versioning strategy for npm modules

It is important to be able to bump the version of a npm package without side effects.

React component themes and CSS variables

Creating React components with flexible themes by using CSS variables.

Content modeling with variants

The efficiency of a variant field in a content model.

Green oil


Documenting a software project is challenging. Here's a few simple guidelines that help a team writing clear documentation.

Orange yellow oil

On Javascript transpilers, bundlers and modules

There's Javascript transpilers, modules, bundles and bundlers. This is a brief overview of all of these.

Dark orange bubbles

Javascript history

In 1986 David Ungar and Randall B. Smith developed Self at Xerox PARC. Inspired by Java, Scheme and Self Brendan Eich created Javascript in 1995.

Blue waves

Agile Scrum

The Agile Scrum framework is flexible enough to be used in many different ways. Here's one way of working.

Blue water bubbles

Contentful, Netlify and Gatsby four years later

What did we learn from using Contentful for four years?

Wheelroom hero image

What happened to Wheelroom?

Founded in 2018. Started to fly in 2020 and abandoned in 2021. What happened?

Orange oil

Typescript interface for React UI components

How to define an interface for React UI components that prevents breaking changes.

Orange green oil

Naming React components

What's in a name? A clear naming strategy helps developers communicate. Most devs rather spend time writing component code than wasting time on a good component name.