Why explicit hook-based APIs:

const toolbar = useToolbarState()
<Toolbar {...toolbar}>
<ToolbarItem {...toolbar} />
</Toolbar>

are better than implicit context-based APIs:

<Toolbar> // Provider
<ToolbarItem /> // Consumer
</Toolbar>

(and why they’re not)

A thread 👇
You’ll find the short answer in “The Zen of Python” ( https://www.python.org/dev/peps/pep-0020/):

“Explicit is better than implicit.”

But, well, this is JavaScript and React! So, let’s try to speak the correct language…
React Context is great! You can build a clean API with less boilerplate. I definitely recommend using it in your app or design system, especially if your design system is used only on apps you or your company control.
Reakit has a bunch of examples on how to use its hook-based API to build a context-based API: https://reakit.io/docs/tab/#abstracting

You can always go from explicit to implicit. You can always turn a hook-based API into context, but you can’t go the other way around.
By using context (and not exposing it), you’re restricting what the user can do with your library, which is fine, if that’s what you want. But it doesn’t lead to fewer GitHub issues. People will still ask for features or even for direct access to the context state.
If you don’t expose the context, some won’t be able to use your lib. On WordPress, plugin authors can render components in different parts of the UI using a Slot/Fill technique based on React Portal. Components may be in separate subtrees and not have access to the context.
To illustrate, this is the simplified code on WordPress:

<Toolbar> // provides context
<ToolbarSlot />
</Toolbar>

And this is the code from the plugin:

<ToolbarFill>
<ToolbarItem /> // doesn't consume context
</ToolbarFill>
ToolbarSlot renders an empty element within Toolbar and registers it so that ToolbarFill can use it as a React Portal container:

React.createPortal(fillChildren, slotNode)

ToolbarItem is not a child of Toolbar in the React tree, so it can’t use the Toolbar context directly.
They can even put your hook into a context they own and benefit from all the perks of having the state as an implementation detail: https://carbon.now.sh/?bg=rgba(171%2C%20184%2C%20195%2C%201)&t=dracula&wt=none&l=jsx&ds=true&dsyoff=20px&dsblur=68px&wc=true&wa=true&pv=56px&ph=56px&ln=false&fl=1&fm=Fira%20Code&fs=14px&lh=133%25&si=false&es=2x&wm=false&code=const%2520ToolbarContext%2520%253D%2520React.createContext()%253B%250A%250Afunction%2520MyToolbar(props)%2520%257B%250A%2520%2520const%2520toolbar%2520%253D%2520useToolbarState()%253B%250A%2520%2520return%2520(%250A%2520%2520%2520%2520%253CToolbarContext.Provider%2520value%253D%257Btoolbar%257D%253E%250A%2520%2520%2520%2520%2520%2520%253CToolbar%2520%257B...toolbar%257D%2520%257B...props%257D%2520%252F%253E%250A%2520%2520%2520%2520%253C%252FToolbarContext.Provider%253E%250A%2520%2520)%253B%250A%257D%250A%250Afunction%2520MyToolbarItem(props)%2520%257B%250A%2520%2520const%2520toolbar%2520%253D%2520React.useContext(ToolbarContext)%253B%250A%2520%2520return%2520(%250A%2520%2520%2509%253CToolbarItem%2520%257B...toolbar%257D%2520%257B...props%257D%2520%252F%253E%250A%2520%2520)%253B%250A%257D%250A%250Afunction%2520MyToolbarSlot()%2520%257B%250A%2520%2520const%2520toolbar%2520%253D%2520React.useContext(ToolbarContext)%253B%250A%2520%2520return%2520(%250A%2520%2520%2509%253CToolbarSlot%2520fillProps%253D%257Btoolbar%257D%2520%252F%253E%250A%2520%2520)%253B%250A%257D%250A%250Afunction%2520MyToolbarFill(props)%2520%257B%250A%2520%2520return%2520(%250A%2520%2520%2520%2520%253CToolbarFill%253E%250A%2520%2520%2520%2520%2520%2520%257Btoolbar%2520%253D%253E%2520(%250A%2520%2520%2520%2520%2520%2520%2520%2520%252F%252F%2520Re-create%2520Context%2520within%2520this%2520tree%250A%2520%2520%2520%2520%2520%2520%2509%253CToolbarContext.Provider%2520value%253D%257Btoolbar%257D%253E%250A%2520%2520%2520%2520%2520%2520%2520%2520%2520%2520%257Bprops.children%257D%250A%2520%2520%2520%2520%2520%2520%2520%2520%253C%252FToolbarContext.Provider%253E%250A%2520%2520%2520%2520%2520%2520)%257D%250A%2520%2520%2520%2520%253C%252FToolbarFill%253E%250A%2520%2520)%253B%250A%257D%250A%250A%250A%253CMyToolbar%253E%250A%2520%2520%253CMyToolbarSlot%2520%252F%253E%250A%253C%252FMyToolbar%253E%250A%250A%253CMyToolbarFill%253E%250A%2520%2520%253CMyToolbarItem%2520%252F%253E%250A%253C%252FMyToolbarFill%253E
So, that’s why it’s fine to use context in your app!

If you need something to be exposed or built in, just update it! If it’s a 3rd party dependency, though, you’ll have to send a PR and convince maintainers that your use case is common enough to be built into the library.
If your PR isn’t accepted or it takes too long, you’ll find yourself building your own — often inaccessible — component from scratch.
Back in the days of PHP, when I was studying design patterns with the master @netojoaobatista, I learned that good abstractions shouldn’t change to adapt to your needs. If they do, they probably have too much specific knowledge.
More than 10 years later, I’m not using object oriented programming anymore, but the principles still apply. Spreading state hooks into components is a “Dependency Injection”: https://en.wikipedia.org/wiki/Dependency_injection
On Reakit, most of the bug reports and feature requests can be solved by users themselves in their apps.

If you want to modify Tooltip so it shows with a delay and hides when pressing Escape, just do it: https://codesandbox.io/s/reakit-delayed-tooltip-uio1q
If you want to replace useTooltipState altogether with your own state logic that makes the tooltip follow the cursor, while you retain all the accessibility features that Reakit provides (showing tooltip on focus, aria attributes etc.), we won’t stop you: https://codesandbox.io/s/reakit-tooltip-following-mouse-7wolg
What about tabs that show an alert dialog asking for confirmation before leaving the current panel? You’ll have to call some methods returned by useDialogState and useTabState and overwrite the Tab’s onClick prop, but it works like a charm ❤️ https://codesandbox.io/s/reakit-tabs-with-confirmation-4uhls
Context menus are problematic in many ways, but you can enhance useMenuState with a few lines to make it possible. Once it’s open, it’s completely accessible via the keyboard just like the other Menu’s: https://codesandbox.io/s/reakit-context-menu-779g7
A few days ago I was curious if I could replicate the MacOS “Help” menu (at the top menu bar) using Reakit, without any change to the library. Here’s the result: https://twitter.com/diegohaz/status/1227334706871652354
As long as you keep the interface the same, you can replace the library state using Redux or XState by creating your own state hooks. You would still get all the a11y benefits from Reakit components. Or you can reuse the state and build your own component logic.
Of course, it’s not all flowers and sausages! The object that is returned by the hooks and spread into the components becomes part of the public API. This means that, if you change something on its interface, that’s a breaking change, and you should release a new major version.
This can be mitigated by including some kind of experimental flag. Reakit state hooks put an unstable_ prefix on properties that are, well, unstable! Those properties may change or be removed altogether on patch and minor releases without notice.
But, yes! People will use it wrong! If this is an a11y library, it’s even worse! You’ll think you’re building an accessible component. But, depending on what you do with the powers you’re given, you may be building something less accessible than if you were not using the library.
The best (or worst?) example I can think of is Reakit Rover ( https://reakit.io/docs/rover/ ). Because it adds keyboard navigation to components and seems cool, people are using it without giving the container proper semantics (that is, setting an appropriate role: toolbar, grid etc.).
You get a very cool UI where you can navigate using arrow keys, but as a consequence you lose the ability to navigate through those items using the tab key. Without proper semantics or alerts, screen readers won’t announce to the user that they should use arrow keys.
You can always overwrite the default tabindex="-1" that Rover adds to elements and have both tab an arrow key navigation. But that’s not what we see people doing.
A better solution is to emit runtime developement warnings whenever the library identifies those wrong uses. Reakit already shows warnings for things we’ve identified over the time the library is in beta. And it seems an effective way to oganically teach developers about a11y.
In general, most people will follow what’s in the docs. That’s very perceptible! But there will be always something that runs out of your control. That’s a trade-off we choose to make in order to get all the benefits.
Ultimately, this discussion applies to front-end only. Other programming areas have solved this issue several years ago with all the patterns they’ve created. But we… we’re very busy trying to center a div on the page.
You can follow @diegohaz.
Tip: mention @twtextapp on a Twitter thread with the keyword “unroll” to get a link to it.

Latest Threads Unrolled: