React containers, some assembly required

Ryan Seddon
Jun 20, 2019 · 5 min read

This article was originally published on designsystems.com.

Previously, I’ve written about the container pattern we use in our React components in the Zendesk Garden design system. They render no UI, handle keyboard and mouse interaction, and support RTL layouts.

Image for post
Image for post

I believe this pattern has a lot of merit but its previous incantation is buried in our high abstraction React components, meaning you have to bring down a package — styling included — just to use our no-UI containers.

Well no more! Introducing react-containers: a new open source library to help elevate these patterns into their own repo. We’ve re-written them to be smaller, smarter and to adhere more closely to the WAI-ARIA Authoring Practices 1.1.

We now expose these as hooks and render prop containers. The render prop containers are actually super light wrappers around the hook themselves.

That’s all there is to it. All the complex UI logic is handled via an easy to use hook. Note that the render prop version gives you the alternative to use these hooks in a class component. We will go into this in more detail later on.

Why?

These containers are excellent building blocks to give anyone a head start in creating accessible, keyboard controlled and RTL aware components.

Not only does this speed up development time, it also drastically improves the end-user experience by making your UI much more inclusive to all users. Inclusivity benefits everybody.

Assembling a simple Tab UI

We supply 12 containers that can help with building up your own design system or a set of custom components. All you need to do is supply the visuals.

Let’s explore creating some tabs with our useTabshook.

Example of a simple Tabs UI using useTabs hook

The main focus of this example is a single line that exposes the tab logic you need to apply to your elements.

This returns an object with two items and three “prop getters” that you apply to the corresponding elements that make up your tab interface.

The two stateful items:

  • selectedItem is the currently selected tab with the default being the first tab.
  • focusedItem is the current keyboard focused tab.

The “prop getters” — a function that returns an object for you to spread onto elements:

  • getTabProps applies the specific a11y attributes required for each tab.
  • getTabListProps applies the tablistrole attribute.
  • getTabPanelProps applies the specific a11y attributes required for each tab panel.

The three required props that need to be applied are item, indexand focusRef.

  • item is a unique name that can be a number or string — this is what selectedItem/focusedItem will return.
  • index is a number that allows the mapping of a tab to a panel when generating ids — usually just the current map index when looping over multiple tabs.
  • focusRef is a reference to the element that useTabswill call focus()on.

The other requirements of getTabPanelPropsare fairly simple:

All it needs to function is index and item which should be the same thing as what was applied to the tab.

Better visual treatment

Think of these containers as the foundational level of your components. They get the important behaviours right; you can focus on the visual treatment. Let’s explore an improved visual treatment for the same code as the above example with styling changes only.

Better UI treatment to tab design without changing tab logic

Beyond encapsulating styling using styled-components, the render of our original component has changed very little.

A step further

I recently came across a neat example of an auto-generated UI based on defined design constraints called Uibot.app. What if we could use our containers as foundational building blocks in this auto UI generator? You end up with an accessible, keyboard navigable and RTL aware randomised design generator.

Using UIBot.app designs to level up RTL and a11y navigation support

Again the code is all about the visual treatment. Behaviour and accessibility remain the same.

The switch from horizontal to vertical tabs is already handled via the hook, passed as an argument into the hook itself.

Internally, this switches the keyboard controls to vertical mode and changes the aria-orientation to vertical.

As with vertical treatment, useTabsalso seamlessly handles RTL switching and the keyboard controls respond accordingly.

The complexity of handling tabs is neatly captured in the hook. All the work is on the visual treatment and handling the states for that.

Using these containers as your base layer helps lift away what would be a complex component into something much simpler and easier to reason about.

What if I have existing class components?

If you have existing class components and don’t have the option of moving over to a function component that will work with these hooks, we also provide a render-prop container that can work nicely.

Changing a hooks behaviour

Sometimes for product or UX reasons, the behaviour of these containers might not work exactly as your users expect it to. Let’s explore the useTooltiphook and how we can stop the default behaviour on mouse enter.

The above demo will show a tooltip when the button receives focus or is clicked. We’ve suppressed the default mouse enter event.

Any event that is applied internally in our hooks uses the composeEventHandlers utility which will execute every event in order until one of them calls e.preventDefault(). So in our case the hook’s internal onMouseEnterevent is never triggered as it is cancelled by the user-supplied event.

Did you remember your ref?

One aspect to point out with these hooks is that it puts all the onus on the consumer to supply a ref to the hook and to attach it to the right element.

We wanted to keep the hooks as simple and as flexible as possible so we decided against creating refs internally and passing them back.

What’s next?

Documentation, documentation, documentation. We only have a storybook of the components right now which makes it hard to see how to use them and all of the available features.

The next task is to remove our old render-prop containers from react-components and dog food these as their replacements.

We hope you like this approach, and potentially can get some use out of them. Just like the rest of the Zendesk Garden design system, react-containers is open source and available on NPM for you to use now.

Zendesk Engineering

Engineering @ Zendesk

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store