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.
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.
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.
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
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:
selectedItemis the currently selected tab with the default being the first tab.
focusedItemis the current keyboard focused tab.
The “prop getters” — a function that returns an object for you to spread onto elements:
getTabPropsapplies the specific a11y attributes required for each tab.
getTabPanelPropsapplies the specific a11y attributes required for each tab panel.
The three required props that need to be applied are
itemis a unique name that can be a number or string — this is what
indexis 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.
focusRefis a reference to the element that
The other requirements of
getTabPanelPropsare fairly simple:
All it needs to function is
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.
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.
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.
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.