How design systems are created: sharing our own example. Part 2

Dmitrii Pashkevich
Quadcode
Published in
11 min readJul 28, 2023

Hey, everyone! This is Dmitry Pashkevich, and I am a frontend developer at Quadcode, specializing in the creation and development of design systems. This is the second part of a comprehensive article, “How Design Systems Are Created.” Last time, we discussed the basics of design systems — problems that prompt a company to create design systems, phases of forming design systems, and general design system development. Today, I’m going to delve into our own experience, explain how to define product (and design system) requirements and much more. Let’s dive in!

Defining Product Requirements and, as a result, Design System Requirements

The process of defining product requirements began with the frontend (FE) team choosing suitable tools to implement the entire project, not just the design system, based on minimal requirements:

  • React:

a) Server-Side Rendering (SSR) for the main page,

b) Client-Side Rendering (CSR) for the user dashboard.

  • TypeScript.
  • Work with REST/GraphQL/WebSockets.
  • SSR support.
  • React components should be covered by tests.
  • Usage of modern transpilation tooling — Babel/Esbuild, etc.
  • Enforced linting and code style (code analyzer).
  • Defined project folder structure and agreement on code organization.
  • Optional: use a UI kit and reuse it for components.
  • Optional: ability to view the project’s UI kit and provide it to designers.

As a result of this selection, the following technologies and tools were chosen:

  1. React + TypeScript
  2. Eslint with a set of preferred rules
  3. GraphQL — Apollo
  4. TanStack Query
  5. Schema-first approach for API development, generating TypeScript types from Swagger schemas
  6. Styled-components
  7. Vite as the project bundler
  8. Storybook for component display
  9. npm as the package manager (Yarn/pnpm/npm were considered)
  10. Monorepo using NX
  11. Feature-Sliced Design for code architecture and structure
  12. Testing: unit tests, Jest (later transitioned to Vitest), RTL/snapshot testing
  13. Monitoring with Sentry

At this stage, we also considered the UI kit and how we would build it:

  • Possible use of react-hook-form for working with forms
  • Using styled-system.com for building the UI, building from scratch, or exploring alternative options

The question of building the UI kit sparked heated discussions and extensive research. I had no desire to start from scratch and go through the process of creating a new set of components again (which I had already experienced and took a long time), while also encountering the same pitfalls. Additionally, it was clear at that time that building the UI kit would not take up much time.

After some more time spent on research and intense discussions, a solution emerged. We chose Mantine, although we considered other options such as Ant Design, Chakra UI, and Semantic UI. Mantine won our hearts with its project documentation and more. In the next section, I will provide more details on why we were so impressed with this tool. This choice became the starting point for working on the design system, and we already had a sense that it would be an entirely new experience.

Results of Working with Requirements or Why Mantine?

The result of the research was a rough draft of the application skeleton. You can check out an example on Github. The final solution differs from what is presented in the repository, but the concept is reflected. After creating the skeleton, we started working on the design system.

The development of the design system began by identifying the following basic elements:

- Color palettes

- Typography

- Margins/Padding

- Icons/Images

- Borders

- Radii (rounded corners)

- Patterns/UX/UI flows

- Animations

Based on this, we initially set up a configuration for theming (and now we are gradually building the design tokens system). As mentioned earlier, our design system is built using Mantine. So, what made it so useful? On the one hand, it might seem limiting because we are relying on a specific tool. But on the other hand:

Mantine is a clear and flexible tool

It provides:

  • Documentation;
  • Theming capabilities;
  • Typing;
  • Source code that can be used as supplementary/training/inspirational material;
  • A broad set of basic, easily customizable components, most of which have been tested in projects and are supported by the community, indicating that they cover many typical use cases and have resolved enough bugs to be used in production;
  • Components are broken down into layers, with almost every layer being customizable and stylizable. Again, if you need to do something specific, you can always take the source code of the basic elements and customize it or create a pull request;

In essence, for us, Mantine serves as the foundation for building our “home” = the design system.

Speed of development

Comparing the efforts required to create components from scratch versus using Mantine, it looks like this:

From scratch (under limited time constraints):

1. You come up with the logic of how the component will work in different states (default/active/focus/disabled, etc.) and then you program the logic/styles.

2. You think about how it can be styled externally (usually, people don’t think/suspect this at the start), and then there are slight differences here and there — no one breaks down the component into layers (each layer/element of the layer should be stylized).

3. There are also different variations of the component, and you have to implement logic and styles for them too.

a) Due to time constraints, it might end up being not very convenient to use and modify the API.

If we use Mantine:

You have a ready-made basic component, organized into layers with predefined states (default/active/focus/disabled, etc.) and behavior logic. Essentially, this is your limitation and safety in component creation.

You simply style the component according to your theme. Styling variations involve describing different styles for display based on the known classes for the layers.

If there is a need for some custom logic, it can be easily implemented through the standard component API or by modifying the source code, but within your own project.

I believe it is essential to note that at the product launch, sometimes it is necessary to simplify the interface and make reasonable assumptions because it is not always possible to achieve everything we want right away. Striking a balance is crucial.

According to some estimates, creating the final component based on Mantine is approximately 5–10 times faster than building it from scratch.

This is achieved because:

- The component’s parameters and layers are immediately clear.

- You can interactively view the component in the documentation, which gives you an understanding of its capabilities and limitations, along with easily readable source code.

- You can start using the component immediately without having to come up with its logic from scratch, by using the offered API, which covers around 80–90% of the logic needs for displaying the component, and in most cases, even 100%.

Layered API:

Let’s consider layers using the example of a simple button. On the surface, it may not seem complicated, but here’s how it looks in layers:

- Root

- Inner

- Icon + Left Icon

- Label

- Loader

- Icon + Right Icon

Or, for a more complex component like Select, it can be broken down into layers with the ability to modify the rendering of the dropdown list, etc.

You can create your custom components according to the following rules:

Examples of the code can be viewed here.

The impact of Mantine version updates

The skeleton of our project and part of the setup were created using Mantine version 4. We went through the update to version 5 relatively smoothly, and soon we will have to update to version 6. It might be a bit more challenging, but the team has provided a detailed CHANGELOG to help with the process.

Benefits

Mantine is about achieving more results with less effort.

When starting to work on a design system, a lot of time is always invested in basic components, no matter how much we want to avoid it. However, using auxiliary tools significantly eases the work for both developers and designers.

Our very intense development and design phase lasted about one quarter. During this time, 96% of the components were ready, the framework of the application was set up with various providers (authorization, translations, etc.), and several sections related to authentication and onboarding were implemented.

The phase of active synchronization took another quarter. A few new components were introduced (no more than 10), and most of the time was spent on assembling pages.

Currently, we are in the third quarter of development, and I believe we have reached a plateau — there are no major changes happening. However, soon we will begin moving towards design tokens and scaling the design system to other team projects. I think we will go through all three phases of design system development to some extent again.

Summarizing the Definition of a Design System

Let me provide a brief summary and definition of what a design system is.

I’ll start by saying that in my understanding, the ideal start of working on a design system is when passionate individuals from both the front-end development and design sides come together. These individuals are enthusiastic and willing to put in the effort, even staying up late at night to discuss and improve their creation. And there’s a little time to start.

Developing a design system is about:

  • Having a single source of truth for UI/UX and interface text.
  • Flexibility, standardization, and simplicity.
  • Thoughtfulness, efficiency, and transparency.
  • Change safety, consistency, and inheritability.
  • Automation (not necessarily at the beginning).
  • Accessibility.
  • Constant communication.

A design system is a set of rules for styling products that helps maintain the integrity of the user experience and optimize resources expended on development and design. It involves a high degree of autonomy through a unified design, component library, guidelines, and established development approaches. This, in turn, allows businesses to simply articulate the problem and what they want to change, while the design system with all its processes and approaches begins to help solve the identified issues.

Ultimately, a design system encompasses:

  • Communication rules.
  • Visual language.
  • Documentation.
  • Component library.

I hope this provides an understanding that a UI kit is just a component library, and without all the other elements, it cannot be called a design system.

What a Design System has brought and who it benefits

A design system is not a simple and cheap tool that can be implemented and used casually. A team or company should reach a certain point where the use of this approach is justified.

Let’s take a look at what we ultimately achieved after 3 quarters of working on the design system and product.

  • Each team member found value in the design system: costs and feature release speed: By reusing well-designed components, more resources are available for creating new/non-standard features in the product. After laying the foundation, we became faster in designing and assembling application sections.
  • Reduction in design time: Using the design system allows designers to reduce the time spent on design and development. This is possible because a significant portion of design elements and components are already ready and can be reused.
  • Unified design and components for development: We hardly write styles within the project, thanks to the unified design and components. The proportion of styling files is less than 4%, which speeds up development and the release of new pages exponentially (time to market).
  • User interface development is like building with blocks: We spend more time on the application’s logic rather than its layout.
  • Single source of truth: Since the design system is the source of truth, there are no discrepancies in interface-related decisions. Any team member can review the design and implementation to ensure compliance with the layout and rules. This helps maintain the quality of released solutions.
  • Convenient testing setup: We have design reviews followed by task/feature testing. If there are changes, they are implemented both in Figma and Storybook. For example, our QA team often uses Figma and consults Storybook to review component properties and capabilities. When working with content and editors, we know that most of the product’s textual content is reflected in the design. The text is pulled from a specialized service.
  • Product quality: Using the design system allows us to create higher quality products. Strict styling rules, a unified design, and the reuse of elements ensure that the product meets quality standards.
  • Developers propose solutions before design: Developers can propose solutions for certain tasks based on established approaches and coordinate them with designers (if the design is not ready yet).
  • Onboarding new employees: During onboarding, new employees gain a clear understanding of the capabilities of the components used in the project. They can view interactive examples of their work and read the documentation.
  • Unified solution across different projects: There is no need to adapt to a new interface workflow.
  • Using a design system helps create a unified design, which enhances the user’s perception of the product. Strict styling rules and the use of a consistent style throughout the project ensure a uniform user interface and make the project more recognizable.
  • Fast adaptation to changes: This is more relevant for design, as development becomes more laborious when the API of a component is not compatible with the previous version. The design system allows for quick adaptation to project changes. If changes are made to a component, they automatically apply wherever it is used, which accelerates the adaptation process.
  • No bloating of the team’s technical stack: Focus remains on implementing our own UI kit. The product is executed in a unified style, despite consisting of multiple services. There is no need to learn a new framework/version of another library.

The main benefits we gained as a team have been listed above. However, there are certain aspects worth considering, which are not necessarily drawbacks but rather peculiarities:

Synchronizing component changes in Figma and code can be challenging.

Starting a product is more challenging as components need to be developed first before the business begins receiving fully functional application pages incrementally. However, this is true for the first two or three iterations of implementing the UI kit in new projects, as it requires resolving related discrepancies during integration: settings, styling, documentation, versioning, etc. Overall, it is a solvable problem, and with each new project, the startup process accelerates significantly.

Maintaining components as a library is not always easy: versioning and applying new versions to all projects, testing them, etc., can consume a considerable amount of resources. This is especially true at the beginning when requirements can change frequently.

Plans for Design System Development

What’s next? We want to:

  • Separate components into a separate package:
  • Implement versioning in code.
  • Implement versioning in design.
  • Start implementing the design system in other team projects:
  • Address and resolve related issues.
  • Develop new components and improve existing ones.
  • Upgrade to version 6 of Mantine.
  • Implement design tokens for key components.
  • Implement color scheme management.
  • Describe standards for component creation and their entire lifecycle.
  • Start describing contexts and rules for component usage.

At the time of article publication, some of these tasks will already be completed.

Code Examples

This is it! I hope you’ve enjoyed this article. Let me know what you think about it in the comments. I’ll be glad to answer any of your questions. See you!

--

--