At React Conf, Maja Wichrowska and Tae Kim, engineers at Airbnb, explained how Airbnb’s design system has evolved its architecture and implementation in response to the business and technical challenges encountered.
Airbnb’s design system is driven by UX and product needs. In 2016, Airbnb Experiences were introduced publicly, and Airbnb applications and sites moved their focus away from just accomodations to the whole end-to-end trip. At the time, the Airbnb codebase was suffering from three technical challenges, enumerated by Wichrowska as fragmentation, complexity and performance.
The fragmentation resulted from engineers using any framework or library they were most familiar with. That meant a mix of JavaScript, CoffeeScript, jQuery, React, CSS, Sass and more. The complexity was driven by the growth of the codebase and the styling needs. Engineers were continually adding CSS files to override existing files. Wichrowska gave the following example of CSS files used to style buttons:
core.scss
.button {
background: #ff5a5f;
color: #ffffff
}
custom_page.scss
.button {
background: #008489;
}
yet_another_custom_page.scss
.button {
background: #a61d55;
padding: 1px 1px;
display: block
}
The root cause behind the additive behaviour is that it was difficult to track whether a CSS selector was still used by some part of the codebase. The problem was particularly acute when it came to positioning and layout. Removing existing selectors or files could result in changing the page appearance in unpredictable ways, and tracking down the culprit selector after the fact could be an arduous task. Keeping the unused CSS led to growing bundle sizes which in turn eventually led to slow page loading, in a way that performance became a key worry.
The fragmentation pain point was addressed by using only React for UI and component concerns. A Design Language System (DLS) was refined. The customization of components was achieved through components’ public interface (props in the case of React). Consistency and predictability were achieved by requiring style customizations to be expressed by props. A button with an alpaca color would for instance require a isAlpaca
prop rather than a style
or className
prop which could be freely customized.
Kim additionally explained that CSS-in-JS, by tightly coupling the component’s CSS to the component’s markup, solved the maintainability issues previously encountered. CSS-in-JS prevented style overrides, enabled theming and allowed shipping only critical CSS through a combination of lazy loading and tree shaking. This helped considerably with complexity and performance issues. As an Airbnb engineer expressed at the time, praising the DLS:
Consistency and reusability with things like responsiveness and reusability all taken care of. DLS is definitely the way forward.
Fast-forward to 2018. As the DLS adoption grew, and the range of Airbnb businesses continued to grow, pain points became more apparent. In 2018, the product portfolio increased considerably, including new tiers of accommodation with market-specific branding, expanding into business travel, concierge services, peer spaces, and more. Those new products required their own layout, typography, use of images and more.
Fragmentation reappeared. As the DLS required being updated to include new style-customizing props, every update potentially meant going through a lengthy process of deciding whether the extra prop actually fit into the DLS in the first place, and also determining whether it followed the DLS guidelines. When pressed with incoming deadlines, engineers would, rather than resort to the design system’s components, quickly write their own components, defeating the point of the design system in the first place. As a result, Kim witnessed the same component being written over and over because that was the more productive thing to do. This lowered consistency and predictability of components when it comes to styling, but also accessibility.
Furthermore, as developers added customizing props to components, the component code base grew and featured convoluted, unwieldy logic. Kim mentioned that the button component over time grew from 1 Kb to 33 Kb minified, not including external libraries and with no tree-shaking possible. Kim exclaimed:
This is a big number for a button.
Kim also scrolled through the <button>
component codebase, which featured 30+ props in its public interface, each prop having in its configuration complexity. The ability to customize components was thus creating implementation, documentation, usage and maintainability challenges. The complexity and performance pain points, previously solved in 2016, were surfacing anew.
In a subsequent iteration, Airbnb’s design system moved to a monochromatic design, making it easy for instance for different Airbnb businesses to add their own palette and differentiate themselves through the use of color. The design system also adopted a modular architecture (as favored by React) and a base + variant pattern. The base + variant pattern consisted in isolating the core parts of a component that are not subject to change from the parts that are open to customization. This translated in a component file implementing the core behavior, structure and appearance of a component (like accessibility or default styling). The base component could later be extended for customization purposes, for example modifying the appearance of the base component. A customized component however does not allow style overrides, beyond those that it already implements.
The Airbnb <button>
core component thus featured callbacks to customize behavior (onClick
, onHover
props), and structural placeholders for components trailing or following the button’s label (renderLeading
, renderTrailing
props). A primary button is implemented in a primaryButton.jsx
file. A secondary button is implemented in a secondaryButton.jsx
file. Both primary and secondary buttons extend the base button, implemented in a baseButton.jsx
file.
The chosen approach restores the capability of customizing a component behavior with the full flexibility of the JavaScript language, rather than being constrained to a hard-to-change prop interface. At the same time, while base components can be freely extended, constraints are imposed on variant components (no overrides), ensuring consistency and predictability. The chosen approach reduces the bundle size as developers only need to import the component which they use, and do not pay a bundle-size tax for features that they do not use.
The full talk is available on React Conf’s web site, and this contains further code snippets and detailed explanations. React Conf is the official Facebook React event. React Conf was held in 2019 in Henderson, Nevada, on October 24 & 25.