Feature: Tabs dynamic defaultValue
🚀 Feature
Docusaurus does not have a good API to support dynamic Tabs defaultValue, particularly where the value can only be computed on the browser.
The following code will lead to a SSR/client defaultValue mismatch for MacOs users:
const isMacOS =
typeof window !== "undefined" && navigator.platform.startsWith("Mac");
<Tabs defaultValue={isMacOS ? "ios" : "android"}>
<TabItem value="android" label="Android">
Android content
</TabItem>
<TabItem value="ios" label="iOS">
iOS content
</TabItem>
</Tabs>
The result is that "android" will be used on the server/SSR, and React will try to hydrate with "ios".
On the client, React will have "ios" in comp state, but display "Android" in the DOM.
Even worst: we can't press the unselected "iOS" tab because setState("ios") is no-op for React when state already has this value: bug reported by @Simek 3 times on RN website: https://github.com/facebook/react-native-website/issues/2771
Problem visible here (requires macos + empty localstorage/incognito mode): https://deploy-preview-2804--react-native.netlify.app/docs/running-on-device
This issue only surfaced recently due to some recent React hydration optimization, by removing a duplicate React rendering that used to "fix" this issue.

I'm temporarily restoring the duplicate tabs rendering on hydration in https://github.com/facebook/docusaurus/pull/5652, so that it is fixed, but we need a proper design to solve this problem without any duplicate rendering (or at least avoid this duplicate rendering if the tab is the same on server/client)
Have you read the Contributing Guidelines on issues?
yes
Has this been requested on Canny?
no
Motivation
Officially support and document dynamic tabs defaultValue
API Design
To avoid any potential hydration nasty issues, we must ensure that server/client have the same value during SSR/hydration. The tab should only be updated after React has successfully hydrated.
I'm thinking of something like:
const isMacOS = () => navigator.platform.startsWith("Mac");
<Tabs defaultValue={(isBrowser) => isBrowser && isMacOS() ? "ios" : "android"}>
<TabItem value="android" label="Android">
Android content
</TabItem>
<TabItem value="ios" label="iOS">
iOS content
</TabItem>
</Tabs>
Also, seeing first android, and then having the UI magically switch to iOS after hydration is a bit weird for the user. As tabs are rendered eagerly by default, we should try to find a way to make the user see the ios tab even before React hydrates (this may not be simple 😅 ). Problem visible here:
- https://deploy-preview-5652--docusaurus-2.netlify.app/tests/pages/tabs-tests
- https://deploy-preview-2739--react-native.netlify.app/docs/running-on-device
Related links
- 2024 conversion asking for a solution: https://github.com/facebook/docusaurus/discussions/10373