Design System Components Using Tailwind
We laid out our design system foundations using tailwind in the previous section. Now let's create the base components that serve us like atoms or base building blocks for our application.
Let's build them one by one. A couple of things before that.
Script Generator
We are going to place these components inside atoms
folder.
npx rsb-gen atoms/ComponentName
rsb-gen is a script generator npm package I published that creates the below files.
ComponentName.tsx
ComponentName.stories.tsx
ComponentName.test.tsx
index.ts
This is a huge timesaver that helps us avoid copy-pasting and renaming whenever we need a new component.
Utility classes Vs Component abstraction
Why should we create a Button component when we can use the utility classes to create one on the fly?
Look at the below example.
// Abstracted component
<Button {…props}>Hello world</Button>// Utility classes on the fly.
<button className="bg-primary-600 px-3 py-2 rounded">Hello world</button>
- One source of truth: Any change requires us to find all the buttons and change them all.
- Consistency: If the developer thinks, ‘I need a big button. What paddings should I give?’ the application will end up having thousands of different buttons.
- Cognitively non-demanding: Using an abstracted button with a predetermined API will reduce the choices the developer needs to make and hence brings down the cognitive overload of the developer.
Button
Run the below command to generate the button component inside the atoms folder.
npx rsb-gen atoms/Button
API
The below are the props the button component is going to have with children being the mandatory* prop.
- children*:
string | ReactElement
— The items we wrap between the tags. - variant:
‘contained’ | ‘outlined’ | ‘text’
- color:
‘primary’ | ‘success’ | ‘error’
— Our application does not have a secondary color. - size:
‘sm’ | ‘md’ | ‘lg’
— This is the scale we are going with. This scale can evolve to containxs
,xl
and so on if needed. - fullWidth:
true | false
- disabled:
true | false
- clickAction:
Function
— The callback function. - classes:
string
— This is a fire exit where the developer can pass any CSS class name.
We implement the button as below.
Points
- We create an interface based on the requirements.
- We create variables for prop combinations.
- The developer has to only pass
children
and the default values will handle the missing props with the most-probable values.
That is it. I created all the variants in the Button storybook. Let's see it in action.
We will certainly face some modifications, and new requirements while working on actual pages. We have to make sure that these atom
components are flexible enough to accommodate them.
Heading
This component will cover the need for all the header texts the developers don’t have to use <h>
tags at all.
npx rsb-gen atoms/Heading
API
- children*:
string | ReactElement
- variant:
‘heading-1’ | ‘heading-2’ | ‘heading-3’
- weight:
‘normal’ | ‘medium’ | ‘semibold’ | ‘bold’ | ‘extrabold’
- headerType:
‘h1’ | ‘h2’ | ‘h3’ | ‘h4’ | ‘h5’ | ‘h6’
- classes:
string
The component implement is below.
Points:
- Header tag: The
headerType
prop decides the HTML header tag of the component. We can chooseheaderType: h1
andvariant: heading-3
when the page heading is not the prominent thing on the page. - Trim later: The weight increases the overall combinations. So, in the development, we can run an analysis tool to find the usage of props and limit them.
This is how our component looks like.
Text
Let's create a component for body copy.
API
- children:
string | ReactElement
- size:
‘xs’ | ‘sm’ | ‘md’ | ‘lg’
- muted:
true | false
- textType:
‘p’ | ‘span’
- uppercase:
true | false
- clamp:
‘none’ | ‘1’ | ‘2’ | ‘3’ | ‘4’ | ‘5’ | ‘6’
- classes:
string
Avatar
API
- src*:
string
- size:
‘xs’ | ‘sm’ | ‘md’ | ‘lg’
- rounded:
‘none’ | ‘xs’ | ‘sm’ | ‘md’ | ‘lg’ | ‘full’
- ring:
true | false
- shadow:
true | false
Here it is in action.
Skeleton
Skeletons not only provide a smooth loading experience but also improve the cumulative layout shift score as the space for the items is already occupied in the screen.
API
- shape:
‘circle’ | ‘rectangle’
- height:
‘1’ | ‘2’ | ‘3’ | ‘4’ | ‘5’ | ‘6’ | ‘7’ | ‘8’ | ‘9’ | ‘10’
- width:
‘w-4/5’ | ‘w-4/6’ | ‘w-5/6’ | ‘w-1/2’ | ‘w-2/3’ | ‘w-3/4’ | ‘w-full’
- classes:
string
Badge
The pattern is almost like how we translate the props into our utility classes. In this example, the prop badgeText
is just any string. color
gets directly translated into corresponding bg
, text
, and border
colors.
API
- badgeText*:
string
- color:
‘primary’ | ‘gray’ | ‘red’ | ‘green’
- classes:
string
NavIcon
API
- IconComponent*:
ReactComponent
- count:
number
- linkTo:
string
- ariaLabel:
string
- classes:
string
That's all for base components.
Thank you. See you next time.