Clara Ekekenta Software Engineer and perpetual learner with a passion for OS and expertise in Python, JavaScript, Go, Rust, and Web 3.0.

How to build a React custom component library with Theme UI

5 min read 1548

React Logo

React is a JavaScript toolkit that facilitates the creation of beautiful user interfaces for both online and mobile apps. It easily interfaces with other JavaScript frameworks and libraries and has short, reusable components.

Because of their great modularity, React component libraries speed up UI development and also provide a lot of freedom. There are several popular React UI libraries; however, they may not offer the level of customization needed for all projects.

In this tutorial, we’ll review how to build and publish a custom component library in React with Theme UI using TypeScript.

What is Theme UI?

Theme UI is a library that uses constraint-based design concepts to create themeable user interfaces. With a broad API for best-in-class developer ergonomics, Theme UI can be used to create bespoke component libraries, design systems, web applications, Gatsby themes, and more.

Getting started

This hands-on tutorial has the following prerequisites:

  • Node.js installed
  • Ubuntu 20.04, or the OS of your choice
  • VS Code, or any IDE of your choice

Project setup

We’ll start by running the following command to create a folder for our project:

mkdir themecomponentui && cd themecomponentui

Next, we’ll initialize a new React project using the npm init command to create a package.json file. Then, we’ll install React and TypeScript with the following command:

npm i -D react @types/react typescript

The -D flag in the above command denotes that the modules should be installed as devDependencies because we’ll need them during our build process.

With React and TypeScript installed, let’s organize our project according to the below folder structure:

┣ 📂src
┃ ┣ 📂components
┃ ┃ ┣ 📂Button
┃ ┃ ┣ 📂Input

We made a custom demo for .
No really. Click here to check it out.

Now, let’s configure TypeScript for our project.

Configuring TypeScript

We’ll use TypeScript to build the components that will enable us to use the library as a module.

First, we’ll configure TypeScript by creating a tsconfig.json file using the following command:

npx tsc --init

Then, we’ll update the tsconfig.json file with the following snippet:

  "compilerOptions": {
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true,
    "jsx": "react",
    "module": "ESNext",
    "declaration": true,
    "declarationDir": "types",
    "sourceMap": true,
    "outDir": "dist",
    "moduleResolution": "node",
    "emitDeclarationOnly": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
  "exclude": [

In the above code, we added the following configurations:

  • "jsx": "react": Transforms JSX code into React code
  • "skipLibCheck": true: Skips type checking the declaration files
  • "module": "ESNext": Generates Modern JavaScript modules for our library (ES6 or higher)
  • "declarationDir": "types": Sets up a directory for the .d.ts files
  • "sourceMap": true: Enables mapping of JavaScript code back to its TypeScript file origins for debugging
  • "outDir": "dist": Sets up the directory for project build
  • "moduleResolution": "node": Follows Node.js rules for finding modules
  • "emitDeclarationOnly": true: Allows Rollup to generate JavaScript export type declarations

Now that TypeScript is configured, let’s move on to configuring Rollup.

Configuring Rollup

Rollup is a JavaScript module bundler that combines tiny chunks of code to create something larger and more sophisticated, like a library or application. Instead of using unique solutions like CommonJS and AMD, it leverages the standardized ES module structure for code.

To get started with Rollup, we’ll install it using the following command:

npm i -D rollup

In the above code, we added the -D flag to the Rollup installation command to add it to our devDependencies. Once the installation is completed, we’ll also install the following Rollup plugins:

We’ll run the following command to install the plugins:

npm i -D @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-typescript rollup-plugin-peer-deps-external rollup-plugin-terser rollup-plugin-dts

When the installation is complete, we’ll configure Rollup by creating a rollup.config.js file in the project root directory using the following snippets:

import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import typescript from "@rollup/plugin-typescript";
import dts from "rollup-plugin-dts";
import { terser } from "rollup-plugin-terser";
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
const packageJson = require("./package.json");

export default [
        input: "src/index.ts",
        output: [
                file: packageJson.main,
                format: "cjs",
                sourcemap: true,
                file: packageJson.module,
                format: "esm",
                sourcemap: true,
        plugins: [
            typescript({ tsconfig: "./tsconfig.json" }),
        external: ["react", "react-dom"]
        input: "dist/esm/types/index.d.ts",
        output: [{ file: "dist/index.d.ts", format: "esm" }],
        plugins: [dts()],

In the above code, we configured CommonJS and ES modules to handle the project build process. This will enable us to use named export and tree shaking. It also provides enhanced static analysis, browser support, and compatibility with other JavaScript versions.

Next, we need to specify the path to the CommonJS files and ES modules in the package.json file. We’ll open the package.json file and add the following snippet configurations:

"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",

Finally, we’ll add the build command inside the scripts object:

"scripts": {
    "build": "rollup -c"

Creating custom components

Now, let’s create our custom components. First, we’ll install Theme UI:

npm install -D theme-ui @emotion/react @emotion/styled @mdx-js/react

Theme UI is a flexible API framework, so we can choose to use a library of primitive UI components or use the sx prop. For the demonstration purpose of this tutorial, we’ll use the sx prop.

Creating the button component

Next, let’s create the button component. We’ll create Button.tsx and index.ts files in the src/components/Button folder.

Open the Button.tsx file and add the following snippets:

/** @jsxImportSource theme-ui */
import * as React from "react";
import { MouseEventHandler } from "react";

export interface ButtonProps {
    label?: string;
    color?: string;
    fontFamily?: string
    onClick?: MouseEventHandler<HTMLButtonElement>

const Button = (props: ButtonProps) => {
    return <button onClick={props.onClick} sx={{ color: props.color, fontFamily: props.fontFamily }}>{props.label}</button>;
export default Button;

In the above code, we added the /** @jsxImportSource theme-ui */ comment to the top of the file to enable the Theme UI sx props.

Next, we imported the React framework MouseEventHandler method to enable button click events.

Then, we defined the interface ButtonProps, specifying the Button component properties. The ? symbol denotes an optional property (e.g., color?: string), meaning TypeScript will not throw an error if we do not provide that property in the component.

Next, we created the Button component and set the styling using Theme UI’s sx props with other properties.

Now, let’s export the Button component in the index.ts file using the following snippet:

export { default } from './Button'

Creating the Input component

We’ll create the Input component by creating Input.tsx and index.ts files in the src/components/Input folder.

Open the Input.tsx file, and add the following snippet:

/** @jsxImportSource theme-ui */
import * as React from "react";
import { ChangeEventHandler } from "react"

export interface InputProps {
    label?: string;
    disabled?: boolean;
    fontFamily?: string;
    placeholder?: string;
    paddding?: string;
    id?: string;
    onChange?: ChangeEventHandler<HTMLInputElement>

const Input = (props: InputProps) => {
    return (
            <label htmlFor={ ? : 'text'}>{props.label}</label>
            <input type='text' id={ ? : 'text'} disabled={props.disabled} placeholder={props.placeholder} sx={{ padding: !props.paddding && "4px", display: 'block' }}></input>;
export default Input;

In the above code, we added the /** @jsxImportSource theme-ui */ comment at the start of the file to use the Theme UI’s sx prop to style the component.

Next, we defined the Input component’s interface. The onChange? property will enable us to add an onChange event to the input.

We created the Input component, which takes in props that match the InputProps interface, and styled the component using Theme UI’s sx prop and other properties.

Now, let’s export the Input component in the index.ts file using the following command:

export { default } from './Input'

We’ll create an index.ts file in the src/components folder, and export the components like so:

export { default as Button } from './Button';
export { default as Input } from './Input'

Now, we’ll build the project:

npm run build

The above command will create a dist folder in the project root directory.

Publishing to npm

The next step is to publish our newly created components to npm so that we can use them in our project and share them with friends.

To get started, log into your npm account:

npm login

If you don’t have an account, you can sign up for an npm account here.

After logging in, enter your account credentials to get authenticated. An OTP code will be sent to your registered email address. Enter the OTP pin when requested.

Now, publish your package by running the following command:

npm publish --access public

You’ve successfully published a React custom component library to npm!


In this tutorial, we introduced the Theme UI library and demonstrated how to use Theme UI to build a custom React component library. We also demonstrated how to configure TypeScript and Rollup for the project build and how to publish a custom library to npm. You can extend this tutorial by creating more components with Theme UI, such as forms, boxes, and grids.

Thanks for reading. Please feel free to share and comment below.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Clara Ekekenta Software Engineer and perpetual learner with a passion for OS and expertise in Python, JavaScript, Go, Rust, and Web 3.0.

Leave a Reply