Not so long ago, user interfaces for the web were built mostly using standard HTML and CSS. The rise of frameworks such as React, Vue, Angular and Polymer made it possible to wrap and re-use UI components across parts of the same application or even between products.
Components found their perfect match with Design Systems, and allowed them to evolve from style guides or reference style sheets to full-blown libraries with modular assets containing Application Programming Interfaces (APIs) for setting a component’s appearance, content and behaviour.
Design systems can be implemented as component libraries or CSS-only stylesheets. While both have their pros and cons, in this post I will focus on the component-based approach.
Among the many advantages of using components over CSS-only libraries, these are the ones I consider the most valuable:
Design can be kept consistent by only exposing the parts which are supposed to be customised.
Product code becomes easier to read and maintain through the encapsulation of logic, styles and markup inside components.
Conflicting class names and other definitions can be avoided since they are isolated inside the component’s scope.
As an example, let us take a look at Material Design’s button implemented both ways. If only label, icon and type are customisable, a component approach only exposes the necessary APIs while a CSS-only approach exposes all the complexity:
Since APIs are not visible to the final users, it may not be clear why UX designers should be involved in designing them.
Components are first assembled by product teams before reaching the final users, and the API is the main interface between a component and product designers or developers consuming it. From this perspective, developers and designers are the first users of a Design System and their experience should be considered as well.
A well designed API will increase the developer’s experience, reducing the risk that they will implement their own solutions and therefore increasing productivity and enhancing the consistency of the final product.
When designing a component and its corresponding API, some key factors should be considered to ensure they are easy to consume, consistent with the rest of the system, and easy to scale and maintain in the future.
With an overview of all possible use cases, designers can help define which parts of a component should be customisable through the use of properties and which should be kept stable, avoiding unwanted variations and therefore enhancing design consistency.
On the image below, label and icon are customisable, while icon-color and removable are not designed to be changed.
Since consumers do not have access to the encapsulated content, customising styles can only be done through APIs.
CSS variables can be used for changing single css values (e.g. --border-radius). In case multiple styles should be changed together for a given variation (e.g. alert type changing icon color and font size) a property could be used instead.
Variables can be defined as a global theme (e.g. --accent-color) and modified for a single component, or new variables can be defined only for a given component (e.g. --footer-padding)
Components and their APIs evolve over time as new use cases arise. For this reason, they should be designed for change, based on facts or forecasts of how use cases may evolve.
An API that is not designed with evolution in mind will likely result in breaking changes when new use cases come up.
On the image below, the warning variation of the dialog could be defined as a warning boolean prop, but if error or success use cases are expected to come up in the future, it could instead be defined as a type="warning" string prop.
Complex components are hard to consume. To simplify a component’s API, it is good practice to isolate smaller, reusable elements.
These elements can be wrapped inside the parent component or manually added by the consumer as child elements, depending on the amount of variation expected (see paragraph about slots below).
Not all functionalities of a component need to be pre-defined and offered through specific APIs.
For use cases that require more flexibility, consumers should be able to insert custom content inside pre-defined slots (AKA portals, containers or children areas).
Slots can define how its children elements will appear (e.g. from top to bottom or left to right, with 8px space between them), but the consumers have full control over the style of the inserted elements, since they are not encapsulated.
While it is extremely important that components are easy to use for the final users, developers and designers should be considered first-hand users of Design Systems and component libraries, and designing APIs that are easy to consume will significantly improve their experience.
Designers that understand how components API work can make more meaningful decisions when defining a component, and this will enhance the communication with developers as well.