Translating Design Principles into Scalable Code

A real-life roadmap for building a design system — Pt I

Kim Hart
JW Player Engineering

--

Welcome to the first installment of a 3-part series outlining how JW Player’s small team of front-end & design nerds leveraged a homegrown design system and web component library to modernize our suite of products.

If you’re new to this realm, I encourage you to start with some prerequisite material like Emma Bostian’s Design Systems Foundations to get up-to-speed.

The goal of this series is not to provide design philosophy or help you integrate with giants like Material Design, IBM Carbon or AirBnb, though they were each considered in our research. Instead, we’ll dig into the strategies, pitfalls and successes of our small-but-mighty custom solution.

The Challenge 💡

In our earlier days, design implementation on the front-end of our products wasn’t all that strong. Our CSS files were massive, variables were defined in every corner of every project, and there wasn’t a central home for patterns and assets that engineers rely on to deliver high-quality UI.

While this situation isn’t uncommon, it’s also a guaranteed path to incurring UX inconsistencies and a lot of tech debt. As our many engineering teams continued to scale, undoing this tangle and creating a single source of truth became our #1 priority.

Creating Neverland ✨

Our designers had already established a set of design principles and patterns within Sketch—the trick was making those patterns accessible for engineers.

Thanks to a lightweight documentation-site generator called docsify, we quickly spun up a home for our new design ecosystem with just a handful of markdown files, an index.html and some minimal CSS. It contains visual representations of our assets, overall usage guidelines, and links to our master Sketch files.

The JW Player Design Team ran with the Peter Pan theme after launching two internal prototyping tools called Smee and Tinker Bell, so it was only natural that our design system became known as Neverland.

Brand colors and iconography docs in Neverland

Because Neverland is built from markdown files and doesn’t rely on complex source code, its contents are easily managed by the designers—freeing up engineering resources and helping to maintain the source of truth.

Data color schemes in Neverland

Introducing Hook ⚓️

In order to make Neverland more than just a set of fancy docs, we built Hook: a design library consumable from all projects that outputs our foundational assets and values. It includes:

  • Brand, system, and data colors: all AA-complaint and based on WCAG 2.0 standards
  • Iconography: individual SVGs and product-specific sprites
  • Typography: font families, weights, sizes

Hook is available via CDN or as a yarn or npm package. Most projects use it by installing the package from our private registry:

yarn add @design/jw-design-library

and importing the required files ad-hoc:

@import "@design/jw-design-library/dist/less/brand-colors.less"
@import "@design/jw-design-library/dist/less/system-colors.less"
@import "@design/jw-design-library/dist/less/fonts.less
.my-cool-icon {
fill: @ds-color-brand-midnight;
}
.my-cool-error-message {
color: @ds-color-system-red;
font-family: @ds-global-font-family-custom;
}

Simple vanilla JS projects can use it by importing the relevant CDN link in their document <head>:

<link href="https://hook.jwplayer.com/jw-design-library/{version}/css/fonts.css" rel="stylesheet" />

Hook is tagged & versioned on GitHub and published via npm and our internal CDN route, allowing users to target a specific versions of Hook and have total control over when and how they update.

// package.json"dependencies": {
"@design/jw-design-library": "5.3.0"
}

Defining Design Tokens

Hook’s lean output is part of what makes it universally-accessible: in other words, it doesn’t weigh down the performance of a project that uses it. Instead of hosting CSS declarations and mixins — anything that needs compiling—Hook is configured with Amazon’s Style Dictionary, which parses semantic design tokens (key-value pairs) and outputs only variables.

Aside from performance wins, the extra beauty of this approach lies in the completely customizable yaml config files.

To understand how all of this ties together, first let’s take a look at the four main categories of our project.

  1. /assets — .tff font files, individual SVGs for icons & logos
  2. /propertiesyaml definitions & values for fonts, colors, icons
  3. /configyaml files that handle name-spacing & output locations
  4. /dist — Output containing .woff font files, sprites & SVGs, and variables (we support CSS, Less, SCSS and Javascript formats)

Naming Variables

Constructing your variable names based on a style dictionary like ours can be tedious, but the consistency it enforces is well worth the upfront effort. We’ve built our configs to output long, yet predictable, names that almost read like sentences. We’re of the mindset that long + verbose is better than short + obscure, especially because engineers have access to autocomplete packages like SCSS IntelliSense to help with typing longer values.

Each variable is prefixed with ds- (design system) so we can easily tell them apart from other project-specific variables at a glance.

Here’s a simplified example of how we’ve named our colors:

$ds-color-brand-dahlia;
$ds-color-brand-dahlia-dark;
$ds-color-brand-dahlia-darker;
$ds-color-brand-dahlia-fade;
$ds-color-system-red;
$ds-color-system-green;
$ds-color-system-blue;

…and fonts:

// Typefaces$ds-global-font-family-custom;
$ds-global-font-family-code;
$ds-global-font-family-system;
$ds-global-font-family-data;
// Weights$ds-global-font-weight-custom-regular;
$ds-global-font-weight-custom-semibold;

Generating Color Variables

Let’s take a high-level look at how we actually end up with these scss variables for our brand colors.

  1. We define our names & values in the brand colors file
  2. We tell style dictionary what format to use, what to call the files, and where to put them
  3. The build script does some magic under the hood and creates a consumable file in the/dist folder with all our new variables

Generating Icons

Icons don’t require variable names, but they easily fit into the style dictionary paradigm. Our configs export both individual icons and combined SVG sprites: one for the video player and one for the dashboard.

Let’s use these three icons called “play”, “preview” and publish” to see how they get processed into the overall dashboard sprite.

Icon examples
  1. Store single SVGs for each icon in our assets folder
  2. Define tokens per icon. In this case, its name and path to the local SVG file
  3. Define our sprite config: necessary formatting, names and destinations
  4. The build script runs each matched icon through SVGO, then converts the SVG into a <symbol>. After all icons in the group are optimized, a wrapper is added and the file is output as a sprite
Icon sprite generation example

Using Icons

We can now reference these icons in a few ways. Our web component library (more on that to come!) imports each icon individually and processes them into a flexible Typescript-friendly component that handles the correct image via a name prop/attribute.

Alternatively, you can include the sprite in your project and reference individual pieces of it with use href syntax. The contents of the sprite need to be included somewhere in the body, just once:

<svg xmlns="http://www.w3.org/2000/svg" id="ds-sprites-dashboard" style="display:none">
<symbol viewBox="0 0 24 24" id="ds-icon-dashboard-play"><path d="M20.11 10.34l-12-8A2 2 0 0 0 5 4v16a2 2 0 0 0 3.11 1.66l12-8a2 2 0 0 0 0-3.32z"/></symbol>
<!-- more <symbols>... -->
</svg>

… and then you can reference individual icons by id like so:

<svg class="my-cool-icon"><use href="#ds-icon-dashboard-play" /></svg>

Extending Hook

The examples I’ve provided here only scratch the surface of what Hook supports; we’ve also built in layout values for our 8px grid and linked extension projects that handle D3 color interpolation, custom docsify themes, and pre-baked Pendo templates.

The Payoff 🎉

We’ve experienced measurable improvements since the implementation of Neverland + Hook — including faster speeds, fewer visual bugs, and an overall decrease in tech debt and engineer headaches.

It’s especially powerful to leverage a framework-agnostic system that supports everything from our core products, to marketing demos, to internal hack week projects.

Next Up… 🧚‍♀️

Tune in for an in-depth look at how we’ve improved our dashboard by combining Hook—our extensible design library—with a custom Typescript-based web component library that serves vanilla JS, React, and Angular projects.

Part II

--

--

Kim Hart
JW Player Engineering

Front-end engineer | design enthusiast | ISFJ | west coast kid | Brooklyn, NY