When developing web applications, choosing a UI component library is one of the earliest decisions your team has to make. Beyond reducing the amount of functionality your team has to maintain, UI component libraries shield developers from the complexities involved in designing user interactions that are accessible and behave correctly across browsers and devices.
Choosing the right component library is nearly as important as choosing the framework in which your application is built. However, not all component libraries are made the same, and the wrong choice can haunt your project for years. Considering that a common set of components lays the foundation for all other functionality, poor design decisions within the library have a way of propagating throughout the rest of the codebase.
Whether you are using an established library like Angular Material, investigating a lesser-known alternative, or even considering an InnerSource library developed internally at your company, there are several considerations that must be made to determine whether your chosen library meets your application’s needs.
Vetting a UI component library can be broken into two phases. The first looks closely at the library’s examples, API, and documentation (high level). The second looks closely at the library’s code (low level), and is especially useful when considering a newer or lesser-known library.
The View from 30,000 Feet
The best place to start is with the library’s documentation. The two obvious questions are, does the library support your chosen design system, and does it offer a wide variety of components? If your chosen design system is not supported, then is there good documentation on creating a custom theme for your application? Also, are there any components the library does not offer that you know will be needed? Try to look at several different component libraries to get a sense of which components may be missing. A less obvious question, but still worth asking, is whether the components are controlled or uncontrolled? Uncontrolled components reduce the amount of state that must be managed directly by the application, but also limit what can be accomplished programmatically (e.g., a tour feature that requires complete control over the UI state).
Next, create a dedicated sandbox route within your application that displays several (if not all) of the components that are most relevant to your project. Alternatively, you can install a local copy of the library’s component showcase (such as Dojo’s) if one is available. With the component library installed in a local application, generate a production build and use a bundle analyzer (e.g., webpack-bundle-analyzer) to assess the impact the added dependencies have on the overall build size. If the build includes functionality your application does not need, do you have the option to exclude it from the build?
With some of the more basic questions out of the way, we can turn our attention to reviewing the library’s interoperability, internationalization, and accessibility. Afterward, we will discuss specific components that can cause problems if not implemented correctly.
Familiarity & Interoperability
In an organization with multiple applications and teams, there may already be a favored UI component library that is common across projects. Utilizing a common component library flattens the learning curve when onboarding team members, and makes it easier for developers to move between teams as needed. Of course, being common within your organization is not a guarantee that it has been thoroughly vetted; it is still your responsibility to ensure the library is safe to use for your application!
It is also worth considering whether it would be better to choose a library that is not tied to any particular UI framework, but rather is based on a technology like web components that can function identically across frameworks. This is especially pertinent to applications that are composed of several modules that are managed by different teams. In such a case, each team must apply the same design regardless of the framework used, and so having a common component library with a shared theme alleviates the maintenance burden placed on any individual team.
Increasingly, the products we build are put in front of audiences that are not restricted to a single language or culture. The component libraries we use should facilitate efforts to communicate with our users in the ways they expect. This process of preparing a code base to adapt to any audience is known as internationalization. While a complete discussion of internationalization is beyond the scope of this article, we can still verify that our component library covers the basics.
In short, all rendered text should be configurable, including that displayed only to screen readers. For example, it should be possible not only to override default form control labels or error messages, but the visually hidden text for icons like accordion expand/collapse controls, progress indicators, and dialog close buttons should be configurable as well. Further, any date or time formats displayed by a component like a time picker should be adaptable to the user’s locale.
Building inclusive web applications is paramount, but doing so correctly is not easy. One of the major benefits of a well-tested component library is that some of that complexity is abstracted from developers who may or may not have much experience with accessibility testing. Every provided component should be fully accessible when possible, and should include thorough documentation on correct, accessible usage when a one-size-fits-all approach is not possible. While there is more to accessibility than keyboard and screen reader accessibility, you should verify your component library for both. Once you have become familiar with using a screen reader, it is much easier to recognize problems your implementation will cause for that audience.
Don’t worry if you have never used a screen reader before; while they have a learning curve, with a little practice you can become proficient enough to use them to verify your UI. And using screen readers to verify your chosen component library is a great place to start. macOS includes VoiceOver natively, and NVDA is free to download for Windows. For concise getting started guides, webaim.org provides decent primers for both NVDA and VoiceOver. If you happen to be working in a Windows environment that does not allow you to download NVDA, then you can use Narrator, which is included with Windows 10.
Once you are familiar with the screen readers for your target platforms, spend some time navigating the examples, considering whether data is read in a way that will be intuitive to your users. For example, are labels correctly associated with form controls? Are notifications and alerts announced upon display? Are visual separators for breadcrumbs confusingly announced or correctly ignored? Do icons like dialog close buttons have explanatory text?
Note that while this is useful to smoke test the library’s accessibility, it is by no means exhaustive. As already mentioned, a thorough guide to accessibility testing is beyond the scope of this article, but the WCAG guidelines are an essential reference for building inclusive user interfaces.
While completely vetting a component library requires a thorough survey of as many components as possible, there are a specific few that deserve closer scrutiny due to the difficulty of implementing them correctly. Form controls in particular can be very challenging since browsers make it difficult if not impossible to design them or build upon their functionality. As a result, any custom implementation departs significantly from the native implementation.
Tables are extremely common in web applications, but what you need from a table component depends on your specific application. Does it handle scrolling smoothly? Does it enable common features like pagination, sticky columns, or cell editing? Do you actually need any of these features? At the very least, for data-intensive applications you should determine how the table performs when handling a) data that changes rapidly and b) datasets with dozens to thousands of rows.
If you know that your users prefer to scroll through large datasets, then you should verify that data is auto-loaded smoothly and at sensible intervals. Also, if you know there are instances in which specific rows or cells will be updated frequently, it is worth setting up a test service that updates mock table data at production-like intervals, and observe the impact on performance. Does the entire table re-render, or just the changed cells and rows?
Dialogs have relatively complex accessibility requirements that should be handled by the component library as much as possible. For example, upon display, focus should be moved to a focusable element within the dialog. Users should not be forced to tab through the entire document just to focus on an element within the dialog. Further, it should be possible to control which element in the dialog receives focus by default (see the WAI-ARIA Authoring Practices guide for examples of when this control might be needed). Also, focus should be trapped within that dialog (i.e., after tabbing through all focusable elements in the dialog, the first focusable element in the dialog should receive focus again). Finally, it should be possible to close the dialog by pressing the escape key.
As a bonus, does the library expose the underlying functionality used to stack and position content? It is not uncommon for applications to need a custom component that is stacked and positioned correctly (e.g., a custom multiselect component). In such a case, it is nice if the component library provides the necessary tools to compose such functionality in a way that preserves accessibility and consistency with the rest of the user experience.
As mentioned earlier, a number of native form controls are difficult if not impossible to style, and so component libraries provide versions that can be customized and enhanced. However, it can be difficult to build such alternatives without breaking accessibility. As such, basic accessibility testing of these components is recommended.
To begin, verify that the checkboxes, radio buttons, and switch controls can be toggled by clicking their labels. Then verify that these controls are focusable and that a screen reader announces them correctly. When toggled, the update should be announced either with text or at least a sound (this depends on the combination of screen reader and browser used), and switch components should be correctly announced as a “switch”.
Next, interact with the provided select, multi-select, and typeahead components. The select’s options should be announced as they are selected and deselected, and options should be announced as they are highlighted via navigation with the arrow keys. It should be clear from auditory clues whether the control is a select or a typeahead, and whether a single option can be selected or multiple options can be selected. Now, create a simple demo that displays a select inside of a dialog. When the select options are displayed, close them with the escape key to verify that only the select is closed and not the entire dialog. Finally, be aware that these components are very complex, and there is not unanimous agreement on how they should be implemented. For further discussion, see Sarah Higley’s thorough treatment.
Lastly, we turn our attention to the date picker component. Many library implementations prove to be too generic and too inflexible. As a result, all too many teams are forced to implement their own, which in turn proves to be a tremendous cognitive and maintenance burden. So, when reviewing a library’s date picker, the primary expectation is flexibility.
Beyond the most basic capabilities (custom start and end dates, disabled dates, etc.), verify that date range controls actually use two different inputs, which facilitates keyboard usage. Also, it is useful if the component allows custom CSS classes to be applied to individual dates (e.g., by consuming a function) in the event that different dates need to be highlighted according to business-specific rules. Additionally, determine whether a custom callback can be used to control how typed user input is converted to valid dates so that dates can be parsed according to the locale or even an established convention. Note that while all of these features may be not needed for your application, it is good to be prepared for them in advance.
In this final section, we shift our focus to the component library’s code itself. As noted, paying attention to the underlying code is especially useful when vetting newer or lesser-known libraries. Starting with the components above, scrutinize the code as you would during a code review. Is it well-written? Easy to follow? Will developers on your team be able to debug any issues by going directly to the source? Are there any obvious security concerns with either the code or the dependencies?
Also, pay attention to the extent and quality of the tests. While test coverage can only communicate so much about a project’s health, it can be a useful indicator both of whether edge cases have been considered and of whether future changes might break existing functionality when updating the library in your application.
If you have not already done so, clone a local copy of the library’s repository, follow the instructions to install dependencies, and then run the tests with coverage enabled. If test coverage is notably poor, then that is a good indicator that the project is not mature enough to risk in your production application. Next, review the test code of at least a few complex components (e.g., date picker, positioning logic, table scrolling/pagination). Do the tests actually verify the underlying functionality, or do they appear to serve no other purpose than to boost coverage numbers?
HTML & CSS
Often overlooked in component libraries is the quality of the HTML and CSS, as nearly all focus is centered on the number of available components and diversity of features. Poorly-written HTML can result in a confusing experience for your users, and poorly-written CSS will propagate itself to all other styles in your application.
Semantic HTML is at the core of web application accessibility. Each HTML tag and attribute was designed for a specific purpose, which browsers and assistive technologies rely on to provide the best possible experience to users. As such, the components exposed by a library need to be implemented using the correct HTML tags and attributes. Are links rendered with
a tags and buttons with
button tags? Do form control components render their labels to
labels? Do they store their state in the correct HTML element? For example, does the checkbox component maintain an internal
input type="checkbox" regardless of whether that input is visible to the user? Does the table component preserve the correct semantics, or is it rendered entirely with
aria attributes applied sparingly and only when needed, or they do appear haphazardly throughout the code base? These questions alone should give you a clear understanding of the attention paid to code quality.
A component’s CSS should clearly be separated into those styles that exist to enable the component’s functionality, and those that exist to style the component according to a given theme. If possible, completely disable the default theme. Does each component still function correctly, or do the theme files include styles that belong with the functional styles? Also, are there still traces of the theme’s design in the individual components, in which case the theme styles have leaked into the functional styles?
Next, look at the CSS for your chosen theme. How easy will it be to override the styles (and inevitably, you will need to override at least some of them)? Are there several
!importants spread across the codebase, or are the selectors more deeply nested than they need to be? If you adopt this library, will your own override styles also need
!importants and long selector chains? If you have a design ready, try to apply that design to the table component to get a sense for the quality of the CSS you will be forced to use in your application.
Finally, does the library specify and adhere to a fixed set of
z-index values? If not, then determining the stacking order for your own components will require trial and error and may not be predictable in all cases.
UI component libraries guard development teams against the hazards of building common components, which makes settling on the correct one for an application a difficult and time-consuming task. However, since the wrong choice can plague the application for its entire lifetime, we owe it to our users and teams to choose wisely. By taking the time to evaluate a library’s accessibility and flexibility, we can ensure that we are reaping the benefits of using a UI component library while containing the complexity it introduces.