Base UI Support
Switch from Radix UI to Base UI for tour components using the UnifiedSlot abstraction and UILibraryContext provider
Base UI Support
TourKit supports both Radix UI (default) and Base UI for headless component primitives. This allows you to choose your preferred headless UI library while using the same TourKit components.
Installation
First, install Base UI as an additional dependency:
pnpm add @mui/basenpm install @mui/baseyarn add @mui/basebun add @mui/baseBase UI is an optional peer dependency. Radix UI remains the default and works without any additional setup.
Enabling Base UI Mode
Wrap your application (or a portion of it) with UILibraryProvider and set the library prop to "base-ui":
import { UILibraryProvider, Tour, TourStep } from '@tour-kit/react';
function App() {
return (
<UILibraryProvider library="base-ui">
<Tour id="my-tour">
<TourStep target="#step-1" title="Welcome">
This tour uses Base UI primitives.
</TourStep>
</Tour>
</UILibraryProvider>
);
}Available Packages
Base UI support is available in all TourKit packages:
| Package | Components with Base UI support |
|---|---|
@tour-kit/react | TourClose |
@tour-kit/hints | HintHotspot, HintTooltip |
@tour-kit/adoption | AdoptionNudge, FeatureButton |
@tour-kit/checklists | Checklist, ChecklistPanel |
How It Works
The asChild Pattern
TourKit components use the asChild pattern to allow custom element composition:
// Radix UI style - passes an element as child
<TourClose asChild>
<button className="my-button">Close</button>
</TourClose>
// Base UI style - uses render props
<TourClose asChild>
{(props) => <button {...props} className="my-button">Close</button>}
</TourClose>The UILibraryProvider automatically switches between these patterns based on the selected library.
UnifiedSlot Component
Under the hood, TourKit uses a UnifiedSlot component that handles both patterns:
- Radix UI mode: Clones the child element and merges props (like
@radix-ui/react-slot) - Base UI mode: Calls the render prop function with props
You can also use UnifiedSlot directly in custom components:
import { UnifiedSlot, useUILibrary } from '@tour-kit/react';
function CustomButton({ asChild, children, ...props }) {
const library = useUILibrary();
const Comp = asChild
? (library === 'base-ui' ? UnifiedSlot : Slot)
: 'button';
return <Comp {...props}>{children}</Comp>;
}API Reference
UILibraryProvider
Provider component for selecting the UI library.
interface UILibraryProviderProps {
library?: 'radix-ui' | 'base-ui'; // Default: 'radix-ui'
children: React.ReactNode;
}useUILibrary
Hook to get the current UI library.
function useUILibrary(): 'radix-ui' | 'base-ui';UnifiedSlot
Slot component that works with both Radix UI and Base UI patterns.
interface UnifiedSlotProps {
children: ReactNode | ((props: Record<string, unknown>) => ReactElement);
[key: string]: unknown; // Additional props to pass through
}Migration Guide
From Radix UI (Default)
No changes needed - Radix UI is the default behavior.
To Base UI
- Install
@mui/base - Wrap your app with
UILibraryProvider:
import { UILibraryProvider } from '@tour-kit/react';
function App() {
return (
<UILibraryProvider library="base-ui">
{/* Your app */}
</UILibraryProvider>
);
}- Update any
asChildusage to use render props:
// Before (Radix UI style)
<TourClose asChild>
<MyButton />
</TourClose>
// After (Base UI style)
<TourClose asChild>
{(props) => <MyButton {...props} />}
</TourClose>The element-based syntax still works in Base UI mode, but render props are recommended for full compatibility.
Mixing Libraries
You can nest providers to use different libraries in different parts of your app:
<UILibraryProvider library="radix-ui">
<Header /> {/* Uses Radix UI */}
<UILibraryProvider library="base-ui">
<MainContent /> {/* Uses Base UI */}
</UILibraryProvider>
<Footer /> {/* Uses Radix UI */}
</UILibraryProvider>