Zach Steiner
Apr 16, 2019

Tenderwolf

  • JavaScript
  • UX
  • Design Systems
  • Style Guide

Two of my client engagements with Buildit required a large scale front-end style guide and UI templating. I identified a good candidate in Fabricator. It got me most of the way there with a flexible organization scheme and ability to build whole pages from mock data. Also it is very hackable. Across two projects, I pushed Fabricator to its limits and, with a front-end teammate, we partially rewrote a large section of its internal engine. See discussion on my Enterprise E-commerce project At the end of the two engagements, I knew I needed something more to move forward.

Limitations of Fabricator

  1. No ability to define default data from a partial, so to have a good default display of a material, each template required a ton of if statements.
  2. Could only define mock data with JSON, precluding faker or API data.
  3. All mock data was shoved into a global store, but there was no way to view it as one might in Vuex or Redux.
  4. Color palette specimen included little information about accessibility
  5. Color palettes and other specimens (e.g., type, breakpoints) could not be shared with Scss
  6. No icon system solution
  7. No ability to leverage Handlebars layouts feature
  8. The navigation of every material on a single page falls apart in large libraries such as the ones I built (think 100+ pages, 100s of materials)
  9. Fabricator needs to be forked each time, so it was a pain to keep our custom version update to date across projects
  10. No search, so finding materials got challenging

Tenderwolf

We were able to add page navigation, building Sass variables, and more robust specimens to a forked Fabricator. However, other limitations were fundamental to how Fabricator works. I collaborated with my teammate Zach Metcalf to create a tool from the ground up. I served as product owner and UI designer/developer. Zach M handled the Webpack and Node side of the app.

After some experimentation with Vue.js and a variety of templating languages, we settled on a custom Webpack build process with Handlebars (kept from Fabricator).

Example Atoms

Mock Data

We built Tenderwolf to be data agnostic, it can accept JS files or JSON files. This makes the sky the limit as far as mock data. Particularly the introduction of faker.js could have saved untold time I spent rolling my own JSON files.

Further, materials (atoms, molecules, etc.) now can have default data (think default props in Vue or React) defined in front matter. Consider a button, now the text can be set as a default rather than having a if statement. The downside is that we had to create a custom helper (include), rather than using the standard partial import syntax (which works in other situations).

---
text: 'Button Text'
---
<button class="button">{{text}}</button>

/* Usage */
{{include atoms/buttons/button text="Page Wrap Button" }}

For debugging purposes, all data is readable in a console log during development.

To aid in working with data driven partials, Tenderwolf includes just-handlebar-helpers which adds a slew of common templating features missing from core Handelbars, in particular inline ternary if statements, arithmetic functions, and more robust logical operators.

Specimens

Fabricator includes rudimentary color specimens built from JSON data, but my projects required a more robust data-driven approach. Based on JSON data in the project (see below), Tenderwolf calculates color values in alternative representations (i.e., RGB and HSL) from a Hex code and accessibility values for white and black text to meet WCAG AA requirements for large and small text. Further, tenderwolf pushes the hex code to a Scss variable partial that is accessible in the project. Unlike Fabricator, where you would need to define color variables twice, Tenderwolf allows the JSON to be the source of truth for documentation and coding.

[
  {
    "variable": "brand",
    "value": "#007bff",
    "name": "Brand",
    "usage": "Main brand color. Used for links and buttons.",
    "active": true
  },
  {
    "variable": "white",
    "value": "#FFFFFF",
    "name": "White",
    "usage": "",
    "active": true
  },
  {
    "variable": "black",
    "value": "#000000",
    "name": "Black",
    "usage": "",
    "active": true
  }
]

Colors

Further, Tenderwolf also allows specification of specimens for typography (headings, paragraphs, etc.), borders, logos, and breakpoints. All are rendered on the styles page and pushed to Sass variables for use in the project. This is relatively extensible, so other specimens could be created in the future. Additionally, arbitrary Sass variables can be defined in the project, if so desired.

Typography

Icons

Tenderwolf builds a directory of SVG files into an optimized sprite file. For browser support, svg4everybody is included. Icons can be accessed through the <use> pattern. A library of all icons is rendered on the styles page.

<svg class="icon  icon--inline">
  <use xlink:href="/svg-defs.svg#icon-chevron-left"></use>
</svg>

Layouts

Tenderwolf supports multiple layouts using Handlebars layout partials, for example:

---
  title: "Sample Page"
  mockLink: "http://google.com"
---
{{!-- Uses base.html layout in /src/layouts --}}
{{#> base }}
  {{#*inline 'header-block'}}
    <script src='https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.14/lodash.core.min.js'></script>
  {{/inline}}

  {{#*inline 'content-block'}}
    <h1>
      Sample Page.
    </h1>

    {{#> organisms/cards/card }}
      <h1>Page Wrap</h1>
      {{include atoms/buttons/button text="Page Wrap Button" }}
    {{/organisms/cards/card}}
  {{/inline}}
{{/base}}

This allows flexibility of both layout and includes for pages.

Navigation to Materials

Tenderwolf borrows the directory driven navigation from Fabricator, but extends its organization to be more flexible for large projects. Materials directories can have as many sub-levels as needed:

Directory Structure

For instance, Atoms / Buttons / Button Links will render its own page.

Navigation

The top level navigation reflects the directories in the materials directory (our starter uses standard Atomic Design categories. If one were to do a scaled back Atomic Design or entirely different organizational scheme, Tenderwolf can support it.

We also implemented basic name search to more easily find materials in large projects.

Navigation

Separation of Concerns

The core CLI build tool for Tenderwolf lives in a separate repo from individual projects, so that the CLI can be updated and version independent of projects that use it. We created a starter project to speed development, so the CLI tool will be imported as a NPM library rather than having to fork the entire repo as with Fabricator.

The separation posed some challenges around error handling for mock data. Initially we assumed all specimen data would exist in the project, but once I started deleting JSON data, the build breaks. I went through and hardened the CLI to have sensible defaults and fallbacks when specimen data is not available.

Next Steps for Tenderwolf

I hope to open source Tenderwolf for interested parties. There will need to be some additional hardening, finishing, and documentation work to get it ready for primetime.

©2020 Zach Steiner