Building The Sandbox Design System
Building a design system that works can be tough, especially when it needs to support over 20 product teams and 100+ developers. That’s an endeavor requiring lots of special considerations. It’s been a fun journey filled with exciting discoveries and valuable lessons that we want to share.
The problem
Currently, Sandbox lacks consistency, making it challenging to maintain a brand identity. This inconsistency also causes technical and design-related issues:
- Duplication of code — a consequence of not having a centralized way of maintaining and documenting what was already developed (ie. the same button implemented multiple times).
- Inconsistency of branding — first, the absence of designer guidelines leads to error-prone designs and inconsistencies between mockups and branding; second, the interaction between designers and developers is challenging because the core branding elements are not well-defined or approved by the design team.
- Development speed — lack of reusable core components slows down development. The use of a design system eliminates the need to interpret designs and instead utilizes pre-built components adhering to branding guidelines.
To address the previously mentioned issues, we have decided to centralize the core components and elements of Sandbox products into a design system.
Design
The goal of the design system (sandbox-ui
) was to create a unified design language that could be used to quickly build new products and features while maintaining a high level of consistency and usability across all of Sandbox’s products.
The design team at Sandbox has created a set of building blocks that can be combined and customized to create new user interfaces quickly and efficiently. This approach speeds up the design and development process and ensures that all company products look and feel consistent to users.
sandbox-ui
also includes a comprehensive set of guidelines covering everything from typography to color schemes to animations. These elements are designed in Figma and organized using the atomic design methodology.
Requirements
We started setting clear goals, and the first one was to ensure that our project is accessible and works well on mobile devices. We want everyone to be able to use our platform and products easily, no matter how they access them.
But the design system is not just about our users — our developers are important too.
We aim for an intuitive API, autocomplete with IntelliSense
support and interactive documentation. We want our developers to find working with the design system easy and enjoyable
Another important requirement is that sandbox-ui
will be standalone and developed in isolation, ensuring it doesn’t mess with anything outside of it and is adaptable (allowing the overwriting of styles within the components ONLY when needed).
With these goals in mind, we started researching possible options.
Research and Development
The Sandbox apps frontend are built with Vue and Nuxt. The styling is a mix of Tailwind, vanilla CSS, and CSS-in-JS.
Due to our positive experiences with Tailwind, its extensive community and ecosystem, we developed a proof of concept for the design system using it and adopting the variant-driven components methodology, leveraging on cva.
cva is a compact JavaScript library designed for creating variant-driven components using traditional CSS classes.
Even though things went smoothly while creating the proof of concept version, we found two big issues with using Tailwind:
- File Size Issue: Tailwind typically doesn’t generate large files, but with variant-driven components, it produces class names for every variant and breakpoint combination. This resulted in an excessively large CSS file, causing performance issues.
- Consistency Concerns: Ensuring consistency across projects proved challenging without a tailwind config file in projects using the design system.
Following the failed Tailwind experiment, we decided to explore a CSS-in-JS solution. That’s how we landed on Stitches.js, and it just clicked for us right away.
Among the many advantages that Stitches.js (and CSS-in-JS) offers, what stands out for us is its capability to define design tokens seamlessly. This involves things like colors, font sizes, and spaces.
The cool thing is we can easily change how components look while still following the set design rules.
Picture this: Sandbox creating a special app for a Paris Hilton event.
Now, to spice things up, let’s say we want all the buttons on the page to be pink. But here’s the catch: we can’t create a special version of the <Button />
component in the design system just for this.
The only way to meet this business request is to override the styles. In this rare scenario, we believe it’s acceptable to tweak the internal styling of the component to meet the specific demands of the business request.
<Button "css={{ backgroundColor: '#FFC0CB' }}" />
But here’s where Stitches.js stands out.
Instead of dealing with random hex values, we can use the pre-set tokens from our theme. For instance, imagine our theme has a pink100
, perfectly aligned with Paris Hilton’s branding. In this case, we just need to pass the design token name, adding a dollar sign:
<Button css="{{ backgroundColor: '$pink100' }}" />
That’s just the tip of the iceberg. We can add more magic by tweaking styles based on screen size using the predefined breakpoints or changing the overall appearance depending on the variant.
The possibilities are endless.
For our documentation, we went with Storybook.js, which acts like a centralized hub where our team can see and play with all the different parts, ensuring they all look and act the same.
Deployment and Processes
The library repository is organized as a standard Typescript project that outputs a Vue library.
At build time, we generate a single bundle of code, which exports components, utility functions, theme tokens, etc., from the generated Javascript bundle.
Development support is achieved by bundling the type definitions and web types together with the code.
Library users can set their IDE (often an automatic process) to consume these definitions, resulting in great autocomplete and import discovery.
To keep things simple, for library development, we use the Git feature branch workflow together with conventional commits.
Releases are managed by a GitHub workflow that creates and maintains release PRs and automatic release note generation.
Merging these PRs automatically increments the library version based on our pushed commits.
It’s easy to track what has changed between versions by consulting the release notes. By increasing the major version on breaking changes, we ensure that existing clients using older builds don’t break.
Final Thoughts
To sum up, managing a design system with a large team of developers is quite tricky. It requires careful planning and teamwork.
What made it a bit tougher was that the library we were considering for styling, Stitches.js, was no longer maintained when publishing this article, so we had to build our own solution.
Hopefully, that was helpful. Feel free to share your findings!