Building and Leveraging our Design System at Juniper Square

Juniper Square
Juniper Square Engineering
5 min readOct 5, 2021

We’ve invested significant time into building out a robust design system in an effort to speed up development and the quality of our products.

A unified design system — shared across design, product, and engineering — creates alignment that ultimately leads to better and quicker development.

Inclusive of defining a shared language and clear documentation, the design system is ultimately manifested in UI components that allow for a standard look and feel across our products.

With a large and growing library of over 50 React components, our design system has become a critical piece of infrastructure. Given the broad impact, we find it best that the design system is developed and maintained as a shared, organized responsibility amongst frontend engineers across all engineering teams. This structure empowers all engineers to not only provide feedback, but also contribute to the tools we all use everyday.

Design System Process

As our engineering team has continued to grow and scale, we quickly recognized some points of friction that made their way into our feature development process. We noticed an opportunity to streamline collaboration amongst product, design, software engineers, and QA engineers. We set out to improve the end-to-end development process by creating a distributed design system team.

The distributed design system team isn’t a team of fully dedicated engineers; rather, it is an organized group of contributors across all teams within the engineering organization with a focus on developing and maintaining our design system tools.

We find this structure is well suited for our needs. Creating and maintaining design system tools should be the responsibility of all front-end engineers. This distributed approach also allows us to leverage all of our engineering talent and enables all engineers to be familiar with the ins & outs of the tools we use everyday.

The distributed team is led by a representative from each function — design, software engineering, and QA. In an effort to organize and track the work, the team meets once a week for standup to discuss progress on the assigned tasks and to distribute tasks accordingly.

Defining a Design System Component

Determining a design system component is not always clear cut. While we develop many reusable components, not all belong in the design system.

Generally speaking, these are the guidelines we follow for deciding if a reusable component should be defined as a design system component:

  • The component is easily reusable across the application
  • The component is not domain-specific and isn’t opinionated as to the data it receives from props
  • The component has simple and single-focused functionality

Contribution Guidelines

Our best practices for the design system contributions align closely with our best practices for broader frontend development. Due to the generic nature of reusable design system components, we have, however, adopted slight differences to improve code maintainability. We’ll highlight a few key differences below.

Documenting the Props API

While we always aim to name props such that they are easily understood by any engineer, there are situations where additional context is needed to better understand how a prop affects the component.

To avoid confusion, we add documentation to all props that explain how a particular prop affects the component. This practice is not only helpful to engineers who use the component, but also to those who may make changes to the component in the future.

Example:

export interface Props {
/**
* Adjusts the Badge's color scheme.
* @default neutral
*/
variant?: BadgeVariant;
/**
* Adjusts the size of the font.
* @default medium
*/
size?: BadgeSize;
/**
* Makes a bold-colored variant of the Badge.
*/
isSaturated?: boolean;
}

Test Coverage

Adding tests is a minimum requirement in all frontend development, including any development on the design system. We use Cypress for end-to-end testing and React Testing Library for snapshot and functionality tests.

While feature components may be built for a particular use case, design system components are built for generic usage and are therefore broadly used, in many different ways.

Because of this, design system components require extensive tests that cover the entirety of the props API. This robust testing ensures that any alteration to a design system component does not result in any unintentional functionality or visual breakages.

Example:

import React from "react";
import { Icon, IconType } from "ds/core/Icon";
import { render, screen, userEvent } from "@testing-library/react";
interface Props {
children: string;
icon?: IconType;
onClick: () => void;
}
const Card = ({ children, icon, onClick }: Props) => (
<div onClick={onClick} data-testid="card">
{icon && <Icon data-testid="card-icon" icon={icon} />}
{children}
</div>
);
// describe string is the component's path
describe("ds/core/Card", () => {
it("renders children", () => {
const { container } = render(
<Card onClick={() => {}}>
Hello
</Card>
);
expect(screen.getByText("Hello")).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it("displays icon", () => {
const { container } = render(
<Card icon="bars" onClick={() => {}}>
Hello
</Card>
);
expect(screen.getByTestId("card-icon")).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
it("fires onClick", () => {
const onClick = jest.fn(() => {});
render(<Card onClick={onClick}>Hello</Card>);
userEvent.click(screen.getByTestId("card"));
expect(onClick).toHaveBeenCalled();
});
});

Using Union Types for Component Variations

Design system components may have different style variations, key to extending a component’s reusability. Variations are often defined by a combination of color and background color style alterations.

An example of this is our Button component. It has 5 different color “versions” that are all useful in the different situations we encounter in feature development. Since these are CSS-only style alterations, we’ve found the most scalable, maintainable, and usable way to handle this is a `variant` union prop like below.

type ButtonVariant = "neutral" | "primary" | "success" | "warning" | "danger";export interface Props {
/**
* Adjusts the Badge's color scheme.
* @default neutral
*/
variant?: BadgeVariant;
...
}

This approach allows us to extend the Button component in an easy-to-use, highly reusable way.

Component Usage Guidelines

Wherever possible, using design system components is always highly encouraged. With over 50 React components available for use, there are many situations where a component in need already exists in the design system.

To allow us to successfully cascade updates to design system components and create a consistent user experience across the application, we want to steer away from adding custom css styles to these components. Adding custom css styles compromises our ability to have a consistent look and feel from one implementation to the next. It also makes it challenging to successfully upgrade these components from a single location.

While we tend to steer away from adding custom styles to design system components in feature development, we skew towards empowering teams. There may be valid reasons for exceptions to the rule. To that end and to enable the ultimate flexibility, our design system components can be extended with additional class names and styles in implementation. This may be used for custom layouts or other spacing needs.

Wrapping Up

We’ve made great strides to improve our development and maintenance processes around our design system.

We’ve invested significant time and energy to establish our best practices, facilitating contributions and enabling engineers to come together as a distributed design system team.

While we’re in a great position, there’s always room for improvement and still plenty to do.

We’re always looking for talented and highly motivated engineers to join our Engineering team here at Juniper Square, so if you’re interested take a look at our open roles.

--

--