logoGERASOFT
Blog

From Idea to App - Coding the Theme and UI Library

Reading time: 6-15 minsTechnicality: 4/5Last updated: Jan 29, 2025
From Idea to App - Coding the Theme and UI Library

Introduction

In the previous post we demonstrated how the design library was made including:

  • variables
    • colours
    • spacing
    • size
    • font size
  • components
    • typography
    • input
    • button
    • checkbox
    • modal

Now it's time to implement them in code. We followed the sequence of how the designs were made, starting with variables.

Variables

Colours

For colours, we created a separate colors.css file in @repo/ui, storing the colours as root variables in RGB format, imported them in index.css, and extended the theme in tailwind-preset.ts with these colours, referencing them via their variable names.

For the non-opaque colours we left out the alpha value to let tailwind set it.

Space, size, font size

Instead of explicit pixel values, we work in rems (reference to root element's font size - html), and used 16px as the root font size. We had to convert the pixel values specified in Figma to rems (for example xl font size is 32px, which translates to 2rem - 32 / 16).

The changes can be found here: GitHub link

Components

Icons

We added the react-icons package that contains the ionicons package which we use in the designs.

Because it inserts the icons as svg tags instead of img, we can freely customise the colour and size of the icons.

Compare the changes on GitHub here: GitHub link

Typography

First, we created the tailwind styles for each typography element:

  • h1
  • h2
  • h3
  • body large
  • body regular
  • body small
  • note

specifying their:

  • font size (from the fontSize variables)
  • line height
  • font weight

We created a low-level Text component that is highly customizable. It supports a custom className and accepts $variant and $bold props for flexibility.

We also created each typography element as individual components, using Text, and specifying their respective $variants.

Check the code: GitHub link

Buttons

To match the design system, we structured button styles based on:

  • Variants (Primary, Secondary, Outline, Ghost)
  • States (Default, Hover, Pressed, Disabled)
  • Sizes (Small, Medium, Large)

Each of these was defined in a way that allows easy reuse and modification, to make sure buttons are consistent.

Instead of styling each button manually, we created a base component (ButtonBase) that:

  • Handles shared button behaviour (cursor styles, transitions, disabled state).
  • Dynamically applies the correct variant and size styles using props.

The main Button component extends this by adding:

  • A label prop for text-based buttons wrapped in BodyRegular.
  • $size and $variant props to match designs.
  • Optional IconLeft and IconRight props to easily include icons.

For even cleaner usage, we also defined preconfigured buttons:

  • <PrimaryButton />
  • <SecondaryButton />
  • <OutlineButton />
  • <GhostButton />

These provide an easy way to use the correct styles without repeating props, keeping the UI implementation simple.

To ensure buttons work as expected, we:

  • Wrote unit tests to check behaviour across all variants and states.
  • Added Storybook to inspect each version visually and test interactions.

See the changes on GitHub: GitHub link

Inputs

Similarly to buttons, we styled inputs based on the design system with:

  • Variants (Outline, Underline)
  • States (Default, Focused, Error, Disabled)
  • Sizes (Medium, Large)

We structured the input component into reusable parts:

  • InputBase – Core input styling.
  • InputWrapper – Handles labels, notes, and errors.
  • InputLabel, InputNote, InputError – Text elements.

The Input component combines these elements, and alongside with native input props, it supports additional props to match designs:

  • $variant and $size props for quick customization.
  • Optional label , note , and error (can be hidden with hideError ).
  • Optional IconLeft and IconRight props to easily include icons.
  • ARIA attributes for accessibility.

We also wrote unit tests to validate behaviour and added Storybook stories to preview all variations.

See the changes on GitHub: GitHub link

Checkbox

Checkbox variations are based on:

  • Size (Medium, Large)
  • State (Default, Hover, Focused, Disabled)
  • Completion Status (Checked/Unchecked)

Since native checkboxes are difficult to style, we created a fake checkbox and linked it to the actual input via label. The checked state is handled through conditional styles.

We reused informational components from Input, including:

  • InputNote – Displays additional information.
  • InputError – Shows error messages when validation fails.

Custom properties follow the same structure as Input:

  • $size to match the design system.
  • Optional label, note, and error (with hideError to disable error messages).
  • ARIA attributes for accessibility.

As standard, we wrote unit tests and added Storybook stories to verify interactions, states, and accessibility.

This is how it looks like in code: GitHub link

A Note on Inputs

As seen in the code, we avoided implementing controlled inputs and instead forwarded refs . The reason for this is to leverage native functionality as much as possible—uncontrolled inputs are more performant since they don't rely on React state or cause unnecessary re-renders.

(We will remove forwardRef when upgrading to React 19, as it has been deprecated.)

Modal

The modal consists of two components:

  • Overlay – A full-screen layer with a semi-transparent black background that blocks interaction with content behind it.
  • Modal – A component that appears in front of the overlay, drawing focus to user interactions or important content.

The Modal component is flexible in terms of content, allowing any children to be passed inside. It includes predefined styles and optional elements:

  • title – An optional heading at the top.
  • Close button – Can be hidden with hideCloseButton.

The modal closes when the overlay is clicked or when the close button is pressed.

We wrote unit tests, and stories for the modal. Check the code here: GitHub link

Keeping Track of Progress

Full code

Summary of all changes in the code can be found here: GitHub link.

ClickUp Board

Tasks and subtasks for this setup are listed on our ClickUp board.

Designs

Full designs can be found in the Figma file:

Wiki

All key project documentation is available in our ClickUp wiki.

Next Steps

We will use these components to build the application's screens, aligning them with the user stories and design specifications.

In the meantime, design will be working on the authentication screens:

  • Sign in
  • Sign up
  • Reset password
  • Set password
  • Verify email

Here's how our timeline look like now:

12345
Create designs for authentication screens
5 d
Implement task management user stories
3 d