Tips for Portable Patterns

Written by Tyler Sticka on

Suitcase with stickers representing various web platforms, tools and languages

One of the promises of design systems is portability. Every new project should benefit from the patterns and principles of the previous… why reinvent the wheel each time? But accomplishing that can be tricky when it comes to HTML, CSS and JavaScript patterns.

Teams that maintain a consistent technical stack have it easier in this regard. Maybe they only develop a single product, or maybe they mandate usage of a framework they’ve invested in. Whatever the reason, their strategy seems clear: Write HTML, CSS and JavaScript for patterns as you would any project code.

But many organizations have a much more varied ecosystem. Maybe their main product was built a few years ago with Ruby on Rails and jQuery, but there’s a new skunkworks project they’re testing that’s a PWA built with isomorphic JavaScript. There’s also the static marketing site, the WordPress blog, the help center, and so on, and so on…

As technology continues to change and we uncover new user needs, it’s no wonder that our platforms and techniques often evolve from project to project. In lieu of psychic ability or a crystal ball, what strategies can we adopt to improve the portability of our front-end patterns between seemingly disparate platforms?

Separate Concerns Per Component

HTML, CSS and JavaScript are the basic building blocks of the web. But different platforms care about and process these building blocks differently. Server-side languages like PHP and .NET introduce templates for rendering dynamic HTML while imposing few (if any) requirements on CSS and JavaScript. Frameworks like React, Vue or Svelte may include their own rules or syntax for content, styles and behavior depending on a project’s configuration.

Consider organizing your patterns so that each has its own distinct template, CSS and vanilla JavaScript source files:

patterns
└── example
    ├── example.css
    ├── example.js
    └── example.hbs

This gives you the freedom to concatenate assets for traditional web projects:

@import "config.css";
@import "typography.css";
@import "patterns/example/example.css";
import { ExamplePattern } from 'patterns/example/example.js';

document.querySelectorAll('.Example').forEach(
  element => ExamplePattern(element)
);

Or to leverage the parts that make sense in a more opinionated structure:

<template>
  <div class="Example" ref="element">{{label}}</div>
</template>

<script>
import ExamplePattern from 'patterns/example/example.js';

export default {
  data() {
    return {
      label: 'Hello'
    }
  },
  mounted() {
    ExamplePattern(this.$refs.element);
  }
}
</script>

<style src="patterns/example/example.css"></style>

Patterns, like songs, are easier to remix when each master track is separated. 1

Keep CSS Components Self-Contained

HTML represents content, so it’s inherently prone to change. JavaScript represents behavior, which changes moment to moment based on user action. So it’s no accident that CSS is typically the cornerstone of any pattern library: If thoughtfully authored and maintained, it can be the most consistent and reliable of our basic building blocks.2

In terms of portability, CSS components are often held back by being overly dependent on other components or utilities. Ideally, any one CSS component should be able to serve its basic purpose even if the rest of your project’s CSS was removed.

To be clear, tiny re-usable utility classes are still a great idea. But even the creator of Tailwind CSS, one of the most popular utility-based frameworks, thinks “you should still create components.”

Document States (Even Fleeting Ones)

It’s common to document the various states of interactive patterns like buttons. What’s less common is to document any transient states that may occur temporarily, like those representing steps of an animation.

Let’s consider a hypothetical drawer pattern. It has two main states, open and closed:

But under the hood, it’s probably more complicated than that. There may be “start” and “end” states for opening and closing animations, and those may be distinct from the “fully closed” or “fully open” states. There may be accessibility considerations, like adding or removing hidden or inert properties from certain elements or restricting animation based on motion sensitivity preferences.

Documenting those steps3 helps preserve those considerations across platforms. It may also expose opportunities for developers to re-use existing transition classes instead of starting from scratch.

Show Multiple Code Samples

It’s easy for our style guides to become biased toward whatever platform we used to construct them. Serving up alternate code samples often requires extra effort, testing and review. It can feel like a lot of extra complexity to account for what may feel like “secondary” experiences.

Shopify’s Polaris guide lets you toggle between React and HTML code samples.

But that mentality does both your team and your patterns a disservice. Showcasing alternate code samples encourages cross-platform usage and reminds contributors of the potential scope of their changes.

Simplify Inclusion

Copying and pasting code over and over isn’t just repetitive and irritating, it can make maintenance a real chore. How to best minimize that effort depends on the needs of your team and projects.

For large projects or quick proofs of concept, it’s hard to beat the convenience of having a single CSS and JavaScript bundle for your entire pattern library. But having everything at once is often overkill.

It may make sense to allow for custom bundles to be generated, via a tool (see Zurb Foundation for Sites) or service (see The Financial Times’ Origami).

Origami components provide two different methods for inclusion in projects.

With many projects these days including their own build steps, we’re fans of exposing individual components via an npm package. Projects like Lerna can even let you version components individually within the same repository.

Sharing component HTML with traditional server-side platforms like PHP and .NET is often the greatest challenge. We’ve had success defining patterns in a template language that works across platforms (Liquid, Handlebars, Twig, etc.), though it takes a bit of effort to set up the integrations and scare out any inconsistencies between implementations. 4

Why Bother?

The business case for design systems may seem cynical on its surface: “Stop wasting time and money designing the same buttons over and over!” But that’s an oversimplification. The best systems don’t simply reduce effort, they empower teams to focus their energy on addressing user needs in true collaboration. It becomes a lot simpler to prioritize accessibility enhancements, readability tweaks and performance improvements when you’re starting from a flexible and robust foundation.

Portable patterns take it one step further by freeing that cumulative effort from any one project or platform.


  1. Some organizations may be able to fully embrace web components now that resources like Stencil exist to simplify server-side rendering (see this wonderful article explaining how to get started). Unfortunately, the integration path for web components isn’t as clear for traditional server languages like PHP or .NET. There are also some other potential issues with web components it helps to be aware of. 
  2. That’s not to say it isn’t important for everything to be self-contained (see the “Self-Contained” section of the Design Systems Handbook’s third chapter). But organizations often under-emphasize CSS quality control, even when CSS is the only easily shared resource between projects. 
  3. If you’re new to including animation in your style guide, this article by Val Head is a great starting point. 
  4. For new projects, you can simplify integration by adopting an isomorphic JavaScript solution, where the same JavaScript powers the front-end and back-end. 
Tyler Sticka

Tyler Sticka is Cloud Four's VP of Design, allowing him to think about design systems every day. When he isn’t setting design direction, sketching on sticky notes, experimenting with SVG or nitpicking CSS, he enjoys reading comics, making video games and listening to weird music. He tweets as @tylersticka.

Never miss an article!

Get Weekly Digests


Leave a Comment

Please be kind, courteous and constructive. You may use simple HTML or Markdown in your comments. All fields are required.


Let’s discuss your project! Email Us