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.
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.
This hands-on tutorial has the following prerequisites:
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:
š¦themecomponentui
⣠šsrc
ā ⣠šcomponents
ā ā ⣠šButton
ā ā ⣠šInput
Now, letās configure TypeScript for our project.
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": [
"dist",
"node_modules",
],
}
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 declarationsNow that TypeScript is configured, letās move on to 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:
node_modules.d.ts filespeerDependencies in a rollup bundleWeā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: [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({ tsconfig: "./tsconfig.json" }),
terser(),
],
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"
},
...
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.
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'
Input componentWeā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 (
<div>
<label htmlFor={props.id ? props.id : 'text'}>{props.label}</label>
<input type='text' id={props.id ? props.id : 'text'} disabled={props.disabled} placeholder={props.placeholder} sx={{ padding: !props.paddding && "4px", display: 'block' }}></input>;
</div>
);
};
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.
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.
Install LogRocket via npm or script tag. LogRocket.init() must be called client-side, not
server-side
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
// Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>

Learn how to use browser AI APIs for summarization, translation, and language detection with practical examples and implementation tips.

Build a live task board with Topcoat and Rust, using reactive signals, server-rendered search, and WebSocket updates.

Learn how to build and install three Claude Code mods to monitor usage, protect secrets, and track changes.

I tested Jev against popular LLMs to compare speed, cost, and accuracy on real bank support messages.
Hey there, want to help make our blog better?
Join LogRocketās Content Advisory Board. Youāll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up now