Lessons Learned Building Culture Amp’s Design System
For the past 18 months, I’ve led the design systems team since its inception at Culture Amp. I’ve worn many hats, contributing to design, development, and product management. Today I want to focus on the tech, culture, and the non-intuitive lessons I’ve learned building a design system. It’s just one story, but I hope you can take away something useful from my experience.
At Culture Amp, the key to building a successful design system was fostering a shared language. A shared language has helped us uplift our product’s design, all while building a strong partnership between design and engineering.
In this article, I’ll cover:
- Introducing the Kaizen Design System
- Storybook is not only for developers
- Branch previews transform workflows
- Internal open source increases shared ownership
- Tokens are the building blocks of design systems
- Effective versioning and releasing is critical to accelerating engineering
- Shared language reduces the hidden costs of conversations
- Stand on the shoulders of giants to fast-track learning
- Naming things is hard
Introducing the Kaizen Design System
Kaizen is Culture Amp’s design system:
Kaizen (“improvement”) is a Japanese business philosophy that focuses on continuous improvement. This is how we see our design system: an ever-evolving language that can be shaped, grown and nurtured to support how we work, and enhance our customers’ experience.
We use Kaizen to accelerate design and development, and empower designers and front-end engineers with a shared language.
Our design system is made of 3 parts:
- Kaizen Site: public documentation site
- Kaizen UI Kit: Figma assets
- Kaizen Component Library: code assets including React and Elm components, draft and core library packages, design tokens and hosted assets, such as binary images.
Culture Amp itself is a people and culture platform with which we strive to create a better world of work.
Storybook is not only for developers
Storybook is a tool for developing UI components in isolation from applications. It plays a critical role in establishing a shared language.
The primary audience for our design system includes Culture Amp employees directly building product, especially product designers and front-end engineers. We also support wider company employees, such as: brand and content designers, content strategists, user researchers, full-stack and back-end engineers, product managers, QA engineers, and customer success.
We use storybook to show our React and Elm components, in all their states, with interactivity, in one place. Each state is shown as a separate “story”.
Give everyone visibility
Storybook gives everyone visibility into what components exist and how they work. A picture of a component doesn’t tell you how it works and feels. Modern design tools are improving, but you can’t beat the accuracy of something working in the browser. It feels more real. As one designer put it:
“I trust it, something about code I trust more. When it’s a picture in Figma, will it actually turn out like that?”
Every hover state, scroll, and drag responding to your input is behaviour you can trust. For example, if there were delays between hover or click and the resulting action, you’d know about it. Maybe the animation is just a tad too long or maybe the multi-select search updating results on every keystroke causes distracting flickers.
Storybook keeps everyone on the same page about exactly how the UI works today. This avoids developers having greater familiarity than everyone else. Shared understanding is invaluable for effective product teams.
To protect user privacy in our product, we limit employee access to customer account data. Given the data-driven nature of our product, this means without data it’s difficult to realistically see how it behaves, which is a challenge for designers. It’s hard to design for a platform you rarely see. The use of storybook encourages stories that separate data from presentation. Designers can see how components would work with different data. For example, everyone can see a single collapsible vs a collapsible group with sticky headers or a single toast notification vs multiple notifications.
Visible, named components surface discrepancies in understanding
Through shared components, it’s easy to see how perceptions differ between designers and developers. For example, our design system includes a Tag component:
Tags help users quickly recognize important information about items that organize and categorize them. They visually label items with small amounts of information or the item’s status.
Prior to storybook and the design system, developers called them chips or badges, depending on which part of the code base they were in (or which library they were using), while designers called them pills or labels. In addition to bringing them closer together, our storybook stories clearly describe intent. For example, there are negative sentiment tags that we use for labelling negative comments and live status tags for showing live surveys. Bringing designers and developers closer together is one of my favourite things. Showing designers what devs call things and building things that align with how they’re used makes our lives easier.
Reliable visual regression testing lets you release visual changes faster
Storybook stories make it possible to create snapshots of components in different states for visual regression testing. We use Chromatic to quickly test visual changes. It’s especially helpful for wide-reaching impact on components, such as our recent change in typography from Ideal Sans to Inter for text. By testing component snapshots, we don’t need to manage the overhead of getting complex application code into the right state for testing, which is often brittle and can require more maintenance than the benefits it brings. This changes how our QA crew test things.
Branch previews transform workflows
A branch preview is a deployed preview of a feature branch. Feature branches in our repo are automatically deployed to a public URL that can be shared with team mates to “preview” the feature live. This includes visualising changes to components in storybook or updating design documentation on the site.
I cannot overstate how good branch previews are.
It’s cheaper to fix design problems earlier in the process
By empowering designers to participate in development as it progresses rather than after shipping, we open up a window of opportunity for designers and engineers to collaborate and make changes at the time it’s easiest to do so.
Greater context leads to higher quality output
Branch previews let us see the full experience exactly as it will appear. For example, documentation isn’t reviewed only in code, it’s reviewed in the context of the page it will appear on, showing text-wrapping and all. This is especially helpful for designers and content strategists. Our branch previews also don’t suffer from the gap between what code looks like locally “on my machine” and how it will look in production. It gives us an accurate preview. Armed with greater information, we go through fewer revisions to reach high quality results.
Effective remote working practices create more inclusive products and workplaces
Branch previews let people comfortably view and interact with work in progress on their own devices. This means there are no issues with Zoom jank on animations over a screen share. This means someone can click on what they want to click on instead of directing the developer to please click on the button, wait, the other button…
Branch previews also inherently mean more variety in testing. I’m a heavy keyboard user, I notice there’s a focus state missing. My colleague is red-green color blind, he notices the lack of contrast. As a huge plus, persistent branch previews let people across time zones look at work in progress asynchronously, instead of waiting until you’re all awake at the same time and have time to meet.
Put designers in the driver’s seat
We’ve heard from several designers that the design QA review is a key step in the process with their teams. The more closely they partner with engineers before launch, the stronger the solution delivered. In these moments, letting the designer steer with a branch preview gives them a chance to interact with the interface as well as try to break things. With their design brought to life, they can see how it will meet user needs. In contrast, without branch previews, one designer reported “only seeing what they show you”, which means they might not explore different pathways or see where the design falls over until its in the hands of customers.
Internal open source increases shared ownership
The Kaizen Design System’s repository is here:
The power of the crowd
Treating our design system as internal open source also invites engineers to file issues, create PRs for features teams needs or bug fixes, and contribute code reviews on shared engineering assets. This is similar to any open-source software that we depend on to build modern apps. As a result, while the design systems team has driven a lot of progress, we’re far from the only team building components.
Contributions from all disciplines. We also keep our UX documentation in Markdown, making it feasible for product designers and content strategists to make edits and add pages through GitHub’s UI. This means we don’t need to build or manage a CMS for a variety of folk to contribute. And again, branch previews give us additional confidence in the changes we’re making, without designers needing to install and run Gatsby (for example) on their own machines. We grow our design system together, ensuring many voices are heard.
Team work makes the dream work. Internally, we have a crew of design system Advocates, designers and engineers from product teams all over the business, that collaborate more closely to discuss interaction design, patterns, API design, and opportunities to improve workflows. We depend on the advocates’ input and feedback to strengthen the system from grass roots efforts.
Public design systems appeal to employees. Public design systems are almost standard these days. It demonstrates that user experience, as well as designer and developer experience is valued. This helps us with recruiting. It also helps our existing employees showcase their contributions to the world.
Tokens are the building blocks of design systems
A design token is a named and stored visual trait, such as colors and typography. They underpin everything in a design system. Let’s be frank: they’re the main reason one design system looks any different from another.
The first design tokens to nail are the option tokens. As described in Tokens in Design Systems by Nathan Curtis, option tokens show what the design system offers. By agreeing on these and encoding them in one place, consistency is then built into everything you produce. Instead of 548 unique hex values, every engineer will use the same hex value intended for “blue”.
The second part of design tokens, decision tokens, tell you when to use a token. For example, you may want all links to use blue. We haven’t got this part totally figured out yet. For the most part, our decisions are captured in our components directly, which are all design reviewed. The main area we see variance is when teams need to cohesively create a new visual element without a component, or adapt the design system to other contexts outside our main product code, such as slide decks.
Reduce hidden costs
If you don’t have design tokens, what you likely have is CSS or Sass variables like
primary-color: #F04C5D. Failing that, you probably have hundreds of different hex values strewn throughout your code base. I recommend checking with CSS Stats. This is a fantastic starting point for quantifying the degree of inconsistency in your product that customers are experiencing and engineers are working around. These inconsistencies increases CSS complexity and file size, and introduce bugs. These are the hidden costs that design systems help to solve.
Build once and distribute
For a platform-agnostic approach, we store our tokens in JSON, which then publishes Sass and Less variables that we can use in different code bases. Even if you don’t use more than one framework now, you might bump into one in the future. For example, if you acquire a company (like we did), build an iOS app (like we did), use a WordPress theme for a public blog (like we do), or integrate with other services, such as Slack or MS Teams (like we do).
Roll out brand changes easily
It’s now possible to rapidly roll out design changes. The brand color we were previously using in primary buttons wasn’t quite dark enough to be accessible, so we tweaked the value in the token and every component automatically uses the new, accessible color. Easy.
Effective versioning and releasing is critical to accelerating engineering
Our packages are published to NPM under the Kaizen organization:
Streamline versioning and releasing
One of the biggest challenges in the history of our design system has been versioning and releasing. Improving the workflow has increased the growth rate of the design system.
Our revamped package release workflow automates releases to NPM when changes are made to packages, using NPM’s strict semantic versioning. We also use conventional commits in our PRs so that our CI pipeline correctly updates version numbers, taking the legwork of this out of the hands of engineers. This has helped to vastly reduce conflicts and unblock engineers. In turn, that’s lowered the barriers to contributing, increasing the overall activity on the repo.
More recently, we’ve also moved from a single-package approach to a blended multi-package approach using Lerna. By breaking our single component library into a core component library package and many draft component packages, teams can iterate more quickly on draft components and minimise the surface area for testing and rolling out changes. This has further reduced engineers being blocked and given us greater control over making visual changes.
In practice, our multi-package approach lets us experiment more and road-test components before settling on a shared approach. It’s easier to rapidly pump out breaking changes while only 1 team is using a draft component. We don’t need to knuckle down on finessing a component until we’re sure we want to keep it. This means anyone can add a draft component with minimal barriers using a component-first development approach to building new UI. Multiple teams can also build on existing draft components instead of simultaneously building their own version of the same component in their own applications.
When we do upgrade a draft package to a core component, we’ll have road-tested the idea and formed ideas around what we call it. We have a component for drawing attention to primary content called a Hero Card that evolved from using that element as the hero of the page. We could have called it a “panel” or “container” or something else, but real usage led to a name that works for us and describes the intent.
Shared language reduces the hidden costs of conversations
The hidden cost of conversations is hideously expensive. Designers and developers spend a lot of time clarifying details. For example:
- What shade of blue is that?
- Where’s the logo file in SVG?
- How many pixels is the padding?
- Is this a new button? Are we updating all the buttons?
A while ago I spoke about UI Patterns for Design Systems at the DesignOps, Melbourne meetup and I shared a slide showing some napkin math on these hidden costs that resonated with a lot of the people I spoke to there:
I have seen many debates on the finer differences between links and buttons, and what cursor a button should show on hover. Design systems are a great way to cut through the noise with a documented stance.
Stand on the shoulders of giants to fast-track learning
There’s no need to reinvent the wheel
Where possible, we build on the community of practice around design systems and open-source component libraries. Before building any new component, we look for existing code in our product. For example, we’ll inspect custom components that are tightly coupled with application code or workarounds such as checkboxes where we wanted to use Toggle Switch before we developed that component.
We also look externally to find 3 examples of someone who’s done this before. For example, many of our documentation site’s component pages include external links to design systems, W3C specifications, accessibility articles, and copy guidelines. See Text Field for an example. As an added bonus, picking apart other component libraries is one of my favourite ways to learn accessibility considerations.
This kind of internal and external exploration means we build on conventions. On top of the pay-off in consistency for users, these conventions also make it easy to onboard new designers and engineers: we already have some shared language.
The effect of every decision in a design system is multiplied
For example, people copy what’s already there. This means good decisions are amplified, so a design system teaches as much as it enables. This also means bad decisions can proliferate. Learning fast is important to nipping iffy decisions in the bud and fast-tracking improvements.
Naming things is hard
Validate names before shipping
Shared language is critical in a successful design system, and naming components and props is a huge part of that. We verify our component names with designers and engineers before settling on anything. We also use respected industry guidelines like W3C specifications and popular frameworks to inform our choices.
More recently, UIGuideline has emerged as “The definitive guide to standardize the UI Components naming” and I am excited by its potential. Renaming components comes with breaking changes and a small amount of change management overhead, so this is another example where it’s cheaper to change it earlier in the process.
Avoid “type” and “variant”
Because naming things is hard, a common pattern we see is naming React component props “type” or “variant”. Unfortunately, this not only makes it hard to know what that property does, it is used inconsistently across components. Where possible, we choose more specific and concrete names to make our code more self-evident. In one case, we worked around “type” and “variant” by coming up with something a little unusual. Many of our components have come in a set of “Positive”, “Informative”, “Cautionary”, and “Negative” moods to create an emotional impact and contextualise information. We named them “moods”.
Search engine optimise component names
Internally, we use our chosen component names fairly consistently. That said, to help onboarding new designers and engineers, we also show alternative names on component guideline pages and within Figma via the “Description” field. Extra names in the description field in Figma ensure you can search for “pop up” and it’ll return the “Modal” component as we call it.
Onwards and upwards
Building Culture Amp’s Kaizen design system has been a journey. The team is about to evolve and I expect we’ll keep reiterating the value of shared language while we steward the next phase of increasing adoption and accelerating the design system and our product teams. Meanwhile, here are the key takeaways of the adventure so far.
Use Storybook or similar to improve separation of presentation, data, and application logic. Storybook can surface edge cases. It makes it faster to eyeball different states. Storybook also supports visual regression testing and gives designers live access to built components.
Use branch previews to empower designers to participate in development as it progresses rather than after shipping. Branch previews also improve asynchronous collaboration and create inclusive remote working practices. They also let you see changes in context.
Internal open source increases shared ownership. It lets you take advantage of the power of the crowd, engage all disciplines, produce a stronger system, and attract top talent.
Use design tokens to ensure a consistent design system and reduce hidden costs. Agree on design tokens, create a single source of truth, and use them everywhere. This lets you roll out changes fast.
Streamline versioning and release management. This is key to unblocking engineers and lowering the barriers to contributing.
Shared language improves communication and reduces wasted time, lowering product development costs.
Fast-track learning by looking internally and externally for expertise. Don’t waste time building things from scratch and reinventing the wheel. The effect of every decision in a design system is multiplied.
Naming things is hard. Don’t go it alone.
About Diana MacDonald
Diana MacDonald is the author of Practical UI Patterns for Design Systems and creator of Typey Type for Stenographers. She led the design systems team at Culture Amp. Raised in the tropical north of Australia, she has spent the last decade in the tech industry, exploring the digital space with progressive organisations like Culture Amp, Bellroy, and SitePoint. Blurring the lines of designer and developer, she believes in the value of considered, inclusive, and remarkable stories. She wants to help you effortlessly execute your digital ideas.