Building a design system for HealthCare.gov
In this post, I’d like to share some of the bigger technical decisions we made while building that design system. Fortunately, a lot of smart folks have already put a lot of thought into the best approaches for building scalable, developer-friendly, and flexible design systems. This post will also shine a light on those resources we used to help steer the technical direction.
Sass and React
Much of the HealthCare.gov front end, and especially new work underway, is built in Sass and React. In addition, most 18F projects and the [U.S. Web Design Standards] (https://standards.usa.gov/ “A design system for the federal government”) use Sass. These were important factors to us when considering what to use in order to provide the widest range of support. We’re able to use the same stylelint rules as 18F to ensure consistent code standards, the same Sass variables as the Standards to ensure color palettes are consistent, and if developers want to, they can import individual components for a smaller bundle size.
A lot of the design system’s architecture and naming convention was inspired by a post written by Brad Frost: “CSS Architecture for Design Systems”. A guiding principle for how we name things came directly from his post:
“The design system may sometimes seem verbose, but it delivers clarity and resilience in exchange. Keeping CSS legible and scalable means sacrificing a shorter syntax.” — Brad Frost
- We namespace CSS class names to avoid conflicting with other libraries and existing code. This is important for implementation on HealthCare.gov, as it allows us to incrementally apply the design system to existing parts without introducing unintended side effects.
- Prefixes are added to make it more apparent what job the class is doing. A prefix of “c-“ indicates a component class, “u-“ indicates a utility class, and “l-“ indicates a layout-related class.
- BEM syntax follows the namespace and prefix. It’s a methodology that’s widely known, resembles the syntax used by the U.S. Web Design Standards, and works well for modular CSS.
Helpful CSS architecture resources
- CSS Architecture for Design Systems
- Thoughtful CSS Architecture
- Less CSS mess
- ITCSS [PDF]
- More Transparent UI Code with Namespaces
An exercise we did during the early stages was review the landscape of frameworks and tools available for documenting design systems. We experimented with some of the more popular ones like Fractal, Pattern Lab, React Storybook, and Catalog.
The requirements we measured these tools against were:
- It should output a static HTML site. This would allow us to archive each version of the documentation so it’s still available for teams using older releases.
- It should be easily customizable. The documentation is public, so it should be easy to brand and make our own.
- It should output examples and code snippets for both HTML and React components.
- It should be actively maintained and have successful case studies.
The tool we ultimately landed on for building the documentation was KSS (Knyle Style Sheets). With KSS, you write documentation as CSS comment blocks that conform to the KSS spec. You then run a command to parse the comments and generate a documentation site from them.
There were several things we liked about this setup:
We knew it would be a lot of work to develop both the design system and a documentation site simultaneously. KSS allowed us to document things directly within the CSS files as we went along.
React components are also documented using inline comments, and we use react-docgen to enable this. The output returned by react-docgen is then merged with KSS’s JSON. The JSON representation of each page is then passed into a React app as props and rendered as a static HTML page using ReactDOMServer.
An interesting local development workflow is unlocked when you combine this idea of inline documentation with live browser reloading. For the live reloading side of things, we’re using a combo of Browsersync and Webpack’s Hot Module Replacement.
You can have your code editor side-by-side with your web browser and see the documentation page for a component take shape as you edit a single file:
Taking this a step further, some markup examples within the documentation include the option to toggle between different breakpoints. This is useful for testing the responsiveness of components. For example, the grid framework includes classes that only kick in at certain breakpoints, allowing you to control the number of columns an element spans at different viewport widths.
Tracking performance and maintainability
Another area of the local dev workflow we’ve experimented with is how we surface stats that relate to performance, maintainability, and consistency. One way we’ve done this is through integrating the [CSSStats Node package](https://www.npmjs.com/package/cssstats “CSSStats Node package”] into our Gulp build process. We then present these stats in a table, comparing them against the latest public release in the master branch:
Another part of the stats output is a specificity graph, which helps us visualize the CSS’s complexity and structure:
Multiple NPM packages
One of our goals for the design system is to make it easy for teams to pick and choose just the parts they need for their project. That’s one reason why we’re using a modular CSS architecture, having each component or utility in its own file. This also led us to break the design system down into multiple NPM packages. We currently have three:
- A core package contains the bulk of things, like CSS and React components, utility classes, and fonts. In the future, we might break these out into more granular packages, but the “core” package will remain as a convenient way to install a bunch of things at once.
- A layout package contains the Flexbox grid framework. This is installed separately since some teams might prefer another grid framework, or need to support older versions of IE.
- A support package contains the design system’s Sass variables, mixins, and functions. This is used internally as a way to share these pieces between all the packages. It’s also available for other teams in case they’d prefer writing their own Sass declarations.
Shoutout to GitHub Primer, which takes this multiple packages idea to another level.
Though the design system is broken down into multiple packages, everything is managed as a monorepo—everything in [a single GitHub repo](https://github.com/CMSgov/design-system “a single GitHub repo). This provides some nice things, like:
- Tooling can live in a central location and be shared across all packages. This means there’s a single linting, testing, and build process for all of the packages and the documentation site.
- Local development is simplified: npm start
- Easier dependency management. [Lerna](https://github.com/lerna/lerna “Lerna) is a great tool for managing a monorepo. With a single command it installs each package’s dependencies and links any cross-dependencies: [lerna bootstrap](https://github.com/lerna/lerna#bootstrap “lerna bootstrap)
Like most NPM packages, the design system follows Semantic Versioning (SemVer). A tricky thing we encountered is that Semantic Versioning defines rules for versioning software, where minor, major, and breaking changes is pretty apparent. In the context of a design system, where there are visual components, these distinctions are less clear.
Patch release: Bug fixes and other minor changes.
- Backwards compatible Sass bug fixes
- Tiny visual changes to make the UI more consistent
Minor release: Backwards compatible new functionality, newly deprecated APIs, or substantial new functionality/improvements to private code.
- Addition of a new component
- New classes, global variables, mixins, functions, or deprecated code
- Minor visual changes to existing components
Major release: Changes which break backwards compatibility.
Example changes: *Renamed or removed classes, mixins, functions, or global variables.
- Major visual changes to existing components
Besides continuing to grow the design system itself as we identify user needs, there are additional pieces we’re hoping to add to our toolbox soon. We already have Sass and JS linting and unit tests setup, but our automated tests can be taken up another notch.
We’d like to introduce visual regression testing so we can more easily track visual changes. We’re excited to experiment with Puppeteer, a Node library which provides a high-level API to control headless Chrome.
Though tooling alone can’t catch every accessibility issue, axe-core, and Lighthouse are some tools we’re investigating for helping automate some of our accessibility and performance audits.