Aptible’s UX Engineering team has recently launched version 1.0 of our in-house design system, Arrow Design System (Arrow DS for short). In this post, I’m going to walk you through some of the frameworks and tools we use and show off one of our more complicated components.
At Aptible, we place a lot of emphasis on creating high-quality and consistent experiences for our users. Each page, dropdown, tooltip, and modal should feel like they are part of a cohesive system. Building with a design system helps our users recognize and understand patterns throughout our products. Having a consistent, high-quality front-end experience makes our software easier to use and gives users more reason to trust our products.
We also want to be able to build and learn quickly. A design system gives engineers the building blocks they need to assemble pages and features at a quick pace, taking some of the more repeatable, tedious UI work out of their hands.
The reason for building Arrow as a standalone repository is that it creates a definitive line of separation between the product (owned by Product Managers and Engineering) and the design system (owned by Product Designers and UX Engineers). Product Managers and Engineering primarily control the direction of our user-facing product while Product Designers and UX Engineers control the direction of Arrow. By treating the design system as a separate, standalone service, we are able to properly invest in it independently of product feature requests.
Side note: If you aren’t sure what a UX Engineer is, check out our blog post on that.
Arrow DS is a big library with a lot of moving parts. Aptible’s UX Engineering team leveraged many different frameworks, tools, and libraries to construct version 1.0. Here are a few of the most important.
Stack Overflow’s 2020 Developer Survey listed React.js as the second most popular web framework. It has widespread adoption, is great for single-page applications, and has an excellent ecosystem of libraries and other tooling to help us build our products.
Tailwind is a CSS framework that comes with lots of utility classes that can be leveraged in Arrow’s CSS (using the `@apply` directive with PostCSS) or used directly on elements. These utility classes are generated from a configuration file within Arrow that we can customize. For example, the class `m-2` will add a margin level 2 (which we currently have defined as `8px`) to the element. Most styling in our front-end applications can be done with these utility classes, resulting in very little actual CSS being written outside of Arrow.
Storybook is an open-source tool for developing components outside of any application. It also serves as a documentation platform with the Storybook Docs Addon. Storybook is a dev dependency of our core `arrow-ds` repository and is automatically published with every new release. Check out our published Arrow DS Storybook.
Semantic Release is a library we use to fully automate our Arrow DS releases. Each commit to `arrow-ds` is labeled with the type of changes contained within. Some examples are “fix” for a bug fix, “feat” for a new feature, and “chore” for changes related to the build process. A full list of the types of commits can be found within Angular’s Commit Message Guidelines (which Semantic Release uses by default). When changes are merged to the master branch, Semantic Release looks at what types of changes are involved, determines what type of release to make (major, minor, patch, if any at all), and puts the new version of our package onto npm.
Yalc is a tool that gives UX Engineers the ability to link the arrow-ds package to another repository locally. This is helpful for testing new features before publishing a new version of the `arrow-ds` library.
The Drawer component is one of our most complex components. It slides a menu of filters in and out of the viewport from the edge of the page while pushing over existing page content (usually a Table with as many as 250 rows), and it has to do this as close to 60 frames per second as possible. Before digging into some of the details, here is a demo.
The biggest challenge with this component was achieving high animation performance. As you can see from the demo, there’s a lot going on as the Drawer slides in from the left side of the page. The TopBar component above the Table shrinks to accommodate the Drawer, but the Table does not. Animating the width of a complex component like a Table isn’t possible without significant performance issues. This is because the browser is having to repaint the entire Table with each animation frame, and the machine just can’t keep up, leading to low animation frames per second (FPS).
To solve this problem, we first lock the width of the Table before the animation starts. This prevents the browser from constantly repainting the Table as it changes width. Then we use the GPU-accelerated CSS `transform` property to move the table. When using `transform`, the browser takes a snapshot of the element and hands it off to the machine’s GPU for animation (a task that graphics processors are specifically made for), resulting in much higher animation FPS.
For more information on CSS GPU animation, check out CSS GPU Animation: Doing It Right from Smashing Magazine. The article is a few years old at this point, but it is still helpful in illustrating how CSS leverages your computer’s GPU.
While we’re excited about Arrow’s 1.0 release, we know there are still a lot of ways we can improve our design system. Here are a few things we’d like to do soon:
Conduct an accessibility audit
— Accessibility certainly hasn’t been ignored throughout Arrow’s development, but we would like to be more intentional about building Arrow in a way that promotes accessibility going forward.
— Writing high-quality documentation takes time and effort. UX Engineering’s focus has been feature-centric in recent months, but we’d like to shift that focus more towards documentation.
Make design tokens searchable —
Design “tokens” include all of the values we supply to our Tailwind configurations (e.g. “margin”, “width”, “font-family”). Having these defined is great, but aren’t very discoverable given our current setup. We’d like to make it easier for developers to find these values in our documentation. As a bonus, it might be nice to have these autocomplete in code editors.
— Tables are used extensively throughout Comply. We’ve learned a ton since our first iteration and are compiling a wish list for a refactored Table component that will boost front-end performance and improve the developer experience.