Here at Mode, we’ve recently started building a design system to support our organization. Design systems have been a hot topic of discussion for the past few years. They’ve proved their value many times over with countless advocates shouting their praises from the rooftops. However, despite their high value proposition, they aren’t as ubiquitously implemented as one might expect. After all, Mode is a relatively mature product having been around for over six years and we didn’t have a comprehensive design system integration in place yet.
As we began this process, our biggest challenge quickly became clear. It also became clear that this issue was also probably why it’s taken a while to get started in the first place. It wasn’t hard to determine what a successful design system looks like. There are countless resources and open projects that demonstrate well thought out implementations. Our biggest challenge was determining how a design system could be successfully integrated into our organization.
It turns out that identifying this distinction was quite important to our discovery process specifically because Mode is an established product with years of development and releases behind it. This difference also likely contributes to why other established teams might find themselves feeling a bit behind in terms of how a design system fits into their workflow.
Product maturity can create a gap in level of effort when it comes to implementing a design system. The difference between “starting fresh” on a project versus “adding to” an existing ecosystem can be vast. Additionally, the most interesting discussions, articles, and resources tend to talk about the most complete and integrated solutions. This is great because there’s a lot of exciting tools out there right now that support building and maintaining excellent, completely integrated design systems! However, the road to get from no design system to model design system can feel very ambiguous, almost like an unknown black box exists between you and that gold star design system of your dreams.
The goal of this article is to demonstrate what we learned at Mode while navigating the process of architecting a design system within our existing ecosystem, provide a path through this black box for other organizations finding themselves in a similar situation, and demonstrate that design systems are indeed for everyone.
Fields of green and brown
First, let’s define the problem and demonstrate exactly how the cost of implementing a design system across a team, product, or suite of products can vary drastically based on the maturity of the ecosystem. For example, let’s create an imaginary new company called Greenfield Corp. They are a new organization starting up today in the current software development ecosystem and have the luxury of making every technology decision from the ground up. They can easily leverage concepts and ideas that many people and teams have spent years iterating over. Additionally, many new tools and technologies are at their disposal to support design systems in the front-end software space. Greenfield Corp is free to make some of the following architectural decisions even before any code is written:
-
A single framework will be used across all applications and platforms, like Angular or React
-
Multiple applications existing across the product space will all consume the same services, APIs, and resources
-
One of those resources will be a component library published as an installable package and our source of UI truth
-
This component library is visualized via Storybook, a foundational piece of our design system
-
A team of designers and front-end developers will be defined to maintain this internal product
-
We’ll build the foundational language of the design system collaboratively across development and design teams
-
Process and governance guidelines will be created to model the release of the design system
-
We’ll implement publishing workflow and testing requirements
The list can go on, but it’s clear that a design system fully integrated into an organization touches nearly every part of the team and application. As the architecture choices in this list get more specific, many of them depend on previous decisions in the list assumed as fact. For Greenfield Corp, this isn’t a problem at all as they can make all of their technology decisions without any external restrictions and can pull directly from many well established patterns used in the community. Greenfield Corp is really excited about their prospects and throws a design system party with an atomically designed cake!
Now let’s look at a second imaginary company, Brownfield Corp. Let’s say they started up about six years ago. At that time, React was still the new kid on the block. AngularJS, BackboneJS, and EmberJS were some of the more mature frameworks at the time. CSS styleguides were starting to become popular, but robust design system patterns were at their infancy. Tools like Bootstrap were widely used. Over six years of growth, development, refactors, and new features, Brownfield Corp now wants to integrate a design system into their workflow. They may have to navigate some of the following requirements, considerations, and restrictions when determining how they can move forward:
-
There are multiple different technologies, languages, and frameworks used across the project space
-
Framework lock-in may tie a project to a non-ideal technology
-
Dependency on outdated theme frameworks can make introducing new theming patterns challenging and time consuming
-
Portions of the project are “legacy” and this older code may require additional support
-
Ongoing migration efforts may be in process to improve legacy portions of the codebase
-
There are existing human workflow patterns already in place within the organization around cross team collaboration
These are just a few common concerns an established organization might be forced to account for when planning how a design system fits into the equation. Any one of these could easily derail the entire cascade of ideal workflow decisions Greenfield Corp was able to make.
As projects grow they develop technical debt, teams get larger, the number of stakeholders increases, and the complexity of the ecosystem generally compounds. Brownfield Corp now wants to integrate a design system into their organization, but they can’t make the same assumptions Greenfield Corp was able to make. Brownfield Corp is becoming overwhelmed at the idea of navigating this landscape of requirements and restrictions. Integrating the ideal design system can quickly begin to feel like a completely overwhelming or wildly resource intensive problem to solve. It’s easy to see how Brownfield Corp may feel like this transition within their project context may not even feel approachable at all!
But first, why?
Before we tackle different ways Brownfield Corp may be able to approach this challenge, let’s break down design systems a bit first.
There are many different opinions and methodologies that exist around design systems and component based design practices. However, there are a few sentiments that you often universally hear whenever anyone talks about design systems:
-
A design system contains a collection of reusable, composable components
-
A design system is the single source of truth (or definition) for UI patterns
-
A design system is “living” (meaning it both exists as code and has development life cycles as opposed to static design mocks)
These descriptions are all relevant and accurate, but they’re all descriptive of what the resulting design system looks like. These kinds of definitions are helpful and important checkpoints when trying to architect an implementation that works well and is easy to use. However, they don’t necessarily make it clear why design systems are necessary in the first place or the issues they solve. When the landscape becomes more complex, it’s worth anchoring back to the “why” to help determine a path forward.
So, why are design systems necessary? And, why have they blown up discussions in the past four or five years?
Let’s start with the obvious. The size and number of web based projects have exploded over the past five to ten years. With that rise also came the steady maturation of the tools and frameworks front-end teams used. Now that large portions of web developers started to build things in structurally similar ways, people were also starting to run into similar problems. Working with the unique challenges the uncertain canvas of the browser brings, coping with differences across browsers versions and devices, and simply trying to ensure consistent behavior became more and more challenging. Tools like Normalize CSS, Twitter Bootstrap, and ZURB Foundation hit the scene and were gaining lots of traction.
Initially, these were mostly seen as tools for developers to work around the issues that were present in the ecosystem. They helped solve for things generally seen as failings of CSS as a language and the web as a platform: handling legacy versions of Internet Explorer, implementing cross browser and device consistency, and working through the clunky parts of CSS. As browser support for CSS features improved, preprocessors became the norm, and resources like Bootstrap and Foundation matured, it not only became clear how valuable abstracting UI elements could be, but now there were also some clear patterns for how to go about doing it.
At this point, component based design grew in popularity and projects like Pattern Lab and Fractal started to pop up as well as many CSS based organization syntaxes and frameworks. All of these tools generally progressed in a similar direction and came to a similar conclusion: these are no longer just tools for developer abstractions, they’re tools to build a cross-functional, collaborative language for designers and developers to share.
This shift in perspective can be broken down into three main reasons why we use design systems.
Communication and (intentional) cross team collaboration
Successfully building complex UIs requires collaboration across the developer and designer functional teams. Without a design system, collaboration may exist across functional teams, but, often in ad hoc and non-reusable ways. A design system as a project within an organization forces this collaboration to be intentional, explicit, and consistent. This structure and purpose supports unified goals and a shared language between the teams.
Providing a contract
At its core, one of the main high level needs a design system fills is that it provides an explicit contract. This contract describes what behavior is expected from the UI and establishes where the pattern abstractions are defined. More importantly, it’s a contract that engineers and designers both must adhere to when creating mocks and building functionality. Most design systems can’t completely prevent people from breaking the rules, so the buy in and fidelity to this contract at an individual level is necessary for the design system to provide value.
Regulation and governance
As a design system is a contract, it follows that the regulation and governance of that contract is important. Defining when, why, and how that contract changes is necessary to build trust within your users (developers and designers using the design system) and consistency within the system.
There are definitely many other reasons why design systems are used, but these three are core contributors and describe the major organizational changes that will take place as a team begins to use a design system within their workflow. Each item can also clearly support those common descriptions of what design systems look like that we previously touched on as well.
Managing the Conflict
***Spoiler alert!!! ***As we return to our fictional Brownfield Corp, I need to reveal that it was not so fictionally based on some of the challenges we have been facing with our design system project at Mode. The product was originally built in AngularJS with a small team over six years ago. Since then, it has pivoted to navigate product market fit, evolved new features, and grew significantly both in terms of codebase and team size. During that time, there were many refactors as patterns and best practices changed. There’s also a current effort in place working to migrate everything over to Angular (away from AngularJS). Naturally, there are some portions of the codebase that follow legacy patterns and others that follow newer patterns in a new framework. In addition to this technical development, both the engineering and design teams have grown significantly and have established workflows for collaboration.
This is a story that may resonate strongly with organizations that have developed over similar timelines. So how does all of this help with our original problem of determining how to get started with a design system? Focusing first on the “why” of the design system allows us to creates the foundation that will lead to determining what it will look like.
This process will require a lot of discussions between team members, sharing of ideas, and iterating over proofs of concept. The three “why”s above are all relating to how the design system is positioned and its role in the organization, so it follows that these conversations should lead the effort. When we worked through this at Mode, there were essentially two main parts of this process for our team:
-
Define the design system requirements
-
Determine the level of pattern abstraction
Plan the requirements
When a design system is being added to a complex ecosystem, explicitly understanding the list of requirements and restraints it will have is integral. Create a list of all primary and edge cases for the design system as well as how it will fit within the current context. Nothing should be assumed or left unsaid. The following would be some of the types of questions that might need to be addressed:
-
What types of codebases will it need to serve?
-
How will it be included in a project?
-
Is there legacy code? Does it need to be supported?
-
How will developers and designers be able to know what’s available?
-
What does collaboration between developers and designers look like?
-
How will it be maintained?
Each of these questions can create large conversations of their own. The purpose is to tease out all of the details of each question, but not necessarily determine what the perfect answer is yet. Once all of the details are gathered about the ideal requirements scenario, take a second pass and determine if meeting each requirement is reasonable and necessary. Will the level of effort needed to support each requirement actually be worth it? Can some requirements be cut, improving the initial implementation of the design system and making it easier to maintain?
For us at Mode, this process lasted a few months and was probably one of the most important steps of developing the foundation for our design system. This gave us time to discuss and iterate over ideas as a team. We defined the group of engineers and designers that would be working to develop the design system and all members were able to build expertise and context. We used simple proofs of concept to demonstrate patterns and discuss differences between approaches with concrete examples. These working discussions would happen mainly with the smaller team responsible for execution, but then would be shared out to the front-end team as a whole to gain buy in and give anyone an opportunity to raise red flags or voice concerns.
The following were some of the outputs that came from this process in our team:
-
We defined the scope of the design system to be only our product (excluding our marketing projects)
-
We prioritized the importance of clear documentation and a self serve model for engineering consumption
-
Design and engineering discussed some specific implementation ideas around simple patterns like colors, font styles, and buttons to get an understanding of what future collaboration will look like
-
Design began refactoring their tools to better support component based patterns within their build process to ensure consistency throughout the whole story
-
We ultimately decided to focus on new portions of the product using Angular instead of legacy support for portions of the codebase using AngularJS
-
We built a proof of concept repository that roughly told the entire developer story we were looking to implement and how it would be developed
These outputs came from multiple rounds of discussion with the design system team as well as presentations to the front-end team as a whole. Limiting the scope of the design system to the product and focusing on Angular support were two key trade offs that we decided to make as a team to significantly decrease the complexity of the project. Creating a lightweight proof of concept project helped clearly demonstrate the pros and cons of these decisions so the entire front-end team was able to understand the compromises and ultimately give buy in.
This kind of discussion will always be contextually based on an individual organization’s needs, but these were some of the key elements of our discovery process that helped pave the road forward in an explicit way. Defining the of cost and benefit of each design system requirement will help to inform the decisions that need to be made to support your organization in the best way.
Determine the level of pattern abstraction
The second step is really one of the items that should be considered when reviewing requirements described above, but it’s so important that it needs to be broken down into its own section.
There are many ways a design system can be implemented, but the key factor that shapes it is the level of pattern abstraction. A more straight forward way to think about this is to determine what exactly is shared across projects. Generally, the lower the level of abstraction, the more it’s tied to the specific system. It will be easier to use, but less flexible. The higher the level of abstraction, the more general it is. It will take more effort to incorporate into a system, but it will be more flexible. For instance, here are some example design system implementation patterns from lowest level to highest level of abstraction:
-
A component library built in a specific JavaScript framework
-
A custom Sass library providing functions, mixins, and variables
-
A static CSS stylesheet that can be added to projects
-
A blueprint component library providing direction built in a framework neutral tool like Pattern Lab or Fractal (or even plain HTML)
Each of the above options has a different relationship with the system it will be used in. Since the lower the level of abstraction the easier it is to incorporate into a system, you’d ideally want to choose the lowest level of abstraction that fits your support requirements. Let’s dig a little deeper into each of the patterns above and when they might be used.
Framework based component library
This approach creates the development of a framework specific component library. It defines an importable selection of presentational and functional components that can be published as a package and imported by other projects using that framework.
Abstraction: This pattern is the lowest level abstraction as it ties directly into the specific JavaScript framework being used. When to use it: If the technology decisions in your organization are consolidated around a single framework, this is generally the best choice. It’s very straight forward for developers to use and since it’s very tightly coupled with your system, it allows for a great deal of control around exactly what functionality is provided. Support: This pattern requires all applications that leverage the design system to be built using the same JavaScript framework (React or Angular for example). Drawbacks: None really. If your design system’s technology support requirements allow for this, it should probably be the approach you take. It should be noted that any project which doesn’t leverage the framework this library is built in will not have access to these patterns. Examples: There are many examples of this approach, as it’s the most widely discussed. Carbon Design System and Airbnb both demonstrate this approach leveraging Storybook, a tool for visualization and exploring a component library. There are many other examples on the Storybook examples page as well.
Sass Framework
A Sass framework can be a flexible and powerful tool. You can provide library of custom functions, mixins, and variables allowing your end user to flexibly implement UI patterns. This framework can be installed as a package exposing the functions, mixins, and variables to your project.
Abstraction: This pattern is one level further removed from the system and has no opinions on framework, JavaScript or otherwise, but is instead tied to the theme layer. When to use it: This pattern can useful when your organization isn’t yet at a place where framework consistency exists across all areas that need to have design system support. Similarly complex patterns can be packaged up in a Sass framework in this way, and if it’s well structured and organized, transferring these patterns to a framework based component library in the future should be fairly straight forward. **Support: **This pattern requires all of your applications to use Sass as a preprocessor (or any other CSS preprocessor of your choice). Drawbacks: A Sass framework can be a very powerful tool, though it does take a bit of work and planning to build one in such an extensible and flexible way. This pattern only handles the theming of components. Any functional patterns that leverage JavaScript for behavior will likely have to be handled separately and will depend on your specific situation within your organization. Additionally, there are less ways to enforce consistent use of these patterns when compared to a component library. Clear documentation around using the mixins and markup patterns must be generated and maintained. Tools like Sassdoc and Fractal can be helpful here. Examples: ZURB Foundation is a great example of a design system built in this manner. It’s built to be a foundational project that can be customized and extended, though the Sass portion of the project is created leveraging flexible and extensible mixins containing the type of theme pattern abstraction described here.
Static CSS Stylesheet
Providing a static CSS stylesheet with theming patterns is a less flexible version of the Sass library approach above. Specific markup structure would be required to leverage patterns in this manner. There would be a project leveraging a CSS preprocessor that generates this stylesheet.
**Abstraction: **This pattern is the highest level of abstraction that still provides an asset for a dependent project to directly include. When to use it: This pattern might be used if your design system needs to support a wide variety of types of projects or projects that your organization has very little context about. You might consider using this approach if some of the resources of your design system are distributed to users outside of your company and need a very simple way for them to be used. If technology decisions are very disparate within your org, this could be a place to start by centralizing some of the simpler patterns like buttons, form inputs, and icons. Support: Any platform that uses CSS can leverage this pattern. Keep in mind, you may want to prefix your classes with some type of unique string if you are distributing this to outside users. This may even be helpful for internal use if you might need to prevent conflicts with other stylesheets a project may depend on as well. Drawbacks: This approach definitely isn’t the most ideal. Since the resource is a static stylesheet, compound components will require very strict markup. Depending on the frameworks or projects leveraging it, that may make implementation complicated. You will likely need some type of vanilla pattern library tool as documentation of the design system to demonstrate the markup is expected for each component (like Fractal or Pattern Lab). Updating and maintaining this pattern in a non-breaking way can be tedious and time consuming. Examples: The original implementation of Bootstrap was generally used in this manner. For a long time, it was the de facto tool for many developers. So if this is the only option available for your organization, it can still be very powerful. You can also structure your project in a future thinking way by creating the static asset via a Sass library or structuring these patterns in a very well organized way that will let them be refactored easily to a pattern leveraging a lower level of abstraction when support requirements change.
Component Library Blueprints
This is the most general approach and can provide a set of blueprints for end-users to leverage. A vanilla JavaScript component library can be built using a tool like Fractal or Pattern Lab that defines the components that are available and allow users to add them to their own projects in whatever way is necessary.
Abstraction: This pattern isn’t exactly creating an abstraction that is directly leveraged by an end user’s project. Instead, it is creating blueprints that act more as living instructions. When to use it: This pattern might be helpful if the surface area for the design system is very large and either has no implementation patterns at all or is unknown (maybe including external users). Support: There is no real support here as this is more of a definition repository. The stylesheet that the vanilla project uses could potentially be distributed, similarly to the option above. Drawbacks: There isn’t much gained here with respect to implementation, however, building something like this could be the beginning of creating the contract between designers and developers before the implementation team is ready to be able to leverage an asset. Examples: The Lightning Design System demonstrates this type blueprint repository. The intent there is that as the end user you would probably also add the stylesheet provided if you wanted to create those components. In this case, the end users could be anyone building anything on any type of Salesforce platform. Since the environment is completely unknown, a map is provided for the users to navigate on their own.
It’s worth noting that these four examples are just rough groupings of common patterns. There are many variations each approach can take, as well as ways they can be combined to work together. Determining what type of approach works for your organization’s context is important as it will completely define how your design system is built and used.
For example, at Mode we were initially planning to include AngularJS support in our design system. This required us to take an approach where defined patterns could be used across different frameworks (in this case AngularJS and Angular). A Sass framework provided all of the patterns of the design system and a Fractal project built a generic component library describing how those patterns should be used. After seeing a proof of concept using this pattern it became clear that it was more effort to implement and less straight forward to use. In combination with the current progress on our Angular migration, this helped the team agree that AngularJS support wasn’t a requirement that was worth the cost. We pivoted and decided focus only on a design system supporting Angular. This change allowed us to take the patterns we defined in our Sass framework and move them over to an Angular component library leveraging Storybook.
Working through this process will be different for every organization, though walking through some of the issues we’ve been tackling at Mode might give some ideas on how to move forward for other teams in similar scenarios. I hope this discussion makes it clear that there isn’t a single correct way to build a design system and that a large portion of the value a design system delivers comes from defining the internal processes and systems of a team. Ultimately, design systems impact the behavior, organization, and communication of a team to make them more efficient and better collaborators. It’s an involved process to build a design system no matter the context, but hopefully some of these methods will help remove the ambiguity and frustration that comes with taking the first steps and allow everyone to enjoy the benefits of working with a design system.
We are hiring!
By Anthony Simone | Software Engineer, Mode