Follow

Dachser pattern lab

Heritage: the pattern library that started me on design systems

This is where the design-system work began: a pattern lab for Dachser — components, in-file documentation with SassDoc and JSDoc, a build pipeline and a versioned changelog, built and maintained over three years.

It is private client work, so the repository is not public; what stayed with me is the approach. Documenting components where they are written, treating the library as a product with releases, and keeping the brand decisions in tokens.


FactDetail
TypeClient pattern lab (private)
RoleBuilt and maintained it, v2.18.0
ScopeComponents, SassDoc/JSDoc, releases
PeriodJanuary 2018 – November 2020 · 1,182 commits

What's in it

Docs next to the code

SassDoc and JSDoc generated from the components themselves, so documentation could not lag behind.

Released like a product

Versioned with a changelog — component libraries deserve release discipline.

The beginning

The experience that later became kickstartDS: same instincts, smaller scope.

In detail

What is actually inside it

A PDF changelog

Printing the changelog for a release was part of the process. Unfashionable, and it meant non-developers could read what changed.

Docs generated next to the code

SassDoc and JSDoc ran against the components themselves, so documentation could not lag behind the implementation the way a wiki does.

Product discipline, agency context

changes/, versioned releases and a CI spec: a client pattern library run like a product with a roadmap, not like a folder of components.

DetailValue
Scale1,182 commits over ~3 years
DocumentationSassDoc + JSDoc, in-file
CIBamboo (`bamboo-specs`)
PeriodJanuary 2018 – November 2020

Technical notes

The decisions behind it

What carried over into kickstartDS?
Almost everything structural: documenting components where they are written, treating the library as a released product, keeping brand decisions in tokens — and the recognition that the integration of a library costs more than writing its components.
Why is the repository private?
It is client work. The approach is the part that travels, which is why the page exists without linking to code.

Inside the library

Nine layers, ten entrypoints, one repository for several frontends

One repository served several independently released frontends: the Mediaroom, the corporate website, country sites and a microsite, plus shell, error and frame modules used by other products. Changelog entries are tagged [MR], [CW] or [CS] for exactly that reason, and changes that force integrators to touch their templates get their own Markup section instead of hiding in Fixed.

The pattern tree is atomic design mapped onto ITCSS. 00-settings and 01-tools hold variables, functions and mixins and deliberately emit no CSS; 02-generic and 03-objects are normalize plus grid primitives; the components live in 04-atoms (89 files), 05-molecules (176 — flyover, issue-bar, shortlist, track-trace, sharedlinks, country-switch, form-slider, likebar), 06-organisms (72, numbered 01-header … 18-survey-form) and then 08-templates / 09-pages.

There is deliberately no concat-all task. Every product line gets its own entrypoint under source/_modules/<name>/ that imports only what it needs, so the Mediaroom bundle never carries the career site's forms. gulp picks up module SCSS by glob; JS entrypoints additionally have to be listed in webpack.config.js — which is how frame.js, added in 2.18.0, ended up shipping its SCSS but never a JS bundle.

293
Pattern exports
Folders handed to integrators, named like 04-atoms-button-_button-default: compiled markup, no Pattern Lab includes, no styleguide chrome.
10
Module entrypoints
base, mediaroom, corporate, shell, shell-intentive, error, microsite, career, frame, pattern-lab. SCSS for all ten, a JS bundle for eight.
249
Handlebars templates
patternExtension is hbs. The mustache styleguide kit only renders the surrounding shell — which is also the reason Pattern Lab needed patching.
17324
Lines of SCSS + JS
Across 155 .scss and 75 .js files in source/, before any of the exported HTML and before the changelog that describes it.

What actually broke

Three years of build, browser and Pattern Lab bugs

Why is Pattern Lab patched inside node_modules?

The library ran on Pattern Lab 3 while it was still alpha (@pattern-lab/core ^3.0.0-alpha.11), and two things we needed were missing upstream.

  • Custom Handlebars helpers. @pattern-lab/engine-handlebars had no hook to load them (the nunjucks engine did), so patches/engine_handlebars.diff makes it require our root patternlab-handlebars-config.js. That file is where eq, or, calc, choose and cardTypeClass come from — the templates use them for card sizing and conditional markup.
  • A post-build hook. patches/events.diff and patches/index.diff add a PATTERNLAB_BUILD_PATTERN_END event. patches/ui_builder.diff fixes view-all pages being named after a pattern subtype instead of the pattern itself.

All four are applied by npm run postinstall as patch -f -F 0 -p0 -i … || true — a failed patch is swallowed rather than failing the install. Getting them applied reliably took several attempts; one commit is titled, verbatim, "Monkeypatch engine-handlebars in Dockerfile instead of package.json for build system compatibility".

How did critical CSS get inlined?

Every module's SCSS is compiled twice through the same PostCSS chain, split by postcss-critical-split: one pass emits output_types.CRITICAL_CSS into critical.css, the other REST_CSS into the module stylesheet. The chain is autoprefixer (last 2 versions, IE >= 10, grid on), css-mqpacker, postcss-combine-duplicated-selectors, plus postcss-clean in prod only and with conservative settings (advanced: false, aggressiveMerging: false, roundingPrecision: 3). gulp-inline-source then inlines critical.css into every pattern's HTML.

That created a rule you can still read in the SCSS: anything that must not flash needs its rules in the same file, which is why the main-nav and hamburger-button imports carry // critical definitions in file comments — and why main-nav submenus were moved into the inline CSS rather than the async stylesheet.

Why did the ES6 migration break the build?

Because the build tooling wasn't ES6-ready. The commit that fixed it reads, verbatim: "Fix the build, was broken by gulp-uglify not being ES6 compatible, changed it out for gulp-ulgify-es" — including the typo, which is how gulp-uglify-es ended up in devDependencies.

Webpack runs babel-loader with @babel/preset-env targeting last 2 versions / IE >= 10, useBuiltIns: 'usage' and @babel/plugin-transform-classes. In May 2019 the whole source/_patterns tree was converted from CommonJS to ES modules. uglify() is still commented out in jsBuildProd() to this day.

jQuery is injected at build time by webpack's ProvidePlugin ($ / jQuery), but some modules assign window.$ = $ themselves for the integrating page — "don't expose jquery in career module" is a real commit, and frame.js still carries the assignment.

What did the export task have to work around?

The export runs Pattern Lab's patternsonly build into pattern_exports/, with the general-header and general-footer partials pointed at /dev/null so no styleguide chrome leaks into the markup. The output still carried Pattern Lab's own trace, so exportDeletePatternLabTrace() deletes lines matching pattern-lab.css or the <!-- Begin Pattern Lab --> markers and rewrites asset paths (../../base → ../base).

Because of that /dev/null path the whole task is Unix-only — the README says so plainly: "compiles each pattern markup without pattern lab specific includes (⚠️ doesn't work on windows)".

A separate Pattern Lab quirk forced a hack in 2.13.4: the CS Start Page dropdown only appeared in the export when the pseudo-pattern had been saved, so it was duplicated as a real pattern. The commit calls it a temporary hack, and the changelog still lists the page.

Which browser bugs were the worst?

IE11 and Safari, repeatedly, and always in the least glamorous parts of the interface.

  • Safari gradients. The cards' white-to-transparent gradient rendered as white-to-grey — a known WebKit bug. The fix (commit "fix card gradients in safari; revert background-image on pr card") was to stop using a gradient background on the press-release card altogether.
  • IE11 polyfills. 01-tools/polyfills.js pulls in url-polyfill for URL/URLSearchParams and hand-rolls Element.prototype.matches and closest. IE also got an SVG dimension fix and a first-load timing fix for the flyover / article actions.
  • The image gallery had two bugs at once: Chrome not switching the caption on the last image, and IE showing a spurious scrollbar.
  • iOS zoom. In 2.13.4 the viewport meta became width=device-width, initial-scale=1, maximum-scale=1 to stop iOS zooming in when an input got focus. It worked; it also takes pinch-zoom away from the reader.

Shipping it

Releases, performance budgets and the integration contract

changes/ snapshots, grouped by similarity

Each release shipped a folder such as changes/2.13.6/ with an index, one page per change and the styleguide, listing every affected markup-only pattern. The diff pages group identical changes across templates — "Similar Change 0 … 23 files affected" — so a reviewer saw one change instead of twenty-three files. These are checked-in artifacts: no gulp or Makefile target produces them.

A PDF changelog, rebuilt by hand

CHANGELOG.pdf sat next to the 91 KB CHANGELOG.md and was regenerated on every version bump — a 536 KB binary living in git history. The changelog is Keep a Changelog plus local conventions: a Markup type for changes integrators must apply, [MR] / [CW] / [CS] tags per frontend, and spreadsheet ids like (MR: 185) pointing back into the client's tracking sheet.

CI with a real performance budget

bamboo-specs/ holds the Bamboo plan as Java code (Atlassian bamboo-specs 6.5.0) that reads rmConfiguration out of package.json. It runs sitespeed.io 7.7.3-plus1 over four live URLs with a hard budget: performance ≥ 80, best practice ≥ 75, accessibility ≥ 70. Chrome, simulated cable, three iterations, crawl depth 1; results went to Graphite/Grafana and were scp'd to /srv/artifacts/performance.

The API contract is a URL, not an endpoint

api/filter-search-articles.yml is a Swagger 2.0 document for something that never became an API: the Mediaroom filter and search builds this query string and then redirects to it via window.location.href. It pins down search, topic[], type[], permission_level[], time_mode (1 = all, 2 = last 3 months, 3 = last 6 months, 4 = explicit range), bookmarked, sort_by (date or relevance) and sort_order — written down so front and back end could not drift apart.