Forging the frontend backbone — Stem UI

Ashutosh Rahi
Attentive.ai Engineering
10 min readFeb 15, 2024

Imagine a scenario where crafting digital experiences consistently adheres to a unified design language, effortlessly and seamlessly. Yet, in reality, developers often grapple with the challenge of maintaining design consistency across various products they build, leading to fragmented user experiences. At Attentive.ai, we recognized this struggle and set out to address it head-on — with Stem UI.

Built on the foundations of our very own Stem Design System, which offers the design and UX guidelines for how the components should look and behave, Stem UI empowers frontend engineers to innovate rather than reinvent the wheel every time a new product is built at Attentive.

We share our insights and experiences with the hope that they will guide and inspire you in your own journey toward creating a robust component library.

But before we start.

Simplifying the Jargon: What We’re Talking About

Design System

Think of a design system as a master blueprint for building a house. Just like how a blueprint defines everything from the layout to the materials used, ensuring every room matches and feels like part of the same house, a design system sets the guidelines for how our digital products should look and feel.

It includes the colors, typography, button styles, and more, making sure that no matter which Attentive product you’re using, it feels familiar and cohesive.

Component Library

Now, imagine if, instead of building every part of the house from scratch, you had pre-built sections like a bathroom unit, a kitchen set, and a living room layout that you could just select and assemble. That’s what a component library is.

It’s a collection of these pre-made building blocks — like buttons, input fields, and navigation menus — that developers can use to build applications faster, without having to start from zero every time.

UI/UX Consistency

UI (User Interface) and UX (User Experience) consistency is like walking through different rooms in a house but feeling a seamless transition from one to the other. The door knobs are the same; the light switches are familiar; the way the doors open doesn’t change unexpectedly.

In digital terms, it means that whether you’re switching between different products or moving through different parts of the same app, everything looks, feels, and behaves in a way that’s predictable and harmonious.

This consistency is key to making products easier and more enjoyable to use, just like how a well-designed house is more comfortable to live in.

What was the need for Stem UI?

In short, here are the things we aimed to achieve:

  • Ensuring UI/UX consistency across all products
  • Significantly boosting developer productivity by reusing components
  • Reduce ramp-up time and make working across pods easier

Let’s dive into the details.

Consistent User Experience

At Attentive, as we kept rolling out more and more products, we started noticing this issue of inconsistent designs and UI/UX across different products.

We, in the frontend tech team, started having collaborative discussions with the design and product teams to brainstorm on the problem and the approach towards its solution.

As our team and products grew, we found that the devs had to implement similar kinds of UI elements over and over in different products, adding a slight variation in visibility and UX each time.

These differences may be slight as you begin building your products, but they compound over time (significantly). The solution came out to be crystal clear — we needed to build our own design system.

Boosting Developer Productivity

We realized we were spending too much time building similar kinds of UI components over and over, like tables, forms, text fields, buttons, etc. In addition, slight variations were introduced in each development phase. This was kind of re-inventing the wheel. A shared component library was the solution which would be built in parallel with the design system.

We also wanted UI development to become an act of composition instead of building from scratch every time, which would also save us considerable development time.

Creating a new UI page should ideally be as easy as stacking together multiple “blocks” (or components) to form the “building” (or the UI).

Reducing the friction in the processes

What’s more, having the same UI components library used in all our products would make it smoother to transition developers across products and teams, with lower ramp up time if they use components from a familiar library in every new product.

We could also cut down on the unnecessary and redundant communication between dev and design teams as new pages could ideally be built with a combination of some pre-existing components.

How did we build it?

Prerequisite: Stem Design System

The essential prerequisite for building the Stem UI library was to have the Stem design system ready. This involved deciding the following:

  • Typography
  • Iconography
  • Colors
  • Root level UI elements like buttons, text fields, etc., and making their designs, including multiple variants (size-wise or use case-wise) and states (like disabled, loading, focus, hover, etc.).
Primary color palette as defined in the designs
Primary color palette as defined in the designs

Choosing the tools

To begin the development, we had to decide whether we would be building our components ground up from scratch or extending the functionality of a pre-existing open-source component library.

However, we were a start-up and did not have enough resources to spend on developing a library ground up and achieving the same level of maturity as existing libraries. So, we chose to go ahead with building a base layer on the top of mui and extending its functionality and styling to suit our design system and product requirements.

In order to build an effective components library, we decided on a few main areas to focus on (and decide the tools for):

(1) Documentation

The component library was meant for usage in products across Attentive. Hence, it was imperative to have our components documented in detail.

However, documentation can be exhausting — which is why we wanted a solution that could allow for easy documentation and provide a platform where the components can be built and tested standalone. We found Storybook to be a perfect fit for this requirement.

(2) Bundling

For effective dependency management and better performance, it is required to combine your assets in what we call as “bundles”. In order to bundle the library source code and prepare the final build, we used Rollup.

(3) Transpiling

Developers want to write code using the latest features of a programming language, but older web browsers or environments might not understand those features as they understand (or “speak”) an older version of JavaScript. Transpiling helps solve this problem by translating the modern code into older, more universally understood code.

We used Babel for transpiling our ES2015+ code to a backwards compatible version of JavaScript (for older browsers to be able to understand our code).

(4) Code Quality

Like any other software codebase, it is equally important to maintain good code quality. For the same, we used Prettier for automated code formatting and ESLint for lining.

(5) Visual Testing

We also needed a way to visually test and review our components — how they look and how they work in a deployed environment. Chromatic is a well-known solution for the same — we deployed our storybook on this platform and rolled out the link as a reference for usage by all teams. The design team tests out the components developed using the same.

(6) Unit Testing

It is important to test our components well before shipping them off for usage in the different products. Hence, we decided to write unit tests for our components, which would test the components in all known scenarios and cases, using React Testing Library.

Development Kickstart

After deciding on the tooling, we started building out our components as per the designs and guidelines provided by the design team.

Any component exported from the library can be imported into an app as follows:

import { Component } from '@attentive-platform/stem-ui';

const Parent = () => {
return (
<div>
<Component {…componentProps} />
</div>
);
};

Theme

We knew we would need to support more than one coloring theme in the library, so we decided to export a StyleProvider for the same, where the user can provide which theme to use for rendering the components. Client app’s App component has to be wrapped in this provider for the styling to get reflected.

Documentation

“Good documentation is an easy sell”

To increase the library’s adoption, it was highly important to document how the components are supposed to be used, the props that they accept, what variants and states are supported, etc.

We employed Storybook for the same and organized the docs in the form of special React components called “stories”, each story documenting a specific variant/state of some library component and the props that the component supports with a detailed description.

Storybook shows the different states & variants of available components
All props available on a component with description

Publishing the library

We published the library internally on the GitHub registry for npm packages to allow for easy usage and installation in all repositories throughout Attentive.

High-level Frontend Architecture after introducing Stem UI

Navigating through Challenges

Change in Thinking

Going from creating unique UI components according to the specific use case to using shared components required motivation to do so and clear communication and documentation. Gaining support from both product and engineering teams was essential for a better user experience and brand integrity.

Balancing between flexibility and consistency

While developing the components, it becomes highly important to decide the extent to which we can allow the component to be customized (in terms of styling, functionality, etc.) for specific product use cases.

Too low customizability can make the component potentially unfit for some product requirements and can worsen dev experience, while too high customizability will hamper the visual and functional consistency between different usages of the component.

In order to solve this kind of problem, we actively coordinated with the design and product teams to know all possible and “allowed” variants of a component and gave users enough flexibility to choose from any one of them. However we do not allow customizing the style of the component other than a few exceptional styling attributes.

Versioning

The stem UI team releases updates quite frequently, and while most of the changes are backward compatible, few of them might not be. We hence felt the need to introduce versioning for the library so that devs can integrate the version with breaking changes as and when they find bandwidth and to avoid inter-dependence between teams.

We are using Semantic Versioning for the same.

Next steps in our Roadmap

While we’ve made significant strides in addressing our initial challenges, the journey to enhancing Stem UI into a mature library is ongoing. Our focus remains on bolstering developer support and refining synchronization between design and development for a smoother experience.

Better Iconography

Currently, the design team provides us with the SVGs for our icons, which we add to our codebase and export as reusable icon components.

However, whenever there’s a requirement for a new icon, a new SVG has to be taken from the design team and exported from the library, making the process of introducing new icons a bit slow and tedious.

As a solution, we plan to employ an icons library which fits the best with our requirements, and provides an exhaustive set of icons we will ever need in the software.

Support for Internationalization

Internationalization (often abbreviated i18n) is an important step in building a successful product that serves users from multiple countries and continents. It is, hence, imperative that our component library is aligned with the same and provides support for it.

Formalize Design Tokens

Design tokens from a tech perspective can be called constants that can point to different things like font sizes, colors, weights, etc.

It is much better to use well-defined design tokens than using hardcoded values for your CSS properties, as even if a token’s value is changed over time, its name stays the same often, and you need to make the change in only one file that defines the tokens.

Design tokens also allow for easier mapping of styling properties from design to development. While we have design tokens implemented today, there are a few changes that need to be made:

  • Actively using the predefined design tokens (that we use in development) in our UI designs as well instead of values of the styling properties.
  • Associating some design tokens with their purpose, for example, a background color should not be called “white” or “gray” instead, it should be called “light” or “dark”, as eventually our perception of a light background might change and we might want to shift the token’s value from #FFF to some other color that also looks light.

Reusable layouts

We have already identified and developed the UI components that we will need in more than 90% of use cases, we now also need to work on building reusable layouts.

I have always been of the perspective that layouts should be built somewhat later in the journey of a component library, especially when the exhaustive set of layouts that you will be needing is not defined in the beginning and you anticipate more types of layouts coming up as the products grow and scale. So this would be one of the next steps in our ladder.

Error Monitoring

It is equally important to monitor and track the errors being reported in the products, whether they be cases of component integration not done correctly, or they be cases of bugs being present in some components.

In the former case, it would prompt us to re-evaluate whether the documentation and development environment are useful enough for dev teams, and in the latter case, we will obviously fix the issue in the component as soon as possible before it starts affecting other products.

Have you faced challenges with UI consistency or developer productivity? How do you see Stem UI fitting into your workflow? Share your thoughts in the comments below, and let’s start a conversation on shaping the future of frontend development together.

Thank you for reading! Stay tuned for more insightful content on optimizing tech @ Attentive

PS: We are hiring across roles in the tech team. Visit our careers page for more details.

--

--

Ashutosh Rahi
Attentive.ai Engineering

I am a Software Engineer, currently working at Attentive, Noida 🇮🇳 and I love JavaScript.