Gemini

Gemini image

Gemini is OpenSesame's design system. In 2019 I was asked to build a component library for an Angular application and, along with our designer, began constructing a design system. We already had a component library of sorts, but what we wanted, with new products on the horizon, was a templated styling solution.

Initially, we built a JSS-based theming engine, a way to apply templates to Angular components in an NPM package. We wanted to support the company brand identity, establish a baseline of atomic components and styles, and change them through JSON themes.
The theming engine, written in Typescript, and the Angular components remained separate, although distributed in the same package. This would work to our advantage later, but it was all precompiled for the Angular AOT compiler at the moment, so a single package was necessary.

A year later, we had a component library, a theming engine, and an Angular application needing updating. As things will do, we encountered additional requirements and platforms. We began work on the next version of our design system with the goal of a platform agnostic, themable, token-driven system that would work with any framework or no framework.

So how do you support React, Angular, Vue, WordPress, any static page generators, or nothing at all? It was a good question, and I spent a lot of time investigating web components before finding it more difficult than it should be. On the one hand, Angular components are very close to the DOM and are essentially web components. Additionally, Angular provides tools to create web components from Angular components. So we could take our Angular components and export them as web components. Except, we couldn't use them in other situations without coercing them somehow. React, at the time, didn't work well with web components, and while I found a way to make it work using Lerna and some web components to React component tools, it was unwieldy.

Supporting every target platform would require a wrapper around web components. Not always, and it wasn't strictly necessary, but maintaining data binding was essential to me. Web components are like any other HTML element, their attribute values are serialized, and I was building for applications that were easier to deal with when passing complex data structures to component properties via markup. Right or wrong, this was the pattern in use, and abandoning it would mean unnecessarily rebuilding or refactoring large portions of the existing application.

The complexity of distributing targeted components became a problem as well. How many NPM packages does anyone want to maintain? Everything about web components was insufficient or disappointing. A different approach was needed.

The power of Gemini is the theming engine. It can create styles from a theme and transpile them into CSS, and CSS works everywhere. So why not ditch the components altogether? Every framework has a community of developers providing components. There are libraries of headless components, components without styling that I could leverage, thus saving the effort of maintaining component libraries for every framework. We kept the Angular components for legacy use, exported the component styles for headless components or plain HTML, and extended the theming engine to provide themes for specific libraries instead of platforms. For example, Gemini can export articles for MUI, a popular component library for React.

The result was a lean, lightweight styling solution for anything. However, the Angular components could not go away. They had to remain for the existing application where they were used across the codebase, and it all had to be precompiled to work with the Angular compiler. This was bad news for our NPM package because it had to be tree shakeable to work with anything other than Angular. I could have made 2 packages, but I used Rollup and put everything in one package. For Angular, there is the precompiled bundle that the platform wants. For other frameworks, like React, there are path imports, so everything not imported is shaken out. Angular became an optional dependency, and CommonJS exports were added to the bundle for applications without frameworks.

With a simpler solution in place, a way to ignore Angular, and the ability to work with third-party component libraries, there was still more to do. The theming engine was extended to generate static CSS from the theme-driven styles. Now we have a CSS framework that, when used with something like Bootstrap, is a viable solution for WordPress and anything that needs basic CSS or HTML pages with no imports or templates.

While doing all that, the design team added tokens to Figma and connected them to Github. I set up a Github action so that when Design pushed new token sets to the repo, the action would convert them to StyleDictionary tokens. This is what the theming engine uses to create themes.

So there it was. A token-driven design system connected to Figma is available to any platform for use anywhere. When the design team changes or updates the style tokens, anything using the design system updates itself. A developer can add Gemini to a project, select a component library or build their own, and the styling happens. Everything comes tested and ready to use. It's a good solution, and I stand by it.

https://gemini.opensesame.com