I'll create a tabs component that keeps all tab content in the DOM at all times, only hiding inactive tabs visually instead of unmounting them.
I've created a custom version of the shadcn/ui Tabs component that keeps all tab content in the DOM at all times. Here's what I modified:
The key changes are in the TabsContent
component:
data-[state=inactive]:invisible data-[state=inactive]:opacity-0 data-[state=inactive]:absolute
to hide inactive tabsdata-[state=active]:visible data-[state=active]:opacity-100 data-[state=active]:relative
to show active tabstransition-opacity duration-200
You can use this component exactly like the standard shadcn/ui Tabs component, but with the added benefit of persistent DOM elements.
The issue is with the TabsPrimitive.Content
component from Radix UI, which unmounts inactive content by default. My previous solution only applied CSS styling but didn't prevent the unmounting behavior.
Let's create a completely custom implementation for the TabsContent
component that will keep all content in the DOM:
This implementation completely solves the problem by creating a custom tabs system that keeps all content in the DOM:
Custom TabsContent Component: Instead of using Radix UI's TabsPrimitive.Content
(which unmounts inactive content), I've created a custom TabsContent
component that:
div
with proper ARIA attributes for accessibilityContext-Based State Management:
TabsContentContext
to share the selected tab value with all TabsContent
componentsProper Accessibility:
role="tabpanel"
, aria-labelledby
, etc.)aria-hidden
for inactive tabstabIndex
valuesCSS for Visual Toggling:
invisible
, opacity-0
, and absolute
for inactive tabsvisible
, opacity-100
, and relative
for active tabsYou can test this by typing in the textarea in Tab 2, incrementing counters in different tabs, and switching between tabs - all state will be preserved.