← Michael Histen: UX & Product Design Leader
Case Study: Building Scalable Design Systems
When it comes to UX & product design, I am particularly passionate about design systems and libraries. They are a "win" on so many levels — they create a more consistent user experience, they allow designers to scale up new features rapidly, and they translate to more efficient engineering development time as well. A well-maintained design system is an enormous benefit to any organization.
In this case study, I aim to share practical insights and proven strategies for designing scalable, accessible, and efficient design systems.
In my six and a half years as head of design at BondLink, I had the unique opportunity to design and build an entire design system from scratch. This work originally began in Sketch, but we soon migrated our design work to Figma, which allowed for an incredibly robust component library, complete with variables and auto-layout features that enabled us to drag-and-drop elements to create new pages and features.
I’ll walk through some of the design decisions I made across a variety of areas, including:
Colors
Defining a color palette for a design system is a much larger undertaking than it appears. You can think of colors in four major groupings:
- Brand colors
- Grays
- UI status colors
- Data visualization colors
When choosing colors, it’s also vital to think about accessibility. Throughout our process of defining colors, we vetted each choice against color contrast checkers to ensure we met WCAG accessibility standards for sufficient contrast. Additionally, when considering colors for our data visualizations, we tested our choices with color blindness tools, to ensure clear differentiation. The end result was a robust color system that was accessible to a wide variety of visual acuity levels.
An example showing the full color palette for our design system.
Brand Colors
Brand colors are where designers typically have the most fun. These are the colors that distinguish you from your competition and become a unique identifier that can unite your designs across every touchpoint. Think of T-Mobile’s magenta color, UPS’s brown, or Tiffany’s blue.
When I started at BondLink, the only major visual element we had for our brand was a logo that incorporated gray, blue, and green. I felt the blue and green seemed too much like standard crayola-style colors, and instead experimented with different shades of blue and green, landing on more vibrant options that would help us stand out in the world of FinTech. It also became clear that our vivid blue and green would be well-balanced with a darker, less bright color, so I introduced a dark teal color to round out our primary color set.
We also associated meaning with each of our colors — we used our blue for interactive elements such as buttons and links, our green to highlight large pull-quotes and other decorative elements, while our teal was used primarily for text and other smaller UI elements intended to differentiate from standard body copy.
Grays
In addition to our brand colors, we established a full scale of grays from black to white to be used throughout the platform. We labeled them in increments of 100 (from 900 to 100), which is a common technique for labeling gradations in CSS.
While many websites default to using black for body copy, we chose our gray-800 (hex value of #333333) as our main body copy color, allowing us to more effectively distinguish important items by only making text black for headlines or other bolded elements. We also set our lightest body copy color option (gray-600, #727272) to have sufficient contrast on both white backgrounds and on our lightest gray (gray-100, #f8f8f8), which we occasionally used as a background color.
This wide variety of grays allowed us to create hierarchies for text and UI elements that imbued meaning, such as indicating the importance of content, distinguishing disabled elements, and creating navigation elements with multiple states.
UI Status Colors
Beyond our primary brand colors and grays, we needed colors that could be used for error/deletion, success, and caution states. Error and success states call for red and green colors respectively. Given that one of our brand colors was a green color, we made sure to choose a "success green" that was clearly distinguished from our brand "lime green" color. For our error red, we went with something bold but not so intense that it would be hard on the eyes. Caution is usually indicated with yellow, but yellow is one of the lowest-contrast colors and thus is very hard to work with, particularly for text. Thus, we shifted our yellow to be a golden-brown color, which gave us a bit more flexibility (and also was less overpowering than something like a bright highlighter yellow).
Data Visualization Colors
Our platform also included a number of data visualizations via line, bar, and pie charts. While our brand colors gave us some options to use, we needed a more robust palette to better distinguish key data. We wanted to keep the "vibrant" feel of our brand colors, so rather than using more typical primary/secondary colors, we explored more vivid options, ultimately choosing magenta, orange, and indigo to fully round out our brand palette. We iterated through several variations of these colors, carefully balancing accessibility needs, including testing them against a variety of types of color blindness.
Shades and Variations
The final step in having a fully defined set of colors was to include various shades of each color that could be used for states such as:
- especially complex data visualizations
- background colors
- hover and active states on interactive elements
While we established a 10-level scale for grays, we opted to just do a 5-level scale for other colors, labeling them 900, 700, 500, 300, and 100. We chose 700 as the "primary" version of each color, and then scaled each color using HSL values (hue, saturation, lightness) that we then translated into hex values. By keeping hue the same across all levels, we ensured that each color maintained a clear "identity," and then through some experimentation, altered the saturation and lightness levels to create full color scales. We also tested every color for accessibility against white and black, and then against the darker and lighter shades of each color, to allow for using the lighter colors as background colors with darker-colored text.
Setting Figma Variables
Once our full color palette was determined, I created variables for each color within Figma in a shared styles library, ensuring their consistency across products. This also meant that if we ever needed to change a color slightly, we could change it in one place and automatically propagate it across all products. Every element in our designs always used a color variable, never directly typing a hex value into a design, thus ensuring we were always staying on brand.
Suffice it to say, what might seem like a fun exercise of color selection for a brand is actually a very complex process, and requires balancing quite a few factors to ensure colors are legible and accessible and convey the appropriate meaning where necessary.
Typography
Much like color, typography can help reinforce a brand identity. Consistent, well-structured typography is also essential for any user interface. We can break this down into a few major decision points:
- Font selection
- Text sizing
- Line-height
- Mobile & responsive considerations
An example showing typography components in Figma.
Font selection
For BondLink, we wanted to use a font that seemed professional and business-like, while still having a little more personality than something like Arial. For ease of use (and the fact that they’re free), we looked to the Google Fonts library and chose IBM Plex Sans.
For many brands, you may want to choose 2 or even 3 fonts for your design system, particularly for headlines vs. body copy. I’d say this is more useful for brands that are more playful or in the entertainment realm, as it allows for bolder headline elements. However, we wanted to emphasize a simple, clean aesthetic for BondLink and opted to just stick with IBM Plex Sans.
Text sizing
From here, we chose header sizes for the text and line-height of our H1-H6 headers, in some cases introducing all-caps styling with increased letter-spacing for further distinguishing smaller headers. We also established sizing for body copy, and quickly identified that we’d need some additional body copy variations (including lighter/darker grays) to convey relative importance, which allowed us to emphasize content (such as opening paragraphs on landing pages) or de-emphasize text (such as disclaimer copy or other secondary information).
We also opted on 3 weights for copy — in addition to the standard regular weight (labeled 400) and bold (labeled 700), we included a "medium" weight (500) for when we wanted to give particular words additional emphasis without fully bolding them, and for our smaller header styles. This medium weight became particularly valuable in tables where we wanted to emphasize a key column.
Line-height
One other major consideration for text that often gets overlooked is line-height. Some websites establish a common line-height of something like 1.5 (so for example, a font that is 16px in size would have a line-height of 24px). While this may seem like a simple solution, in reality, it’s much better to explicitly define line-height for each different font size. Large fonts for headers typically look best with much smaller line-height than standard body copy. So rather than using a multiplier, we defined pixel heights for line-height for each size of copy, sticking with multiples of 4px, which allowed greater consistency and also more predictable sizing when text was next to other UI elements such as images.
Mobile & responsive considerations
Speaking of text sizing, we chose 16px as our default body copy size, which is also the default that most browsers use for body copy. This was especially important in our form design for text inputs in our responsive deisgn. Many mobile browsers, such as Safari on iOS, will automatically zoom the screen on form inputs if they are smaller than 16px. Since our design was already optimized for mobile screens, when we experimented with a smaller font size, this had the unintended effect of making forms harder to navigate due to the unexpected screen zooming. Sticking with 16px for inputs allowed enhanced readability and allowed users to easily complete forms.
Conversely, large header fonts that may look great on big screens can be overwhelming on small mobile screens, so we established smaller header sizing for smaller screen sizes as part of our responsive design.
Iconography
First, let me say that there are a number of pre-made icon sets that, for many brands, are probably sufficient to cover the vast majority of your needs. In our case, we wanted to have icons that could relate directly to some key features of our industry that were somewhat atypical from a standard icon set. After initially exploring using icons from The Noun Project on an as-needed basis, it quickly became clear that we’d end up with an icon set that had an inconsistent look and feel, so we decided to create our own BondLink-specific icon set.
An example of the icon set for our design system.
While there are a handful of icons that have become so commonplace that most people know their meaning (such as an envelope for email or a pencil for writing/editing), most icons will require some accompanying text or other additional context to ensure their meaning is clear. Icons also need to be able to scale considerably and be legible even at very tiny sizes.
For our icon set, I decided on using a consistent 32 x 32px canvas for each icon in an Adobe Illustrator file. All shapes within each icon were defined with vectors (using a mix of lines and shapes) to ensure scalability. We also established some rules for our icon creation:
- Icons should always have at least 2px of clearance around each side and should never touch the edge of the 32x32 canvas.
- Given that icons may be next to each other or next to text, this added a built-in buffer (in much the same way that letters in words typically do not directly touch each other).
- This also ensured that the sizing of icons next to text did not seem overly large, as this allowed the icon to more close match the size of letters.
- There are also a few instances where you should deliberately make your icons even smaller than the available space. For example, a down-facing triangle used in a form dropdown would look wildly oversized if it filled the entire space — a smaller triangle is much more effective in this instance.
- Use a consistent line width (2px wide) and rounded corners for shapes.
- This established an important level of icon consistency, and also allowed icons to look roughly the same "weight" as text.
- We made a handful of exceptions where we used 4px wide lines for a few of the simpler icons that conveyed specific actions, such as the "+" symbol for adding content and the pause symbol in our media player, which gave these function-specific icons a more appropriate visual weight.
- Create icons by using overlapping lines and simple shapes, and then use union/subtract to determine the final shape.
- While free-form vector drawing may give more freedom with designs, we felt that sticking to simpler shapes allowed our icons to look more consistent.
- It’s vital to also transform any lines into outlines and shapes in Illustrator to ensure icons will properly scale.
- This also allowed our icons to be smaller files, as the simpler vector shapes require less code.
- Avoid placing content in the corners of icons whenever possible, and use "optical centering" for unusual shapes.
- This is a bit more art than science, but since icons are often presented in circular UI elements, having them stretch into corners can look awkward.
- Also, literal centering often has a counterintuitive effect of looking unbalanced if an icon has more content on the left or right side.
- A simple trick we used for checking the "balance" of each icon was to put a semi-transparent 28 x 28px circle on the icon canvas. Ideally, your icon will be completely within the circle, and if any portion is slightly outside of the circle, it should be balanced on each side. We noticed this most clearly with our "share" icon, where "literal" centering meant that the right side of the icon was visibly outside the circle — shifting the icon 1px to the left on the canvas allowed us to achieve optical centering.
Beyond these general rules for creating icons, we also primarily used icons in conjunction with text to ensure clear meaning. Our major exceptions were in tables in our portals, where space was at a premium. In this scenario, since our users are frequently interacting with this content, they have a greater likelihood of having time to learn the meanings of each icon. We also tried to keep these functional icons to more universal icons, such as a pencil (edit) or trash can (delete), and added table headers to ensure clarity when possible.
Components
Thus far, I’ve talked about the more basic building blocks of a design system. Where a design system really becomes powerful is when you create reusable components. There are a few key aspects to component design:
- Defining variables
- Creating components
- Refactoring as needed
- Nesting components
An example of button components, showing various contexts.
Defining variables
Figma allows you to create numerous variables with each component so that you can adjust them as needed for each context. For example, our basic button design had a number of variables. Our buttons sometimes (but not always) included an icon, so we created a variable that allowed you to show or hide the icon, and then also swap out the specific icon as needed. You can also use variables to establish the various states of a button — such as default, hover, active, and focus.
You can also create variables based on context. We had both primary (solid color) and secondary (white background with a color outline) buttons, as well as alternate button designs to use when a button is overlaid on an image, or when a button is being used to delete (red) or confirm (green) content.
As you can tell, there are a lot of variations here. This is where personal taste comes into play as well with component design. We also had a "small" version of our button, but rather than add yet another variable to our standard button design, we decided the differences were significant enough that we simply made a different component for small buttons. Another design team may have approached this as all part of the same component, however my opinion is that once the number of potential variations of a component starts getting into the dozens, it may be time to consider breaking it up into separate components to reduce complexity.
Creating components
For any website/app, there are going to be a certain number of default components you’ll want to design — such as buttons, form fields, tables, etc. But you can also extend your design system to include larger components more specific to your particular product.
For example, at BondLink, we frequently showed upcoming bond sales by using cards that include multiple data points, so we created a component for bond cards. Each card can have quite a bit of variation, as many data points are optional, so we designed the card with a "kitchen sink" approach, showing the maximum amount of data that could be in any given card. We then used variables to hide or change which data was presented.
Refactoring as needed
Bond cards in particular were a good example of how we refactored our designs to make them more efficient in our design library. As we built out our platform, we eventually had 4 different products that showed bond cards, and as they evolved, each product showed the bonds slightly differently. Many of these differences made sense — for example, when looking at bonds from a single issuer, you don’t need to list the issuer on each card since it’s always the same. But in our investor portal, users were able to save bonds from multiple issuers — thus necessitating showing the issuer name on each bond card.
As it became clear our designs were diverging, we did an audit of all content on each version of the bond card, and then refactored the design to allow it to be the same component, just showing/hiding relevant content based on the product context. This not only improved efficiency on our end by turning 4 disparate components into one, but also ensured that our users were seeing a more consistent experience by ensuring the content of each card was in a familiar, consistent design across our products. This had the added benefit of making our engineering team happy, as it allowed them to also reduce the complexity in their code.
Nesting components
Once you get into the world of components, there is a near endless set of possibilities for how you configure your design library. In Figma, you can also nest components inside other components. Some of you may be familiar with the concept of "atomic design," and you can use a similar set of principles in your Figma files.
For example, a button is a component, but you could also have a card component that includes the button component. In some cases, we continued taking this further to create large chunks of pages as components, particularly when similar page content was being reused across products. This not only sped up feature development, but also made it clearer when we needed to adjust pages or content to ensure greater consistency.
Voice
One often overlooked aspect of a design system is the voice or tone of any text within your designs. This is much harder to standardize than something like a color or font size, but if you come up with some basic principles, it can significantly improve the overall user experience while reinforcing a brand identity.
For BondLink, one of our missions was to make the complex world of municipal bond issuances feel as simple and clear as possible whenever we could. To achieve this, we approached our copy as being plain-spoken, clear, and conversational, avoiding overly technical jargon. We also made a point of clearly explaining the impact of any actions. For example, our websites have the option of a draft mode, where clients can make changes and save them before deciding to publish them to their live sites. Rather than simply give a "save as draft" option, we added copy in a modal dialogue that let users know that their changes were being saved, but that an admin would need to review and approve the content before it was published. If the user in question was an admin, we also provided a link there to directly take them to a change approval screen. Our goal was to add enough information to avoid any guess work, and to anticipate any questions or next possible actions.
There are a number of other stylistic considerations to make about voice. Do you want to personify the brand, and use copy like "We’ll save this for you so you can come back later and approve when you’re ready" (making the brand the "we")? Or do you want the brand to be a bit more dry, emphasizing functionality rather than personality, by using copy like "These changes will be saved and can be later approved by an admin." Whatever voice you choose for your brand, what matters most is consistency of tone throughout your products.
Additional Guidelines
A well-maintained, comprehensive design library makes creating new features much more efficient. But it also requires a certain level of rigor in how you use the design library. Below are a few guidelines that I highly recommend:
-
Always use variables, never manual values.
This means that you should never be typing a color’s hex value or a font size — you should always be selecting a pre-defined variable. The moment you start using manual values, you lose a key benefit of the design system and you invite the potential for design drift. If you find that you need an option that isn’t already defined as a variable, that may be a sign that you need to update your design library to create a new option. -
Rely on components as much as possible.
Similarly, an ideal page would be made entirely out of pre-existing components, with only selected variables or text changed within each component. There are occasional situations where a page or feature may have some type of content or functionality that is genuinely unique and might not have a pre-existing component. In those situations, I recommend considering the following:- Is there a similar component that already exists? If that component had some new variables added, would it be reusable in this context? If so, you may just want to update that existing component to be more flexible to cover your new use case.
- If this is genuinely a new type of functionality, you should consider creating it as a new component, even if it is only on this one page. There may be a case in a future project where that component is needed, so you’ll be saving time on potential future projects by creating this new component now.
-
Don’t overcomplicate your components.
This is always a judgment call, but I recommend not trying to overstuff components with so many variables that they become hard to manage. For example, on BondLink, we used cards throughout many sites. If we had simply created one card component with variables for each situation, we would’ve ended up with 100s of variables for this one component, which would have reduced our efficiency by requiring such an extensive amount of variable selections each time we needed a card. Instead, we made multiple card components based around either a common set of functionality or content for each one. We did have one common card background component that we used in each separate card component, however, which allowed us to have a consistent color, drop-shadow, and border. -
Be strict about introducing new components.
As we designed more features, we frequently found ourselves asking, "is this actually a new component or is there something existing that we should be using?" Most of the time, there would be an existing component we could re-use or slightly modify with new variables to make it fit our needs. -
Start with the kitchen sink for each component, and then scale back.
When creating a component, try to think through every possible scenario for what content/functionality might be needed, and include all of it in your base component. Hiding component elements is easy in Figma — adding new elements to a component instance is not. -
Consider having a design library "owner."
While in an ideal world, every designer working on a project would have the same sensibilities about adding and modifying components, that is simply not a realistic expectation. Having some system of governance where one person functions as a gatekeeper/organizer of sorts is incredibly helpful. Anytime a new variable or component is being considered, this person should be part of the decision-making process. Depending on the size of your organization, you may even want to institute a more formal process for requesting/approving new components/variables to ensure a well-organized, comprehensive library.
Summary
A well-structured design system is more than a collection of components — it’s a strategic tool that drives consistency, accessibility, and efficiency across teams. Design systems empower organizations to innovate and grow by enabling the rapid development of new features. Whether you’re building from scratch or refining an existing library, the principles and practices shared here can help create a foundation for success.
← Back to my full portfolio