Design System for the web
At BlaBlaCar, the front-end team is currently building a design system for the web, hand in hand with the UX team.
To give you a bit of context we’re also working on a Single Page App that will replace our old mobile website and desktop in the near future. We’re using React for both our SPA project and our design system component library named
The first thing we wanted to have is a nice, easy-to-use, and interactive interface that can showcase all the components we create. We decided to go with Storybook for multiple reasons:
- easy access to the library for anyone in the company through an internal url
- creating a component is really straightforward
- documenting the components API in one place
- good support of React ecosystem
Separation of concerns & scalability
While our main app is using this library, we tried to see further than this, so that if another app needs the library it will be easy to integrate it.
We decided from the very beginning to create an external repository for
kirk, and version it with npm. We also set a few rules along the way to determine whether a given a component should be in or out. The best candidates to be included are the low-level components such as buttons, form elements, etc…
On the other hand, if a component contains business logic then it will be directly integrated into the main application; an example of this would be a component showing the itinerary of a ride offered on the platform.
As a rule of thumb, if a component is too connected to BlaBlaCar business then it shouldn’t be in the library. That way, the library can be used in multiple products, not only in our main product.
To be part of
kirk, all components must be agnostic of our core business.
We started working on
kirk before that the SPA project had even started.
We had to start off with what we had at that point which was a bunch of html/css/js files. But the good thing was that they already were separated into — what we could call — components, not a bad start at all. We just needed to reproduce the same behaviors using React. And that was even before we started having an actual React app, we started creating components related to UI only, really early on.
Since then, the SPA project was born and, as time went by, design & interaction changed. Today, the library is still in constant development. When we first released the library, and tried integrating into the SPA for a first feature, we saw that we were missing a whole bunch of props and callbacks.
The team building the components is the same one that’s using them, so there’s no information loss, and we can tweak them as we go.
Most often, the developer who needs a new feature, props, callback on a component, is also the same person implementing it in
kirk. There’s not a strong ownership by a developer per component. The team is owning both projects as a whole, which is a real strength, in my opinion.
Permissive vs Restrictive
We based the lib on the open/closed principle:
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
We’re trying to be restrictive (closed) on
kirk and permissive (open/extensible) as well. Let me explain, we don’t want the library users to be limited, quite the opposite actually. Our end goal is to have the users to be able to do exactly what they want in the easiest way possible.
To give you an example on being closed for modifications, we don’t allow whatever prop to be passed onto the component, and then rendering all the props that we get on the component side. We use a whitelist of props for each component. It’s
kirk ’s responsibility to provide an API that is easy to use and abstract away the complexity.
About being permissive and open for extension, on each component we allow CSS classes to be passed through, so the basic style provided by the library can easily be overridden.
It’s really a matter of balancing this all out, by asking ourselves simple questions. What do I need? How do I expose it? By doing this, will I be locked in and not being able to do what I want?
We take a11y seriously. We had the chance to build this design system from scratch, therefore we could thought of a11y ahead. It’s probably the best way to go, rather than having an a11y update on all components later on.
We’re also using unit tests to make sure that the
aria-attributes are here when needed with their correct values associated to them.
The library will be used on mobile, but also desktop, or even fridges if that’s the way you like to browse the web.
There’s no restriction on component width so that they can fit anywhere we use them. And we don’t set any media queries in
kirk, that wouldn’t really make sense, since it would be related to the layout which
kirk should have no knowledge of.
We leave this to the library users, another way to be permissive.
kirk to onboard developers who have not worked with React yet.
And it’s a great way to get familiar with React and its ecosystem, without having to learn about data flow, router, and all the other things needed to make a full app.
npm start will run the storybook server.
We’ve spent quite a bit of time also in the beginning of the project to create a generator helper to get started.
When we run
npm run kirk this is what we get:
We’ve got a couple of commands here to help the developers to create/remove components.
Let’s say we want to create a component
npm run kirk componentTwo questions are asked:
- the name of component
- the kind of component you want (with 2 choices):
Functional Component or Class Component
And it will create a nice little folder with 4 files you need:
- the actual component file with some basic boilerplate
- the unit test file
- the story file so that it’s directly available in storybook
- the style file
The generated component will look like this:
This is the generated file, with a default test failing to remind you to write an actual test.
Most of the time, we’re unit testing the component based on its props and making sure that the component is rendered correctly. We’re also doing some unit/integration tests, where we simulate clicks or other events making sure that the component will react as it’s supposed to. We’re using Enzyme to render & simulate user interactions in our tests, and sinonjs to mock our functions.
Let’s take an example on our Textfield component which is the component we use to render
On this component we have two interactions to test. The first one is that a cross icon is displayed when the input element is not empty, and when we click on it, it clears the value. And the second one will test that we add an eye icon to show password, which is basically a switch between
<input type="text"> and
CSS in JS
Yes, you might think that the pandora box is opened but actually I’m not going to talk too much about it. Today we’re using styled-jsx. When we chose the lib, we wanted to keep a syntax as close as possible to CSS.
This is the generated file:
As you can see, we exported in a branding file anything that we want to share through components and that is owned by the brand. In that file, we’ve got the colors, but also anything related to font size, spacing, transition timing, etc… With this, we want to be able to override this branding object in the app that will consume the library, to be as flexible as possible. Restrictive but also permissive, remember?
Natively, we can’t tie a CSS file to a JS file together, we had to use CSS in JS so that the CSS will be directly exported with its related component. CSS import only exists when using a module bundler and we don’t really want to add webpack and its style loaders into this library. Also, one of the reasons is that we would get a unique bundled file which will result in not being able to use tree shaking on the SPA side.
To wrap up this article, what are the next steps for
At the time of writing, we’ve got 3 ongoing topics:
- we’re discussing the idea of having a mono-repository with our Single Page App project and
kirk, but still keeping the library as a separate package so that we could import it into different apps. The main goal of that would be to share configuration (which is about the same at the moment) between those two projects.
- we are implementing typescript in