AUTHOR
Abir Pal, Software Engineer
Abir is a curious software developer, who loves to build fullstack applications, demystify and unwind software abstractions. He is also a Major League Hacking Fellow. He has been featured by Postman Inc, AsyncAPI Initiative, Javascript.info and also been invited as Guest Speaker at DSAILT Conference by Georgia Tech and Nvidia.
What is a Component Library?
Component Library helps you to define consistent branding across all your products. Starting from buttons, forms, to modals, charts, it consists of all the elements that represent your brand.
Why do we need a component library?
With a growing number of changes in projects, maintaining a consistent UI across all the product pages, is a challenging task, and can affect business productivity. Henceforth, with component libraries, one can quickly implement the required UI components without worrying about the brand design guidelines.
In short, component libraries make developers' lives easier. And today, we are going to learn how to build one such advanced component library using Chakra UI. We divide this article into the following sections.
- Extending Themes in Chakra UI
- Setting up the theme file.
- Extending a foundation theme style
- Extending a component theme style
- Creating Complex Custom Components in Chakra UI.
- Packaging your library to use it in other projects.
Extending Themes in Chakra UI
Extending themes in Chakra UI for developers has a rich experience. To give components a look of our brand design, we must extend the theme.
For example, in a react application, we pass the theme to the ChakraProvider the following way,
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>
So to create a theme, we will use the extendTheme
package by Chakra UI.
//theme.ts
import { extendTheme } from "@chakra-ui/rect";
const theme = extendTheme({
colors:{
brand:{
100:"#f7fafc",
// ...
900: "1a202c"
}
}
);
Setting up the theme file.
Using the above approach makes the theme file less developer friendly and may throw a linting error on CI. To make the theme file developer friendly. We refactor theme to following directory structure.
theme
- index.ts
- components
// all custom themes for single components
- foundations
// theme related to borders, spacings, colors.
This makes our theme file look more clean and readable.
// theme/index.ts
import { extendTheme, type ThemeConfig } from "@chakra-ui/react";
// Foundations
import * as foundations from "./foundations";
// Components
import * as components from "./components"
// Chakra Configuration on Initial Mode
const config: ThemeConfig = {
initialColorMode: "dark",
useSystemColorMode: false,
};
const theme = extendTheme({
config,
...foundations,
components:{
...components,
},
});
export default theme;
Extending a foundation theme
Let’s try to extend a foundation theme for colors. Head over to foundation directory and create two files, index.ts
and colors.ts
.
For reference, the foundations directory should be like.
- theme
- foundations
- index.ts
- colors.ts
Let’s open colors.ts, with your favorite editor, and we add the base colors, functional colors, and the neutral colors.
// theme/foundations/colors.ts
const base = {
rose: "#F81C9D",
orange: "#FC8926",
onyx: "#383838",
};
const functional = {
yellow: "#FFB83D",
red: "#FF4A4A",
green: "#00C543",
blue: "#23A9F2",
};
const neutral = {
0: "#FFFFFF",
50: "#D7D7D7",
100: "#1A1A1A",
};
const colors = {
base,
neutral,
functional
};
export default colors;
Now head over to foundations/index.ts file and import the colors.
// theme/foundations/index.ts
export { default as colors } from "./color.ts"
Congratulations! A pat on your back, you just completed with colors.
Extending a component theme style
So far, we have extended a foundation theme, now let’s add style to a Chakra UI component. So whenever we use a Chakra UI component, it follows our brand guidelines.
One can try out styling all the components in Chakra UI. But for this article’s scope, we will be using the button component from Chakra UI.
For any Chakra UI component, the final exported variable should narrow down to the following structure.
// Example structure for custom theme to Chakra UI components.
import { ComponentStyleConfig } from "@chakra-ui/rect";
const ComponentStyle: ComponentStyleConfig = {
// style object for base or default style
baseStyle: {},
// styles for different sizes ("sm", "md", "lg")
sizes: {},
// styles for different visual variants ("outline", "solid")
variants: {},
// default values for `size` and `variant`
defaultProps: {
size: '',
variant: '',
},
}
export default ComponentStyle;
Let’s start with baseStyle
.
baseStyle
consists of the styles that apply to all the variants of the component.
Usually, font family, paddings, margins, etc are set up in a baseStyle.
Let’s say all the variants of Button have the same padding, font color, and a same disabled background
// theme/components/Button.ts
import { SystemStyleFunction, mode } from "@chakra-ui/theme-tools"
const baseStyle: SystemStyleFunction = (props) => {
color: mode(
props.theme.colors.neutral["100"],
props.theme.colors.neutral["0"]
)(props),
padding: "2px",
_disabled: {
opacity: 0.4,
cursor: "not-allowed",
},
_hover: {
//disabled button on hover, should have initial background.
_disabled: {
bg: "initial",
},
},
}
const Button: ComponentStyleConfig = {
baseStyle,
};
export default Button;
You may wonder?
-
What does
mode
do? It is a theme tool, which helps you to toggle colors in light and dark mode. And has a function signature likemode(light, dark)(props)
-
Also above we tried leveraging power of theme property to use colors from our brand, which we implemented in previous section. And this helps you to create baseStyle.
Note: Chaining of props to mode is required for leveraging
props.theme
Similar way, one can create a variant, and pass it to the final exported variable. Let’s see how.
// theme/components/Button.ts
const primaryVariant: SystemStyleFunction = (props) => {
return {
//yourVariantStylesHere
}
}
const secondaryVariant: SystemStyleFunction = (props) => {
return {
//yourVariantStylesHere
}
}
const variants = {
primary: primaryVariant,
secondary: secondaryVariant,
}
const Button: ComponentStyleConfig = {
baseStyle,
variants,
};
export default Button;
Few Chakra UI components like, input, checkbox, radio, tabs, switch, alert, etc. require specific fields under base style and variants. To learn more about these specific fields, its recommended to go through the following directory in Chakra UI repository here
Congratulations, we finally completed how to style the existing themes in Chakra UI. By this section, you can create custom styles of various components, as per your custom branding guidelines.
Creating Complex Custom Components in Chakra UI
So far, we have created a custom style for a component. But in daily work, we mostly have a custom component which includes multiple Chakra UI components. So how do we make them look as per our brand design guidelines.
To do it, one has to follow these three steps
- List out all the Chakra UI components used as child components, in the parent custom component.
- Style the individual Chakra UI components, as shown in the previous section.
- Finally, use these individual Chakra UI components, back together in the custom component.
Note: For step 2, the best practice will be to create a variant, for the specific component, and then use it during the 3rd step
One may wonder, these components are not themes, so to which directory these components belong to. So to answer such questions, we will be following this directory structure.
- components
// all the custom components
- theme
- index.ts
- components
// custom themes for single components
- foundations
// theme related to borders, spacings, colors.
Let’s say we want to create a CustomInput
Component, which consists of these Chakra UI components FormLabel, Input, and FormHelperText.
We identified the child Chakra UI components, these are FormLabel
, Input
, and FormHelperText
.
Now we have to create the layout of the CustomInput, so head over to, components directory, and create a file CustomInput.ts
//components/CustomInput.tsx
import React from 'react';
import {
FormControl,
FormErrorMessage,
FormLabel,
FormHelperText,
FormControlProps,
FormLabelProps,
} from "@chakra-ui/form-control";
import {
Input,
InputProps as ChakraInputProps,
} from "@chakra-ui/input";
import { UseFormRegisterReturn } from "react-hook-form";
export type CustomInputProps = FormControlProps & FormLabelProps & {
label: ReactNode;
helper?: ReactNode;
error?: ReactNode;
input?: ChakraInputProps & UseFormRegisterReturn;
};
export const CustomInput = ({
label,
helper,
Error,
input,
...props
}: CustomInputProps) => {
return (
<>
<FormControl {...props} {...input} isInvalid={!!error} >
<FormLabel
{...props}
mb={4}
>
{label}
</FormLabel>
<Input
{...input}
{...props}
onChange={(change) => {
if (input)
input.onChange(change);
}}
/>
<FormHelperText>{helper}</FormHelperText>
<FormErrorMessage>{error}</FormErrorMessage>
</FormControl>
</>
);
}
export default CustomInput;
Once we created the layout, we use the same strategy to style individual Chakra UI components discussed in previous section.
Make sure, to go through the specific fields used in FormLabel
and Input
from following directory in Chakra UI repository here
Afterwards, we can use the CustomInput component, by just importing it without worrying about brand guidelines.
Packaging your Library to use it in other projects.
So in previous sections, we built a theme and custom components. But in order to use it in other projects, we have to transpile it to es modules and commonjs modules.
Our final objective here is to make sure, other developers in other projects, can use our components and theme easily with just one import as shown below:
import { theme, CustomInput } from "component-library"
Before we proceed further, let’s make sure, our directory finally looks like this.
- component-library
- src
- theme
- components
- index.ts
- package.json
- yarn.lock
- .gitignore
Make sure, the src/index.ts
have the imports and exports from theme and components.
To package this, we will use the rollup library. Rollup helps to package javascript libraries, and is being widely used.
To install rollup as dev dependency, use
yarn add rollup --dev
Create rollup.config.js
in the project root directory, and paste the following.
Make sure to install the rollup plugins as shown in imports.
// rollup.config.js
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "@rollup/plugin-node-resolve";
import commonjs from "@rollup/plugin-commonjs";
import { terser } from "rollup-plugin-terser";
import typescript from "rollup-plugin-typescript2";
export default {
//entry point
input: "src/index.ts",
preserveModules: true,
//output directory
output: [
{
dir: "./dist/cjs/",
format: "cjs",
sourcemap: true,
exports: "auto",
},
{
dir: "./dist/esm/",
format: "esm",
sourcemap: true,
exports: "auto",
},
],
//plugins
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
typescript({ useTsconfigDeclarationDir: true }),
terser(),
],
};
Let's update the build script in package.json, and your component library is ready to packaged.
//package.json
{
"name": "component-library",
"version": "1.0.0",
"license": "MIT",
"main": "./dist/cjs/src/index.js",
"module": "./dist/esm/src/index.js",
"types": "./dist/index.d.ts",
"src": "./src",
"files": [
"/dist"
],
"scripts": {
"build": "rm -rf ./dist && rollup -c"
}
}
To transpile and package, run yarn build
, and you will find the newly created dist directory, in the project root.
Now you can easily import this, in other projects.
Congratulations
Now you can create an advanced component library in Chakra UI and show it to world.
Feel free to join our codiga community on discord.