David Clark develops the web

Building an Open Source React Component Why and How I Made react-aria-menubutton

Recently I set out to build a fully accessible dropdown menu-button with React. It would have been trivial to code an inaccessible dropdown menu-button in React — just toss in a little state and a click handler to hide and show the menu. But adding accessibility, accommodating screen readers and keyboard users, was not trivial; so I decided I’d try to bundle my solution into a module that could be open-sourced and shared, reused and improved by others — a self-contained React component that would be (ideally) complete, composable, flexible, and well-tested.

The creation of react-aria-menubutton (demo here) was an interesting process: I had to confront a few of the dilemmas that make UI components so hard to isolate, modularize, and share effectively. I thought it might be worth writing about those dilemmas and the solutions that I settled on.

Accessibility is Hard

Don’t let anybody tell you otherwise.

Yes, yes, yes, I know, of course, you’re right, absolutely — there are indeed some very simple, elementary best-practices that do affect accessibility (e.g. use alt text! semantic tags! colors with sufficient contrast!). And if that’s all you’re worried about, then yes, accessibility is easy. But pass beyond those basics, try to build something sufficiently complex with JS interactivity, and (unless your experience is dramatically different from mine) your research will lead you into a baffling hodgepodge of incomplete, contradictory, insufficiently exemplified, inadequately verified, usually outdated material.

(If you care about accessibility, and have some knowledge in the area, you could gain a few Twitter followers by delivering self-righteous one-sentence decrees that universal accessibility is a divine mandate and any web project falling short of it has failed because of the sloth, willful ignorance, concupiscence, and general immorality of its individual developers. Or you could be honest about this bad situation — that inadequate information and poor exemplification is thwarting the best intentions — and try to improve it.)

What I needed to build my menu-button component was an authoritative resource telling me precisely what WAI-ARIA attributes, keyboard interactions, and focus management I would need to implement in order to achieve the best possible accessibility. I am not an expert myself and do not have the resources to run my own user tests, so I needed to rely on authority. Luckily, my intended component fit nicely into one of the WAI-ARIA Design Patterns: the Menu Button widget. I decided to accept that spec and its clear requirements as my authority, without worrying further about other contradictory resources, and follow it to the letter. (It’s possible, given the apparent uncertainty and lack of consensus around these issues, that some people would disagree that this pattern is the “ideal”; but unless a higher authority provides a clear alternative, it’s the best I have to go on.)

The main reason I consider react-aria-menubutton worth open-sourcing is that it carefully implements this ARIA Design Pattern. With React (or jQuery, or just plain JS), showing and hiding some menu element with the click of a button is too simple to justify an open source component. However, I would not want to re-implement, again and again, the fine details of the accessible interaction, especially the keyboard accommodations. I think this probably applies to many other frontend components, as well: the hardest generalizable part to implement is often solid accessibility.

CSS is Unavoidable and Problematic

In the realm of UI, JS must always integrate with CSS and HTML. There is no escape from this. You cannot create a working component without DOM elements, and, in almost every case, that component will hardly look or function as intended without some CSS.

This is a major obstacle for encapsulation, which is an essential feature of effective modules. Everything in CSS is global, everything cascades. It is so easy to write CSS so badly, littered with unintentional side-effects, uncontrolled consequences, quantum entanglements. And browsers come with inconsistent default styling that developers override in unique and unpredictable ways. All of this means that the module developer cannot really know whether or not her CSS will actually work in your context. Nevertheless, almost all UI components need to come with a stylesheet, at least something minimal. And on top of that, those styles need to be easily customizable, because (outside of frontend toolkits) no two designs are exactly the same, and an effective component needs to fit nicely into an indefinite multitude of stylistic contexts.

(Maybe the advent of web components, with “scoped styles”, will eventually solve this problem; but who knows when that technology will practically affect day-to-day development.)

In my opinion, it is more important for open source UI components to provide the means for custom-styling than it is for them to include pre-written styles. In my many attempts to use third-party components, I have spent more time fighting against built-in styles than embracing them, more time annoyed by the component’s CSS than grateful for it.

With react-aria-menubutton, my attempt to alleviate this problem has several parts:

  • In the JS I inline a few styles — just a few that I think are inevitable — so that users don’t have to think about them at all.
  • Instead of working hard on a single impressive skin for the component, I provide style suggestions in two forms:
    1. A totally bare-bones stylesheet containing the minimum essential rules to “complete,” in a sense, the component’s functionality. I imagine that the user could copy-and-paste this code into her own stylesheets and then add more rules to make the thing match its context.
    2. Several examples of polished skins (toggle between them in the demo). If a user wants to take one and run with it, that’s fine; but I imagine that most people will use them as sources of ideas, extracting and modifying the rules that blend with their different-but-similar design.
  • In the markup I include a coherent set of classes that follows SUIT CSS conventions, and I document these classes in the Readme. I’m a true believer in BEM-style CSS, so I think this may be the most important part of the CSS strategy.
  • Those class names are configurable (within the conventions of SUIT) through options. Users can change the component name (e.g. .AriaMenuButton-menuWrapper becomes .MyComponentName-menuWrapper) or add a namespace (e.g. .AriaMenuButton-menuWrapper becomes .myNamespace-AriaMenuButton-menuWrapper).

Notice that my solutions to the CSS conundrum depend upon ways that I “imagine” or “hope” users will put the component to work. So it goes. As far as I can tell there is no way to fully encapsulate a UI component that requires CSS: the user will almost always have to be aware of implementation details in styles, and will almost always need to tweak those styles to some degree. That said, the approach that I’ve adopted might work for a lot of people, especially those people who have bought into some BEM-style methodology like SUIT.

JS Distribution Problems: Receiving Dependencies, Exposing the API, Compiling

The jQuery plugin has been the dominant form of open source UI components for some time. Probably one key to the success of that ecosystem has been its standardized format for receiving dependencies and exposing APIs: expect a global $ and connect your plugin to that in a specified way.

That method worked for jQuery … but it’s outdated for many of us. More modern JS workflows avoid global variables, preferring modules — which in turn require a compilation process. And React, specifically, also requires compilation if the plugin code uses JSX.

But an open source author cannot know whether the user will have global variables or modules, will configure a suitable compilation process or not. Therefore, I think, a React open source component needs to work with both globals and modules, and needs to be distributed in a pre-compiled form.

I decided it would be justifiable to assume two user types: global-users and module-users. Global-users would have any of the library’s dependencies (just React, in this case) exposed as global variables and would expect the library to expose its own global variable. Module-users would import the library as needed and expect dependencies to resolve themselves.

Because I want to author the code with modules and JSX (and ES6), obviously I need to compile to a dist/ directory. But I need to compile two formats: the global-user format and module-user format. Following a pattern I saw in react-dnd, I setup the following build process:

  • Use Webpack to compile to a dist/ directory a bundle that expects dependencies as globals and exports the library as a global. Global-users should use this.
  • Use Babel to transpile JSX (and ES6) from src/ to a dist-modules/ directory, and have index.js reference files in that pre-compiled directory. Module-users will then import these pre-compiled files when they import the npm module; so the code should work regardless of the module-user’s build process (i.e. the user doesn’t have to deal with JSX or ES6).

(There’s one aspect of this “distribution problem” that I do not think I have suitably resolved: how to optionally utilize React’s CSSTransitionGroup. The CSSTransitionGroup component is only included in React.addons, a larger dependency than the base library — so I cannot assume that it will be available, and I do not want to surreptitiously import a larger dependency than the user expects. The solution I settled on is unusual, I think: provide a transition option that expects users to manually pass the CSSTransitionGroup component if they want to use transitions (see the docs). It’s not a familiar pattern; but it does solve the problem. I wonder if other people know of any alternate, better means to the same end?)

(UPDATE: Looks like a future release of React will allow addons to be included individually, without pulling in the whole pack — so that might take care of this last confusing problem.)

Testing

UI modules have always been inadequately tested — partly, I think, because with the DOM and CSS involved (as essential parts of the module’s “API”, really), the surface to test is large and confusing; also partly because UI developers aren’t always accustomed to the value of testing.

React makes this easier, with its functional format (props go in, DOM comes out) and handy TestUtils. Testing with React is straightforward enough that it should be expected of any open source React component.

Testing is especially important for react-aria-menubutton because it aspires to achieve accessibility by following an ARIA spec to the letter. The tests need to match the spec. To ensure that this happens, I’ve scattered quotations from the spec throughout the tests, illustrating which test-cases address which specific requirements.

Summary

Here’s a little summary of the measures I took in my attempt to make react-aria-menubutton a decent open source React component:

  • Follow a WAI-ARIA Design Pattern to ensure accessibility.
  • Provide users the means to write their own styles instead of pre-written styles they will have to override (via configurable classes, a base stylesheet, and fleshed-out examples).
  • Use strict SUIT CSS class naming conventions to make styling as smooth as possible — to ensure that the component follows best-practices, regardless of what the user’s CSS looks like.
  • Compile one version of the component, for global-users, to accept global dependencies and expose the component as a global variable (in dist/).
  • Compile another version of the component for module-users, which should work regardless of the module-user’s build configuration (in dist-modules/).
  • Provide a means for the user to manually pass in React’s CSSTransitionGroup if she wants transitions (this is the one I’m uncertain about …).
  • Write lots and lots of tests.

I think it’s still an open question whether React will sprout an ecosystem of high quality open source components that add real value to applications (especially accessibility) while remaining flexible enough for reuse. The quandries mentioned above, though, are not specific to React: almost all of them will apply to any open source frontend component that has to deal the overlapping of HTML, CSS, and JS and diverse frontend build processes and workflows. A bigger question, maybe — one applicable to non-React users — is this: Can we raise the bar on open source UI components, figure out how to modularize them more effectively than is typically done with good old jQuery plugins? react-aria-menubutton is my (first) attempt to experiment with that possibility and confront its challenges. I’m eager to see what other people come up with.