What's a Design System

Tech Articles

Greg van Brug
Senior Software Engineer

Developing websites and applications is hard. In addition to the nuts and bolts of designing and building the actual thing, individuals on the team might also have a different understanding of what all the things are or what all the things are intended to do, making communication within the team or individual teams difficult and expectations hard to manage.

A design system's purpose is to reduce some of that friction and difficulty by providing a single source of truth for websites or applications that live under a common roof.

A design system defines the voice, style, common patterns, and universal components that can be used to build out any number of websites or applications using these common pieces. That said, every design system is different and might not include all of these elements, but the design system should be a living document and may evolve over time.

Why do I need a Design System?

Teams work smarter, move faster, and build higher quality applications with a design system in place. Product owners can better articulate requirements using a shared language. Designers can iterate ideas quickly by using patterns and elements defined in the system. Developers can be focused on implementing business requirements. QA has a reference for how things should work so they can communicate back to the team with greater detail when a particular implementation doesn't work as expected. In all cases, having a design system in place helps provide a solid foundation for an application and allows the team focus on what the application does not the surface level details of how it does it.

All this sounds great. How do I start?

Ideally, the process of building a design system begins at the start of a project, but really it can happen at any point with a new or existing application. The main thing to grasp and sometimes the most difficult thing to do is to think of all the elements within the system abstractly. Rules, patterns, styles, and components of the system shouldn't be limited to any specific implementation, but rather they should be able to be implemented in many instances and produce the same consistent result.

Variants should be welcome in the system to provide flexibility, but try not to allow too much variation as that will become more difficult to implement and harder to maintain in the long run. For example, it's very common to have multiple styles of a button in your design system, but if you have a dozen different versions of a button it might not only be unclear when to use a specific button but it also makes your code quite complex for a simple component.

Step 1: Audit your wireframes or design

All parts of the team should be represented in this step of the process as their perspectives will create a stronger system. If visual designs are not complete, you could start this step when wireframes are in a good place. In some cases, performing this step using wireframes is ideal because the design is already visually abstracted and yet the wireframe still clearly defines all of the component's properties.

When reviewing the designs, look for similarities, variants, and functionality that could be abstracted into a common element or component — ie. all of the x elements look this way or all the x components do this thing is a good way to start thinking about what should go in the system. Another good rule of thumb is to reference the html element spec. While the spec is more directed towards a technical audience, this tried and true reference provides a direct connection between your interface, the code to define it, and how elements are expected to function.

Step 2: Define your visual language

In this step, look for things that provide visual consistency. Define all of the colors, fonts, icons, spacing, and imagery. This will set the visual baseline for your system.

Some things in this step might also be general guidelines rather than specific values. For example, you might describe when to use a particular type of icon in addition to the physical size of the icon.

Try to still think abstractly when defining things here and name things generally. You will not know all of the instances of where these things may eventually get used.

💡 Note: The case for primary and secondary colors or heading sizes. I get it. As a former designer, these names have meaning. But... In my decade plus experience as a developer, there are often cases where named things get used interchangeably.

Defining a "primary" button with a "secondary" color or a heading style with "paragraph-large", just feels... well, wrong. While the end goal is visual consistency, intermixing language like this can ultimately cause a bit of confusion in the system and the codebase itself as changing variables values down the road could lead to unexpected results.

This is the one area where I find it is usually clearer to define things visually instead of abstractly. For example, use names like "$red" or "$text-default" rather than "$primary-color" or "$paragraph". If you want the best of both worlds in your codebase, you could also create a variable alias like "$brand-color-01: $red" or "$h1-size: $text-xlarge" which might give you the most consistency, clarity, and flexibility. But maybe that's also slightly over-engineering it in some cases...

Step 3: Collect or build components

If you're working with a new codebase, start by building the smallest components and build up to the larger ones. Keep flexibility in mind and try to find ways that your components could be extended in the future. For example, instead of passing arguments directly to a component consider using a props or options object instead. This will ensure that if the options for your component need to change in the future, your component isn't dependent on the order of the arguments it receives which could lead to a lot of refactoring after your codebase has grown.

If you're working with a pre-existing codebase, your job might be a little harder. In this scenario, your components may be hardcoded into multiple templates already. First try collecting a few examples of how your component is used and assess the differences. This should give you a general idea of what options to support in your component. Then start small by replacing a few instances of your hardcoded component with your new dynamic component, do some testing, and build up your library over time.

Try to support all the attributes of standard html elements when building basic components. This will avoid integration gotchas and headaches later.

Focus on the internal styling and workings of the component itself not its context on the page when developing your components. This means avoiding widths and outer margins where possible as the component should be able to live in many contexts and these properties will likely need to be overwritten in specific layouts.

This is also a chance to verify your component definitions. If you're building something with a very specific or complex name, can you abstract it into something more general or reuse something you've already built? You should aim to have a small-ish library of robust components at the end of this step rather than a large one with very specific components.

Step 4: Document and provide examples of usage

This may be the most important and the most valuable part of your design system. Be sure to document all your components, their API, and their implementation. Be thorough and clear so that others fully understand when and how to use your components.

Documenting your components is also a simple way to test how difficult they will be for others to implement. If you're spending a lot of time documenting all a components options, maybe your component is too complex and should be broken down or maybe there's a better way to achieve the same flexibility.

You should provide a live demo example of a working component. This will allow others to see how it works and provide a way for you to view your component in isolation, ensuring that given a set of options your component behaves as expected.

Step 5: Test and Iterate

Your design system will grow and evolve over time. Maybe your palette changes. Maybe transitions are added to help inform the user of their interactions with the UI. Maybe a new requirement calls for a new component or component option. Whatever changes, make sure you document it.

As your system grows, be sure to review your existing components for regressions. Visual testing is a simple first step but also consider adding unit tests for more complex logic so you are sure that your system stays stable over time.

Using your new system

Now that you've created your design system, you should have a robust set of visual elements and components that should feel easy and natural to implement. If implementation feels awkward or gets too specific, consider refactoring. In any case, having a design system helps all members of the team communicate better, iterate faster, and ultimately build scalable stable applications.

If you're ready to start building your Design System, there are a bunch of tools out there to help you get started. But if you're working with Apostrophe, consider using our Apostrophe-Guides module. For installation and specifics on how to use the module, see the documentation on Github.

Greg van Brug

Senior Software Engineer

Former designer, gone Full-Stack Developer. Greg strives to develop an intuitive experience for developers and end users