Design System Infrastructure
Building an Enterprise Design System, Part 2
You spent weeks preparing a presentation convincing your boss that a design system will have a huge ROI and increase product development efficiency. You had a big meeting with the Head of Design, the CTO, and CEO and explained how every hour spent saves 5 hours of isolated teams rebuilding the same components over and over again. There were cheers and applause, maybe some high fives, and an all-important, “This sounds great. Let’s do it.”
Then the next question you get is, “So how do we do this?” The hard answer to that question is that every company is unique. Your design system will look completely different than Polaris or Carbon or Canvas because they have different problems, tools, and needs. Regardless of what your system ends up looking like, they all come from a similar starting point. First, an audit of what your company is currently doing, and second, a plan for how the design system will exist. That’s exactly where we kicked off this new project. Here’s an in-depth look at all our considerations and thought processes.
Design Tool Audit
First we needed to take a look at the tools the team is using. One of the many benefits of a design system is improving the efficiency of your design team, letting them have more time to spend solving harder experience issues. So we need to assess what tools and workflows are already in use so that we can optimize them. We need a baseline to optimize from and a jumping off point to teach designers how the system will work.
The 10 person product design team at ServiceTitan had already been using Sketch for design work and Zeplin for doing handoffs. That seems pretty standard for a product design team. We additionally looked at some other design tools like Figma, Framer, Adobe XD, & Axure.
They also just transitioned projects over to Abstract to keep everything in a shared space. Abstract is such a cool tool. It allows us to have a tight syncing with Sketch’s native Libraries feature. Among other things, it supports version controlled Sketch Symbol Libraries which will let designers opt-in to new versions on a per-project basis.
We decided that the best workflow is the one the team is already using. All of the tools we evaluated are doing really interesting things in the space but more or less didn’t have enough features to offset the cost of making designers learn new tools. Adopting the Sketch + Abstract workflow let us have a much easier jumpstart into the teams existing workflow. All it took was introducing a new library and showing the team how to add it to their projects.
On the development side, the engineering team was transitioning our app over to React+TypeScript. Ok, that’s pretty reasonable. I already had some React experience but doing an initial crawl through the TypeScript docs, it more or less seemed pretty easy to add on to React. It just adds stricter typings for props.
There was also already a rough plan to use
semantic-ui-react as a base for the new design system. The codebase was using a combination of Bootstrap, Semantic, and Material Design in different sections of our giant enterprise app. After interviewing the engineering team, we discovered the team was having trouble solving conflicts between those libraries.
On one hand, using a framework will save time up front. That’s good. On the other hand, you’re locked into that system. After doing a component audit, you can easily evaluate if a framework will be able to support all your needs. In all the frameworks we looked at, they all simultaneously have more than we need in some ways, and less in others. There was no perfect solution here. After a lot of discussions, we changed our plan away from using
semanitc-ui-react (thank the stars).
We decided that no single framework will solve our problems but that didn’t mean we couldn’t leverage some of the work already done to help us start. If we import and reskin components smartly, we can mix and match the parts of each framework we need with our own custom components where it makes sense. That means we could use form components from Semantic UI and icons from Material Design for instance.
Source of Truth
So now that we know how the design parts of the system will work, and how the code parts of the system will work, we need to figure out how to tie it all together. But to do that we need to answer some questions.
- Where do edits happen to components?
- Should design files be referencing code or should code be referencing design files?
- Where do usage guidelines live?
There are several ways we could go about this so we evaluated all these questions to put into our plan.
Design as the Source of Truth
Design files as the source of truth seems to be the path that most design teams want. They’re in Sketch, Figma, or Adobe XD anyway, we could have the components and documentation live there. That allows for very quick edits to components if needed.
That opens up the possibility that if component change isn’t communicated, the interface might not actually represent what is in our design files. A component change might be interpreted as a new component and be made differently, making the UI more fragmented. It also makes usage documentation only available to coworkers who have access to design tools.
Code as the Source of Truth
Using code as the source of truth is what engineering teams expect. Code is ultimately what the users will see, not pixel-perfect design files, so it makes sense to that code is where all decisions should be made.
The same questions get raised by this method as with design files being the source of truth. If design files are just representation of code, if a component changes visuals or behavior because of a bug fix or accessibility improvement, how does that change get communicated back to the design team? Also having documentation in code is in a lot of ways out of reach for most of the company.
Two Sources of Truth
Theoretically if you have a very organized and in-sync team, you could make both design files and code the source of truth. It takes a lot of communication about what is happening on both sides and people dedicated to keeping them in sync. It’s definitely doable, but incredibly time consuming once the project gets off the ground.
We really spent a lot of time thinking about the tradeoffs of each of these. There are different scaling and maintenance issues involved in each. We went back to the drawing board and thought about what the ideal solution would be for a hypothetically perfect system.
There are new tools coming out every day for design systems for improving teams workflows. If we had a tool that could convert design files to real and good code, or convert code to design files, this could help that maintenance and communication issue. One side receives the generated version of the design system from the other. This is the answer we were looking for but could we make it?
We explored tools like react-sketchapp and html-sketchapp and found that we were able to make a pretty functional prototype of a code-to-sketch workflow. This would allow us to have our code be the source of truth, since that’s what our users will see after all, and also our design team would get perfect 1-to-1 representations of all of our components in their tool of choice. Yes, we would still need to check what is being generated to make sure it looks good, but would be a lot less work than trying to double check how every single change affects the visuals of our components.
We decided to pursue this idea. We decided that for the long-term growth of the project, it makes sense to invest time up front to make sure we can solve any out-of-sync issues that could arise. This does mean that designers have a little more friction getting design changes into the system, but it ensures when a change is made, it is represented in our design files and to our users.
This was such a major hurdle to solve but isn’t everything. We still need to figure out where our usage guidelines live. Since we’re using both code and design tools for our system where is the best place for those to live?
There are many tools out there for documenting design systems but many of them are stand-alone apps. Since our source of truth is code, it makes sense to have usage guidelines live with that as well. When we make a new component variation, we want to update the guidelines for how to use it at the same time. Everything should be in sync at all times.
We found a tool called Catalog that is a static site generator for documenting design systems. It supports a lot of really useful features (called specimens) to allow for live code examples, responsive viewports, simple color and type displays, and a bunch of other cool features. Each page is written as Markdown so it’s easy to add new sections and examples with the most minimal amount of code knowledge.
In about a day we had a pretty good first-pass at an in-sync component documentation tool that could be published to our internal network. This would allow not only designers, but also engineers and product managers the ability to see how components should be used to provide the best experience for our customers.
So let’s outline what all our pieces are again.
- A React component library that is a combination of custom components, and components pulled from third-party frameworks
- Our html-sketchapp tool to convert those React components to symbols in Sketch. That tool generates a file that can be imported into Sketch using a custom plugin. We can then check that file into Abstract which distributes it to our design team as a shared library.
- Our Catalog documentation site which is also using those React components in live examples to show best-practices.
│ ├── component-1.md
│ ├── component-2.md
│ ├── component-3.md
│ └── component-4.md
│ ├── component-1.tsx
│ └── component-1.tsx
│ └── sketch-library.js
│ └── component-4.js
We made a new repository in GitHub and used a code tool called Lerna to keep each of these separate projects working in sync with each other.
Another perk of organizing our system this way is that if we end up replacing a third-party, we can pull that out easily because it’s completely separate. If our design team wants to move away from Sketch, we can pull that part out and figure out what to replace it with. If we want to jump to a different documentation tool, we can. Everything is modular and always in sync.
I’ll go into it in more detail in another blog post, but with this infrastructure we have set up, releasing a new version of the design system is as simple as publishing it to NPM, checking in the symbols to Abstract, and publishing the documentation site. The only manual task we have to do is checking into Abstract. Otherwise it’s all pretty automated.
Design Systems Are Hard
I can’t explain enough about how much thought goes into making a design system. The biggest thing I learned was that, just like any experience problem, you must communicate a lot of with your customers and really understand their needs. You can only start solving problems once you uncover the needs and it might not look how you or they originally thought.
Also keep in mind that tools change frequently, the needs of your company changes and grows, the team working on it has a breakthrough (or breakdown…) and changes the workflow. The best way forward is to stay nimble — make it as modular as you can. You can’t be too protective of what you’ve done because 6 months from now, you might need to tear it all down for some new hotness anyway. Every step forward is a success and helps you plot your course to your next success.
Iterations Since Launch
In the year since we released v1.0 of Anvil, we have iterated on our flow quite a bit. The biggest change we made was adding a tool called Storybook. This tool allows us to have a real development environment as well as better documentation of the code for our engineering team. We also iterated on how our icons were being generated so that we can add new custom-designed icons into our icon set.
We also added a new project to generate tokens. Tokens are small variables or design decisions that can be used across multiple components to keep them consistent. As an example, you can have a token for border radius that then is used by your Buttons, Form Fields, and Cards. If you update that border radius token, all of those components will automatically update with your new setting. This also allows our engineering team to use the same tokens in custom non-system components so that they still align with the system principles.