Furnace Design System
An accessible, themeable, and scalable design system for e-learning products
Background
EVERFI has a large portfolio of e-learning courses, ranging in subject from Early Literacy to Workplace Inclusivity to the crowd favorite, Bloodborne Pathogen Training. Historically, courses were one-offs designed by individual teams using building blocks provided by our SDK, styling and composing them in unique ways. However, there was no feedback loop to collate these stylesheets, templates, and interactions for later reuse, so each new course build started from scratch.
Around 2018, as the company began to scale, there was a well-intentioned effort to do just that. Components were gathered from existing courses, their styles turned into variables, and custom stylesheets became themes that could be reused across product families.
In theory.
But the components were hyper-specific and inflexible. Styles were incomplete and context-dependent — font sizes were often hard-coded, a padding variable was available on one button but not another. Somehow, accordion chevrons were always pink.
For the next year, we tried to make do, but without a style or UX foundation in place, designers and writers struggled to make square pegs designed for a past course fit into round holes posed by a new audience or topic.
It became difficult to maintain, impossible to fully document, and risky to make even minor changes to. We'd have to break everything in order to fix it, so in early 2019, I began working on a proposal to rethink it from the ground up. By the end of the year, we'd built it.
Goals
- A centralized style system that supported widespread theming. No more one-off component variables or CSS seeping in from other courses.
- A fully-styled base theme with smart defaults. Documentation and QA were nearly impossible because we had no underlying styles for the components.
- Responsive layout and type with additional flexibility. We had 13 different layout components — none of which were a grid.
- Standardized iconography. Designers had to export recolored SVGs for each new theme.
- Greater emphasis on utilities. It shouldn't require a feature request to override a text color or make something italic.
- Improved naming conventions. The lack of a consistent naming convention made it difficult to predict the effect of a given class and impossible to align our Figma library styles with themeable properties.
Introducing Primitives
I worked to distill all styling needed for a theme into seven categories that would power the system. These were Primitives, a constrained set of foundational design properties that are applied to each element in the design system.
Changing one value affects all of its instances throughout the system, allowing for a consistent application of visual style in a scalable and maintainable way. Each component was refactored to use only global variables from the following families:
- Color
- Typography
- Spacing
- Border Radius
- Border Width
- Shadows
- Layout

Color
The challenge of creating a theme-friendly color system comes in threading the needle between customizability and simplicity — too many options will slow both designers and developers creating themes.
I wanted to bake as many smart defaults into the system as possible so that components and themes could shed the reputation of being unwieldy and unpredictable; instead you should only need to worry about your handful of primitives, and trust the system to put them in the right place.
Each theme's color system is constructed using four color palettes, which are limited in scope. These colors are then assigned to specific roles to ensure correct contrast and usability throughout a course.
Interactive Palette
To reduce complexity and help focus learner attention, a single primary color scale is used to indicate interactivity in a theme. These are ordered from lightest (100) to darkest (900), skipping 400 and 600 to enforce visual separation between primary and secondary elements.

Interactive colors: Core theme
Semantic Palette
Semantic status colors convey meaning through convention or repeated use outside the primary interactive color. These are chiefly used for feedback or status indicators, covering Success, Warning, Error, and Informational contexts.
These colors are intended to be distinct from a theme's interactive palette so that their presence on a page clearly communicates the intended message to learners.

Semantic colors: Core theme
Neutral Palette
The neutral palette is used for text, borders, and surface states. It ranges from near-white (100) to near-black (900) non-linearly to avoid muddy middle greys that have poor contrast with white/black.
In the base theme, neutral colors have a 4.5:1 contrast ratio with those five slots away — e.g., Neutrals 100 and 600 have a 5.74 contrast ratio.

Default neutral colors
Transparent Palette
The transparent neutral palette is designed for subdued or disabled UI elements, dividers, or accents, typically using opacities of Neutral 900, but are defined separately for greater theme control.
This separation allows theme designers to use handpicked colors for overlays and ensures that disabled colors, using a semi-transparent midtone, are visible on both light and dark backgrounds.

Default transparent colors derived from opacities of Neutral palette.
Color Roles
With few exceptions, the colors specified in the palettes are not used directly inside components. Instead, they are assigned to two sets of color roles: backgrounds and text. Each background color role has a corresponding $text-on-[color] variable with which it is paired to ensure proper contrast.

Available color role variables in a theme.

Relationship between color palettes, color roles, and components.
For example:
Although $text-on-ui-secondary and $text-interactive share the same color in the default theme, they are separate variables.
This distinction is essential because $text-on-ui-secondary always pairs with $ui-secondary backgrounds, while $text-interactive is used for links that may appear on various background colors.
By defining the two colors separately, themes can flexibly change button colors without compromising the accessibility of links within a course.
Spacing
Spacing is calculated using a base unit ($spacing-xs) multiplied by different scales on mobile and desktop breakpoints.
Themes do not have the ability to override a single component's spacing — instead, responsive margin and padding are baked into components. Changing the base value in a theme will cause the new spacing scale to cascade down to each component.

Responsive spacing formula
Typography
Our existing text classes weren't responsive, so that was a priority for any new type system.
However, it's not fully responsive — only headings are, which is by design. Many of our products, especially in the K12 space, have a number of pages with short bits of content punctuated by visuals and interactions — there was really no need to shrink the font size. Reducing bloat would ease future maintenance, while keeping prose large and legible for young users.
Plus, we could always add responsive body text in the future if we needed it. Spoiler alert: we didn't — it never once came up.

Default type scale with responsive headings. The Core theme uses Lato, EVERFI's brand typeface.
Resize the pen below to see the responsive type and spacing in action.
Layout
One of the biggest pain points in our old library was the lack of a flexible grid. While there were a number of layout components (13!), they were all variations of two-, three-, and four-column grids, and each of them simply stacked their children below the desktop breakpoint.
To replace them, we created two CSS Grid layouts: a classic Bootstrap-style 12-column grid for full flexibility, and another inspired by Material Design 2 that dynamically renders different columns per breakpoint (4 on mobile, 8 on tablet, and 12 on desktop).

Grid examples at the tablet breakpoint. Note that the Dynamic Grid maxes out at 8 columns, while the Static Grid displays 12.

Documentation comparing responsive class behaviors within the two grids.
Shadows
Shadows are defined as five gradually increasing elevations, corresponding to perceived z-index, or its relative distance "above" the page.
Themes can customize these elevations, such as using none for a flat theme or tinting shadows with a color from their palette.

Shadow elevations and z-index guidance
Borders
Primitive variables also included border-width and border-radius values. Border colors were applied using color palette variables and generated classes.

We found that only three border-width values were sufficient.

Radii variables were assigned according to a component's relative size.
Impact
Theming
Overall, we drastically simplified theming, while also elevating our visual design. Old themes contained 1,595 variables spread across 58 files — with Primitives in place, themes became a single file containing just 63 variables, while providing more comprehensive styling than we'd ever had.
For the first time, we had a fully styled base theme, Core, that aligned 1:1 with our once-aspirational Figma component library.

Core theme with fully-styled base components
This provided smart defaults for other designers and streamlined the creation of new themes, both in Figma and in code. With styles in Figma aligned to our primitive variable names, designers were empowered to plug those values into an SCSS theme template and ship it off to a developer to push.

The wide variety of themes enabled by primitives
Development
The creation of a base theme and the strict application of primitives provided a baseline that enabled regular visual, functional, and accessibility QA on each of our components prior to a release.
For a sense of the difference this made, compare the two images below from our pre- and post-primitives QA environments:


Previously, if a course wanted to use a new component, they would manually add and update its unique SCSS variables to their theme, then wait for a release before the changes would take effect. With global variables in place, implementers could simply drop any component onto a page without having to worry that its styles would be broken by a missing variable.
Behind the scenes, we instituted BEM naming conventions throughout the SCSS and refactored component templates with proper semantic markup. We deprecated 20+ components in favor of more flexible, general-purpose ones. We also upgraded our dependencies with an eye toward easier maintenance and scalability moving forward.
Internationalization
The addition of new grid, typography, and spacing systems made internationalization of courses possible, satisfying a major business priority.
With the removal of hard-coded dimensions and spacing, element sizing was now determined solely by the sum of its font-size, line-height, and padding. This approach accommodated localization and internationalization, since certain languages can greatly increase the length of content.
We also added RTL support to our grid, allowing it to flip based on the locale. Horizontal margin/padding also reverse in RTL, along with directional icons.

Testing pseudo-localization in our QA environment
Accessibility
In early 2019, prior to the refactor, 352 accessibility issues in courses were attributed to components — an average of 88 per quarter. That dropped to 14 in the quarter after our release; by the next year, there were just 2 reported bugs in the same quarter.
Prior to the launch of Furnace, none of our courses were fully compliant with WCAG Level A or AA criteria. By the end of 2020, we had five new courses that met all AA requirements, and 21 courses had reached full compliance in the next year.
A 2021 accessibility audit revealed that 87% of our components were now fully accessible. For more insight, check out my colleague Annie Alvarado's presentation for Fable Accessibility.
By the numbers
Variables per theme
62
↓ -1,533 (-96%)
Layout components
2
↓ -10 (-83%)
Unique spacing declarations
6
↓ -136 (-96%)
Color combinations
44
↓ -109 (-71%)
Responsive courses
74
↑ +65 (720%)
AA-compliant components
59
↑ +53 (2580%)