or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdcontext.mdi18n.mdindex.mdplugin-system.mdrouting.mdswizzling.md
tile.json

swizzling.mddocs/

Component Swizzling

Component customization system for safely ejecting and wrapping theme components.

Capabilities

SwizzleConfig

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'
    }
  }
};

Swizzle Actions and Status

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 customization
  • wrap: Creates a wrapper component that can enhance the original

Status Descriptions:

  • safe: Recommended for customization, unlikely to break in updates
  • unsafe: Possible but may break in future versions
  • forbidden: Should not be customized, may cause issues

Plugin Swizzle Integration

How 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;

WrapperProps Utility Type

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>
    </>
  );
}

Swizzling Workflow

Using the Swizzle CLI

# 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 -- --list

Ejected Component Structure

When 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.tsx

Wrapped Component Example

Wrapping 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>
  );
}

Best Practices

Safe Swizzling Strategies

// ✅ 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>;
}

Type Safety in Swizzled Components

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>
  );
}

Progressive Enhancement

// 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"