The Ultimate Guide to UI Kits: How to Create, Integrate, and Master Them with Confidence Pt. 1

--

Hey there! I’m Alex Kuzmin, imaga’s Front-end Lead. My colleagues and I have combined our experience in preparing UI kits to create a comprehensive and easy-to-follow guide for beginners. Due to the article’s comprehensive nature, this guide will be split into 2 parts.

This is part 1, so without any further ado, let’s jump into UI Kits.

First off, a UI Kit is incredibly convenient — we’ll be providing this article to our interns and newcomers. Second, we’re more than happy to share all and any details — so read on, share your experiences, and feel free to ask any questions in the comments.

We’ll start with the very basics, covering everything from why you need UI Kits to how to use them in a real project. At the end, you’ll find a repository with code snippets that you can use in your own work. This guide is suitable for both beginner and experienced front-end developers. Special thanks to my colleague Angelina for helping me put this all together.

What is a UI Kit and Why Do You Need One?

A UI Kit is a set of user interface components that developers can use as building blocks to create application interfaces. This concept is used across all frameworks.

The main advantage of a UI Kit is that it allows you to quickly update interfaces across multiple applications using a single source of truth. This speeds up development in the long run. Although creating a library might take some extra time initially, it will definitely pay off in the future.

Here are some other advantages of UI Kits:

  • Seamless synchronization with design tools like Storybook.
  • Easier and quicker component testing, independent of the entire system.
  • Enhanced application security during updates, since everything is tested outside the application.
  • Increased development speed: if a component is used across multiple systems, you only need to write it once and then use it everywhere.

Next, I’ll discuss the categories we can divide libraries into and the strategies to choose based on these categories. After that, we’ll dive into the practical use of UI Kits and look at an example starter for a library.

The Basics of a UI Kit and Its Categories

Creating any component library requires a clear understanding of a few key factors:

  • How developers will use them.
  • How maintainable will the entire component system be?
  • How will the library integrate with the design system?
  • Optimal component integration methods.
  • How to maintain the library when supporting multiple design systems simultaneously, and so on.

And these are just some of the questions developers will inevitably face. When breaking down components, we can identify two main parts: logic and styles.

Logic handles the functionality of the component, such as event handling (clicks, focus, keyboard control, etc.), managing component states (open/close, active/inactive, etc.), and processing input data (filtering, sorting, etc.).

Styles are the shell of the component. Styles visually reflect the design system and therefore may change frequently and have a wide range of variations.

Based on these two components, all UI kits can be categorized into two groups:

  1. Library of Smart “Headless” Components (Headless UI). In this UI kit, there is no styling; all components will strictly include functionality only.

When to use these components:

  • When creating a library for use in different projects with varying style requirements.
  • In large projects with multiple teams, where one team may handle the library’s functionality and another the design.
  • When adapting a website for different platforms, allowing separation of styles.

2. Library of Smart and Beautiful Components. In this type, all components have both logic and styles.

When to use these components:

  • When the library is used across multiple projects where a unified design system is crucial.
  • When fast deployment of client projects is necessary.
  • When extensive flexibility in styling is not required.

Strategies for writing UI kit components

There are two main approaches to developing UI kits:

1. An all-in-one approach

Including a component with or without styles. In this approach, each component is a self-contained unit that already includes everything needed. Within this approach, two subtypes can be distinguished:

Inline styles using Styled Components (possibly, just adding style connection inside the component). This method allows writing styles directly within the component. This approach keeps styles isolated, reducing the likelihood of conflicts between styles of different components.

import styled from 'styled-components';
const StyledComponent = styled.div`
/* Component styles */
`;
const Component = () => (
<StyledComponent>
{/* … */}
</StyledComponent>
);
export default Component;

Style-Free (Headless) Components. In this approach, components provide only the logic without any built-in UI, giving you complete control over the styling. To create a library like this, it’s essential to understand the Compound Component pattern, which we’ll cover next.

const Component = () => {
return (
<>
{/* … */}
<>
);
};
export default Component;

2. Dependency CSS & Bundle CSS Approach

The second major approach is to connect styles and components separately. In this case, the styles and component logic are kept separate.

Dependency CSS: This method improves modularity and allows styles to be loaded only when they are truly needed.

Bundle CSS involves loading all styles at once, separately from the components. Essentially, all styles are combined into a single bundle and imported at the root of the project.

However, in practice, they are similar as styles are connected to components as modules.

import styles from './component.module.css'
const Component = () => {
return (
<div className={styles.div}>
<h1 className={styles.title}>Title</h1>
{/* … */}
</div>
);
};
export default Component;

Methods for Connecting Libraries

Now let’s examine how these approaches look in code. We’ll use examples based on a React/Next.js application.

Let’s start by exploring how these are typically integrated into a project. There are several methods to do so:

  • git + https — A fairly straightforward way to include a library as a dependency. In this case, by default, it references the latest commit on a branch in your repository. A downside is that you’ll need to install the library each time since the commit is locked in the lock file, ensuring you consistently get the same library version. Also, remember about the full Push build. In this case, we can’t ignore the Dist folder with the build.
  • Package registry — From our perspective, it’s a more reliable and familiar approach. You can set this up within your Git repository using CI/CD configuration, details of which are readily available in the documentation. For instance, here’s how you can do it in GitLab. The question then becomes whether you prefer to tackle this yourself or if you have a DevOps resource available to assist.
  • npm/yarn link — It’s a bit of a hack, but it gets the job done by locally linking your package as a dependency to your project, allowing you to use it without publishing.

When using Headless UI, Styled Components, or importing modular styles into a component file, it’s straightforward. We import the component and can immediately start using it. In all cases, we allow for passing a class to customize styles externally.

import { Button, Typography } from "@frontend/ui-kit";

One limitation when using styles as a component dependency could be your framework. For instance, Next.js prohibits this approach. You can read more about it in the documentation.

When using a full library of components, we have two different paths ahead:

  1. Importing all styles into the application in one file, and then using components where needed. It all depends on your framework. In Next.js App Router, you need to import styles in the root application file. In our case, this is _app.tsx located at ./src/pages, with just one line:
import "@frontend/ui-kit/dist/style.css";

And then, similar to the previous example, we can use a similar import where needed:

import { Button, Typography } from "@frontend/ui-kit";

If you have a Single Page Application (SPA), this is the simplest and likely the correct approach to using components.

2. Using SSR/SSG when we don’t need the entire style bundle. It’s straightforward, but not as convenient. The first thing to do is to write/find and connect a plugin that allows separating styles from the component without injecting them as dependencies. Next, we’ll see how this will be used in the code. I’ll say right away that nothing changes for importing components. It all remains as a single line:

import { Button, Typography } from "@frontend/ui-kit";

Now let’s see how styles are connected:

import "@frontend/ui-kit/dist/Button/Button.css";
import "@frontend/ui-kit/dist/Typography/Typography.css";

It’s tolerable here, but not as convenient as with a shared bundle. Especially considering that this needs to be done for each component, and there can be a lot of them. However, we save on the bundle size that the end user of the application will receive.

This article is getting pretty long, so we’ll pause here for now — this concludes Part 1. In Part 2, we’ll dive deeper into adding flexibility to the UI Kit, analyzing the chosen strategy, and discussing how to implement it. If you have any questions, feel free to ask in the comments.

--

--