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

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 contain xs ,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.

  • 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
  • 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.

  • Header tag: The headerType prop decides the HTML header tag of the component. We can choose headerType: h1 and variant: 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.

  • 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

  • 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.

  • 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
Skeleton

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.

  • badgeText*: string
  • color: ‘primary’ | ‘gray’ | ‘red’ | ‘green’
  • classes: string

NavIcon

  • IconComponent*: ReactComponent
  • count: number
  • linkTo: string
  • ariaLabel: string
  • classes: string

That's all for base components.

Thank you. See you next time.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Karthick Ragavendran

Fullstack engineer @ atem.green | React, Typescript, Redux, JavaScript, UI, Storybook, CSS, UX, Cypress, CI/CD.