Jan Miksovsky’s BlogArchive AboutContact

2018

Building a great combo box component is so much trickier than you'd think

Combo boxes are hard to implement well. At the most abstract level, a combo box combines a text input with a button that can invoke a popup. The popup contains a list or other data-entry element:

But even this simple visual arrangement hides many complexities. A while ago we shared the challenges building a great menu component, so we thought we’d offer a similar look at the many issues faced as we added a new family of ComboBox components to the Elix web components library.

Keyboard focus challenges

Keyboard focus proves to be one of the most challenging aspects of writing good general-purpose web components:

Beyond these points, very similar UI patterns may call for focus to be handled quite differently, making it tricky to establish reusable baseline behavior.

Where should the focus go in a combo box?

Handling keyboard focus for a combo box is particularly hard, because there are multiple potentially focusable elements. For a combo box containing a list, we want to keep the keyboard focus on the text input element, but the user may also want keys to navigate selection in the list.

If the user presses the Left or Right arrow, they most likely want to move the insertion point to the left or right. However, if they press the Up or Down arrow, it’s reasonable to assume they want to move to the previous or next selection in the list.

Moreover, the user wants to seamlessly move back and forth between typing and navigating.

Given these constraints, it seems best to always keep the keyboard focus on the input element. That means we need to listen for keystrokes that seem meant for the list (like Up, Down, Page Up, and Page Down) and tell the the list what to do via method calls. If the user clicks on the popup, we’ll let that click do whatever it would normally do (like select the clicked item), then ensure the focus is still on the input.

Controlling the input and popup with the keyboard

Keeping the focus on the input makes managing the popup harder. We usually want a popup to implicitly close if it loses focus, but in this case we won’t give the popup the focus at all.

We also face some odd challenges on mobile:

Auto-complete

Auto-complete is a common feature in combo boxes with lists. That feature is useful in other contexts, too, so we’ve implemented auto-complete in an AutoCompleteInput that we can use elsewhere.

Auto-complete presents its own challenges:

If we do manage to match the user’s input against a known list of text choices, we auto-complete the input text, and then leave the auto-completed portion of the text selected. If the user wants to enter something else, they can just keep typing, and that’ll automatically overwrite the selected (i.e., auto-completed) text.

Accessibility

We try as hard as we can to do an exemplary job supporting universal access in the Elix web components, including ARIA recommendations for combo boxes. A combo box, unfortunately, appears to hit the limits of what’s currently possible in web component accessibility.

For the time being, we do the best we can, and hope that the W3C Accessibility Object Model will open up the possibility of better accessibility.

Making ComboBox as general-purpose as possible

Our goal with ComboBox is to provide a solid general-purpose combo box class that can be used for any combination of a text input with a popup. The element featured in the combo box’s popup could be a list, or a calendar, or anything else.

To accommodate this level of flexibility, we’ve defined roles for our ComboBox family. Each role is filled by another component. For example, the base ComboBox class defines an input role that you can fill with any input-like element that should be shown in the combo box. By default, this role is filled with a standard <input> element, but you use other input elements in that role. Our AutoCompleteComboBox uses the aforementioned AutoCompleteInput, letting a combo box easily pick up auto-complete support.

So a ComboBox is being defined as quite an abstract thing: it has an input-like element, a button to toggle the popup, and the popup itself. Everything else — auto-complete behavior, the use of a list in the popup, and so on — is defined in more specialized component classes.

That flexibility lets us quickly adapt the combo box pattern to new contexts. For example, we created a FilterListBox that filters its items to: a) show only those items that match a text query string, and b) highlights the matching portion of the item text. We can then drop that FilterListBox into the list role of a ListComboBox and, voilà, we get a combo box that can filter items as the user types:

Factoring UI this way lets each component do a great job at its appointed task. In this case, we can take address all the complexity of keyboard support in the generic ComboBox component. You can then use that foundation for great keyboard support in more specialized combo box components.

Recombining components to implement new patterns

One other benefit of factoring UI this way is that components can be readily recombined to create new variations. A UI pattern that’s become common in development tools is a “list with search”. This pattern lets you quickly open a file or select a command by typing the first few letters, then selecting the best match from a list. Here’s an example from Chrome dev tools:

As you type into the input field, the list of files below is filtered to show only those whose names contain the text string. Moreover, the matching portion of the file name is highlighted.

We can quickly reproduce this general UI pattern as a web component by taking the AutoCompleteInput and FilterListBox described above, and wiring them together to create ListWithSearch:

This is an in-place variation of the FilterComboBox we saw earlier. ListWithSearch is useful in cases where the user is completely focused on selecting an item from a large set. That task focus permits us to place the input and list in situ, instead of needing to save space by hiding the list behind the popup. The user can apprehend the purpose of the UI elements more quickly, and can complete their task in fewer steps.

Building a great menu component is so much trickier than you'd think

We’ve released v2.2 of the Elix web components library, which includes some new components for menus:

We want all these menu components to feel as polished and natural as native OS menus. Native menus have a number of subtle details, and getting the UI details right turns out to be outrageously complex. Menus are a good example of the fractal nature of UI design.

Just to get started, we need to be able to position a menu with respect to a source button.

Because these positioning rules generally apply to all popups invoked from buttons — not just menus, but also things like combo boxes — we’ve enshrined responsibility for position popups relative to a source button in a general-purpose PopupSource class.

Two ways of selecting a menu command with a mouse

Most people have probably never noticed there are two different ways of using a mouse to select an item from an OS menu:

Nearly every web menu handles only the first method: selecting a menu item in two clicks. But both macOS and Windows support selecting menu items in a drag operation, which can feel faster and more responsive. If you’re reading this on a laptop, try using your mouse/trackpad now to select a browser menu command using both approaches. Observe the different feel of the two approaches. Which do you normally use?

(I seem to recall that the original Mac OS supported only menu selection with a drag, while Windows supported both methods. Windows generally had better keyboard support for menu navigation, and once Windows engineers allowed the user to pop up a menu with the keyboard and keep it open, it was probably easy for them to support the two-click method.)

The two-click method is trivial to implement, but if we want to achieve the same usability of an OS menu, we’ll want to also support the drag method. That’s hard to do, which is probably why most web apps don’t support it. (The various Google Suite apps are a notable exception.) A few of the more interesting problems:

This is all hard, but still doable, so we’re giving it our best shot. If you’re on a laptop, try opening our MenuButton demo and confirming that the menu component feels like an OS menu.

For completeness, I should point out that many web menus also handle an additional means of selecting a menu item with a mouse: the menu opens on hover, after which the user only needs to click once on the desired menu item. I hesitate to mention that approach, however. It’s my personal belief that hover menus are a usability disaster: they invariably appear when they’re not wanted, and disappear when they are wanted. The hover approach does have a distinct advantage, in that it lets the top-level menu heading itself serve as a clickable link. But I think that advantage comes at a steep usability cost.

Our two-click approach for menus should generally work on mobile devices, with some minor changes. Generally speaking, mobile menus appear when a tap ends and force use of the two-click method described above. To ensure the menu responds instantaneously, we must enable fast-tap behavior by applying the CSS touch-action: manipulation to the relevant elements.

If reading this on your phone, try opening our MenuButton demo and tap around. The menu should both appear and disappear as soon as you complete a tap.

Keyboard support and accessibility

As with all Elix components, we strive for excellent keyboard support. This benefits all users that want to use a keyboard and improves universal accessibility.

We allow users to invoke a menu button by pressing Space. The user can navigate the items in the resulting Menu with the full set of keyboard navigation keys supported by KeyboardDirectionMixin, KeyboardPagedSelectionMixin, and KeyboardPrefixSelectionMixin. Without writing any new code, those mixins give Menu support for Up/Down keys, Page Up/Page Down keys, Home/End keys, and prefix selection (e.g., type “Z” to select “Zoom”).

In making our menu components accessible via ARIA, we were helped by this excellent Inclusive Components article on Menus & Menu Buttons. The whole Inclusive Components series is worth a read.

While our Menu component generally behaves like our ListBox, the accessibility rules for menus are different than lists. The role attributes involved are different, for one thing. Another way in which menu accessibility is different than that for lists is that the overall list element can take the keyboard focus, whereas the browser expects a menu to put the keyboard focus on an individual menu item.

Happily, our mixin-based approach to components was hugely helpful in letting us create a Menu component that worked mostly like our ListBox component, but with some differences. Rather than subclassing ListBox or creating a common base class (as we might have in a traditional class hierarchy), we simply copied over the set of mixins ListBox was using, dropped the ones we didn’t need, and then created an AriaMenuMixin for menus to replace the AriaListMixin which ListBox needs. We end up with a Menu that cleanly shares 90% of the code from ListBox without any class hierarchy entanglements.

Customizability

For styling and general customizability, all these menus components have replaceable parts. So you can use a MenuButton, but swap out the elements it uses by default with our own custom elements. You could:

Bonus: a customizable select element

With our MenuButton component in hand, it was easy to create a DropdownList variation that shows the selected value as the menu button’s label. When the user makes a selection from the menu, the button label updates to match.

This effectively lets you use DropdownList as a customizable version of the built-in HTML <select> element. The native <select> can only cope with text choices, but DropdownList can handle arbitrary content — including custom elements, of course — as content in both the menu button and the menu items. See this customized dropdown list demo for an example.

Interestingly, the native <select> is a place where some users may use the drag-to-select method to make a selection — even if they’re the type of user that normally selects from an app’s menu bar using the two-click method. In other words, all the work we did to build a menu button with great mouse support also makes it possible for us to deliver a dropdown list (<select>) with great mouse support.

These details are a pain!

Getting all these details correct takes far too much time. Which is precisely why no app team should try to build a menu component from scratch! The only sane way to achieve OS-quality menu components for web apps is to share code — to pour the attention of an open component library community into menu components that everyone can use. That is why Elix exists.

Creating software in 2028 is so amazing now that we build with reusable UI components!

We’re getting much closer to a world where we can create sophisticated applications from widely reusable, general-purpose user interface components. We’re still not there — it might be another 5–10 years before that world comes to pass. But we can imagine what software development will be like once that’s happened.

Here in the year 2028, we’re lucky to share enormous public collections of reusable components implementing every user interface pattern under the sun. Every old-school UI technique you can think of — menus, carousels, shopping carts, Pull to Refresh, plus all the cool stuff that was invented in the 2020s — everything you need is already coded for you!

Just think of how good we have it now:

Isn’t user experience design in 2028 great? It’s interesting to realize that all the benefits above were already possible 10 years ago — we just didn’t realize it. All it took was for us to recognize the value of implementing common UI patterns as reusable components and start really sharing them.

Using JavaScript template literals with JSX for server-side rendering

While updating our Component Kitchen site to incorporate the Elix web component documentation, we replaced our server-side use of Preact with template literals for JSX. The result works well and could be applied in many situations, so we describe the solution here in case others are interested in trying it.

Some pros and cons of JSX on the server

Back in 2016, we decided to use plain JavaScript functions to render page content. We really liked that straightforward approach, but we eventually shifted to using JSX because the code was easier to read. The use of components as tags in JSX makes it easier to see how the page is being constructed, and code editors that apply syntax highlighting to HTML/JSX make it easier to spot simple mistakes (missing end quotes, etc.).

But using Preact on the server felt like more architecture than we needed. We’d construct a component, render it immediately to a string, then throw it away. So Preact’s support for lifecycle methods and incremental rendering were all extraneous. And using JSX in our Node code base added a compile step we wouldn’t have otherwise needed. Finally, we had to go through some gyrations to asynchronously collect the data a page needed before we could tell Preact to render the content.

Using template literals

With the advent of template literal languages like hyperHTML and lit-html, we wanted to get back to the stripped-down simplicity of plain strings while incorporating the benefits of JSX legibility.

The result is a small template literal library we call litJSX. It lets us concisely use JSX-like syntax in template literals that combine static content and dynamic content to create strings. All parsing is done at runtime; there’s no build step. Parsing is fast, and because we’re using template literals, parsing of the static content is only done once per unique template string.

const jsxToTextWith = require("litjsx");
const html = jsxToTextWith({ Bold, Greet }); // Create custom template literal.

function Bold(props) {
  return html`<b>${props.children}</b>`;
}

function Greet(props) {
  return html`
    <span>
      Hello,
      <Bold>${props.name}</Bold>.
    </span>
  `;
}

html`<Greet name="world" />`; // <span>Hello, <b>world</b>.</span>

Our components are all just JavaScript functions that take a properties object and return a string.

With any system along these lines, it’s necessary to tell the template literal function what JavaScript class names it should expect to find in the strings we give it. Some JSX template literal libraries like t7 handle this by setting configuration options on the parser.

In our case, we designed litJSX as a generator of custom template literal functions. You feed it a set of classes, and it hands back a custom template literal that recognizes those classes.

The second line above creates a custom template literal called html. We tell it that if it sees the strings “Bold” or “Greet” in a tag, then it should invoke the JavaScript functions Bold and Greet, respectively.

Simply naming our template literal function “html” lets code editor extensions for syntax highlighting know they should parse and decorate the template contents as either HTML or JSX. So we get the design-time legibility and error-checking we were after.

Server-side rendering

On the server, we write our top-level page components as functions that accept an HTTP request object and use litJSX template literals to return a string result.

const html = jsxToTextWith({ Greet });

function Greet(props) {
  return html`<p>Hello, ${props.name}</p>`;
}

function GreetPage(request) {
  return html`
    <!DOCTYPE html>
    <html>
      <body>
        <Greet name="${request.params.name}" />
      </body>
    </html>
  `;
}

// The page at /greet/Jane returns HTML saying "Hello, Jane."
app.get("/greet/:name", (request, response) => {
  const content = GreetPage(request);
  response.set("Content-Type", "text/html");
  response.send(content);
});

Asynchronous components

Server-side pages often need to perform asynchronous work before they can return a response, so we designed litJSX to handle components which are async functions. If any component in the JSX is async (returns a Promise), the tagged template literal itself returns a Promise for the final, complete result. This lets you create async component functions and await the final template result.

async function GreetUser(props) {
  const user = await getUser(props.id); // Some async function to get data
  return html`Hello, ${user.name}.`;
}

const html = jsxToTextWith({ GreetUser });
const userId = 1001; // Jane's user id
const text = await html`<GreetUser id="${userId}" />`; // Hello, Jane.

Such async components allow us to cleanly encapsulate the async work performed by specific pages on our site.

Results

We’re now using litJSX to render our entire site. The words you’re reading here have passed through a litJSX template literal! Our server code has gotten more concise and more clearly expresses our intentions, with very minimal library overhead and no build step. So far it’s holding up well.

Elix v2.0 released with support for extensively customizable components

Two months after releasing version 1.0 of Elix, we’re happy to announce version 2.0.

The main focus of this release is a new paradigm for customizing components in which complex component can expose internal elements as configurable properties. You can control the appearance and behavior of such a complex component by handing it other components it will use internally to fill various roles.

This paradigm lets us unify components that had previously been distinct, implementing them with reusable, shared code. For example, by default the Elix Carousel shows little dots along the bottom:

(Tap/click for live demo)

We can override those dots and tell the carousel component to use thumbnails for that role instead, producing a new CarouselWithThumbnails component:

As another example, the Elix Tabs supplies a default style with a classic rounded tab look:

It’s easy enough to supply a custom button to fill that same tab button role, as in this Tabs instance used as the organizing navigation element in a mobile app:

If you click the image through to the live demo, you’ll see that the main “stage” element for this navigation UI has also been changed. Tabs has a main stage that shows a single tab panel at a time. By default, Tabs uses a simple Modes component as its stage. But this stage can be replaced with another element like SlidingStage, which not only adds a sliding transition, but also support for touch/trackpad gestures to move between tabs.

That’s a level of customization far beyond what’s feasible in CSS. By using one custom element as a parameter to another, we can efficiently create different expressions of fundamental UI patterns.

But as we built Elix 2.0, we realized we could take this idea of customization through custom elements as parameters a lot further.

The mind-blowing part

When viewed at the right level of abstraction, all of the component examples shown above are the same component. These carousels and tabbed UIs don’t look alike, but at a logical level, these UI patterns both share core behavior:

All of those parts — the stage, the list of proxy elements, the proxy elements themselves — are configurable via properties.

By separating logical roles and relationships from particular DOM representations, we can find new opportunties to efficiently reuse code. Elix 2.0 delivers the shared behavior for the UI patterns above in a new component called Explorer. That can be configured on a per-element basis, or subclassed to bake the customizations in, as with the component classes showcased above.

We’ve applied the same configuration paradigm to the Elix set of overlay elements, so that Dialog, Drawer, and Popup are all built around a configurable Overlay core. We expect we’ll find opportunities to use this same pattern in many other places.

Building components around extensively configurable components like this means we can handle subtleties like accessibility and keyboard support in a consistent way, allowing us to deliver a higher-quality and more usable result. It also means that you can readily adapt Elix components for the unique needs of your application, including extensive possibilities for branding.

Read the full release notes for Elix 2.0.

Customizing custom elements with... custom elements

We’ve recently been trying a new way to let devs customize complex web components: let a component accept parameters for the custom elements that should be used inside the component’s template.

A while back we indicated that we noted that it’s hard to style web components, and that we’ve been using subclassing as a partial solution. Using custom elements themselves as parameters to more complex components opens up new possibilities for styling, as well as interesting new possibilities for customizing behavior.

Example

Suppose we have a simple spin box:

This component has two <button> elements inside its shadow. Suppose we construct this shadow from a template defined by a string:

const template = `
  <span id="value"></span>
  <button id="upButton">▲</button>
  <button id="downButton">▼</button>
`;

How do we let a developer customize those buttons? As noted in the first post linked above, the whole point of Shadow DOM is to encapsulate styles, so we can’t directly style those buttons from the outside. And while there eventually be a standard way to style across a Shadow DOM boundary, that won’t be available any time soon.

But if we’re constructing the shadow from a string, we can simply let a dev insert whatever element they’d like as the “button” element in the above template.

Exposing a component parameter to accept another custom element

That’s easy to arrange. We define a buttonTag property that can be set on a spin box at any point before the component’s connectedCallback runs:

const buttonTagKey = Symbol();

class SpinBox extends HTMLElement {

  constructor() {
    super();
    this.defaultButtonTag = 'button';
  }

  get buttonTag() {
    return this[buttonTagKey] || this.defaultButtonTag;
  }
  set buttonTag(buttonTag) {
    this[buttonTagKey] = buttonTag;
  }

  /* Plus rendering code, etc... */
}

The spin box component can then use this property as a parameter in its template, instead of hard-coding <button>:

const template = `
  <span id="value"></span>
  <${this.buttonTag} id="upButton">▲</${this.buttonTag}>
  <${this.buttonTag} id="downButton">▼</${this.buttonTag}>
`;

So by default the template looks like the original one above, and shows button elements for the arrows. But now you can pass a custom element tag to a spin box instance and ask that it be used instead.

A developer who wants to use custom buttons in this spin box starts by creating a standalone custom button by any means:

They register this as a custom element, then supply the name of the custom element to a spin box instance:

<spin-box button-tag="custom-button"></spin-box>

and the spin box will use that to construct a template that includes custom-button:

<span id="value"></span>
<custom-button id="upButton">▲</custom-button>
<custom-button id="downButton">▼</custom-button>

So the final spin box uses the developer’s custom button for the up and down arrow buttons:

Live demo

If the developer always wants to do this, they can create a spin box subclass that sets the default button element to custom-button:

class CustomSpinBox extends HTMLElement {
  constructor() {
    super();
    this.defaultButtonTag = 'custom-button';
  }
}

Advantages of making components customizable this way

A developer who customizes a spin box component this way doesn’t need to know everything about the internals of the spin box; they just make a button. (To create a good button, they can use the Elix WrappedStandardElement utility class.) Because the spin box will use the button in the right place, the button will get the right positioning and have all the right event handlers to ensure interaction with the rest of the spin box.

This kind of indirection is roughly analogous to a function that accepts another function as a parameter. In this case, we’re creating a custom element that accepts another custom element as a parameter. Complex components can expose as many element parameters as necessary.

This approach works with any web component system that can cope with a tag name that’s specified at runtime. Elix components generally use string templates (as shown above), in which case parameterizing the template is a simple matter. While React components are not (generally) web components, React has long supported similar dynamic construction of a component tree, since a JSX tag name can be a JavaScript class, and that class can be supplied as a component parameter.

Because the core unit of customization is an element, it can do anything! For example, we can create a custom button element that generates mousedown events repeatedly when the user holds down the button. This lets someone customize the spin box in ways that go far beyond what the spin box’s creator can anticipate. (See the demo page for an example.)

Summary:

Elix general-purpose web component library releases v1.0.0

We’re excited to announce that the Elix project we lead has reached its v1.0.0 milestone.

This represents the culmination of over a year of work to create a great set of high-quality, general-purpose web components for common user interface patterns. These include:

These components, and the others components in the initial Elix release, are all built from focused JavaScript mixins that cover a wide range of basics, from a lightweight React-like state rendering architecture to touch swipe gestures to managing modal and modeless overlays. Elix’s mixin architecture is what lets the project manage the complexity and subtle details hiding behind seemingly simple components.

Beyond the project’s technology, we’re also proud to be managing the project with an open governance model that includes regular core team discussions and an open Request for Comments process.

The project is already hard at work on its 2.0 release. A big focus for that release will be a system for thoroughly customizing the appearance and behavior of the key parts inside a component.