Component customization system for safely ejecting and wrapping theme components.
Configuration for component swizzling that defines which components can be customized and how.
/**
* Configuration for component swizzling
*/
interface SwizzleConfig {
/** Configuration for each swizzleable component */
components: {[componentName: string]: SwizzleComponentConfig};
}
/**
* Configuration for an individual swizzleable component
*/
interface SwizzleComponentConfig {
/** Actions allowed for this component and their safety status */
actions: {[action in SwizzleAction]: SwizzleActionStatus};
/** Optional description of the component for documentation */
description?: string;
}Usage Example:
import type { SwizzleConfig } from '@docusaurus/types';
const themeSwizzleConfig: SwizzleConfig = {
components: {
'Navbar': {
actions: {
eject: 'safe',
wrap: 'safe'
},
description: 'Main navigation bar component'
},
'Footer': {
actions: {
eject: 'safe',
wrap: 'safe'
},
description: 'Site footer component'
},
'SearchBar': {
actions: {
eject: 'unsafe',
wrap: 'safe'
},
description: 'Search functionality - wrapping is preferred'
},
'DocPaginator': {
actions: {
eject: 'forbidden',
wrap: 'unsafe'
},
description: 'Internal pagination logic - customization not recommended'
}
}
};Types defining the available swizzling actions and their safety levels.
/**
* Available swizzling actions
*/
type SwizzleAction = 'eject' | 'wrap';
/**
* Safety status for swizzling actions
*/
type SwizzleActionStatus = 'safe' | 'unsafe' | 'forbidden';Action Descriptions:
eject: Copies the component to your site for full customizationwrap: Creates a wrapper component that can enhance the originalStatus Descriptions:
safe: Recommended for customization, unlikely to break in updatesunsafe: Possible but may break in future versionsforbidden: Should not be customized, may cause issuesHow plugins and themes provide swizzle configuration:
import type { PluginModule, SwizzleConfig } from '@docusaurus/types';
const themeClassic: PluginModule = (context, options) => {
return {
name: 'docusaurus-theme-classic',
getThemePath() {
return path.resolve(__dirname, './theme');
},
};
};
// Provide swizzle configuration
themeClassic.getSwizzleConfig = (): SwizzleConfig => ({
components: {
'Navbar': {
actions: {
eject: 'safe',
wrap: 'safe'
},
description: 'Main site navigation component'
},
'Footer': {
actions: {
eject: 'safe',
wrap: 'safe'
},
description: 'Site footer with links and copyright'
},
'Layout': {
actions: {
eject: 'unsafe',
wrap: 'safe'
},
description: 'Main layout wrapper - wrapping recommended'
}
}
});
export default themeClassic;Advanced TypeScript utility for extracting component props in wrap swizzling.
/**
* Utility type for extracting component props with proper handling of
* function components with no parameters
*/
type WrapperProps<
T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>,
> = T extends JSXElementConstructor<infer P>
? unknown extends P
? {}
: P
: T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T]
: {};Usage Example:
import React from 'react';
import OriginalNavbar from '@theme-original/Navbar';
import type { WrapperProps } from '@docusaurus/types';
// Extract props from original component
type Props = WrapperProps<typeof OriginalNavbar>;
export default function NavbarWrapper(props: Props): JSX.Element {
return (
<>
<div className="custom-navbar-header">Custom Header</div>
<OriginalNavbar {...props} />
<div className="custom-navbar-footer">Custom Footer</div>
</>
);
}# List available components
npm run swizzle @docusaurus/theme-classic
# Eject a component (copies to src/theme/)
npm run swizzle @docusaurus/theme-classic Navbar -- --eject
# Wrap a component (creates wrapper in src/theme/)
npm run swizzle @docusaurus/theme-classic Navbar -- --wrap
# Check safety status
npm run swizzle @docusaurus/theme-classic Navbar -- --listWhen you eject a component, it's copied to your site:
src/
└── theme/
├── Navbar/
│ ├── index.tsx # Main component
│ ├── ColorModeToggle/
│ │ └── index.tsx # Sub-component
│ └── styles.module.css # Component styles
└── Footer/
└── index.tsxWrapping allows you to enhance components without copying them:
// src/theme/Navbar/index.tsx (wrapper)
import React from 'react';
import OriginalNavbar from '@theme-original/Navbar';
import { useLocation } from '@docusaurus/router';
import type { WrapperProps } from '@docusaurus/types';
type Props = WrapperProps<typeof OriginalNavbar>;
export default function NavbarWrapper(props: Props): JSX.Element {
const location = useLocation();
// Add custom logic
const isHomePage = location.pathname === '/';
return (
<div className={isHomePage ? 'navbar-home' : 'navbar-default'}>
{/* Custom banner on home page */}
{isHomePage && (
<div className="home-banner">Welcome to our site!</div>
)}
{/* Original navbar with all original functionality */}
<OriginalNavbar {...props} />
{/* Custom footer */}
<div className="navbar-custom-footer">
Current page: {location.pathname}
</div>
</div>
);
}// ✅ Good: Wrap components to add functionality
export default function EnhancedSearchBar(props: Props) {
return (
<div className="search-wrapper">
<OriginalSearchBar {...props} />
<div className="search-shortcuts">
<kbd>Ctrl</kbd> + <kbd>K</kbd>
</div>
</div>
);
}
// ✅ Good: Eject safe components for major customization
export default function CustomNavbar({ items }: Props) {
return (
<nav className="completely-custom-navbar">
{/* Completely custom implementation */}
{items.map(item => (
<CustomNavItem key={item.to} item={item} />
))}
</nav>
);
}
// ❌ Avoid: Ejecting unsafe components
// This might break in future updates
export default function CustomLayout({ children }: Props) {
// Complex internal logic that might change
return <div>{children}</div>;
}import React from 'react';
import type { Props } from '@theme/Navbar'; // Use theme types
import OriginalNavbar from '@theme-original/Navbar';
// Type-safe wrapper
export default function NavbarWrapper(props: Props): JSX.Element {
// TypeScript will ensure props match the original component
return (
<div className="navbar-enhanced">
<OriginalNavbar {...props} />
</div>
);
}// Start with wrapping for minimal changes
export default function NavbarWrapper(props: Props) {
return (
<>
<OriginalNavbar {...props} />
<CustomSearchShortcuts />
</>
);
}
// Later, if more customization is needed, consider ejecting
// But check if the component is still marked as "safe"