Components Reference

React component library for EmberDocs documentation sites.

ThemeToggle Component

Toggle between light and dark mode themes. Supports three themes: dark (default), light, and monochrome.

Usage

typescript
import { ThemeToggle } from '@/components/ThemeToggle';

export function Header() {
  return (
    <header>
      <ThemeToggle />
    </header>
  );
}

Props

PropTypeDefaultDescription
None--Component takes no props

Behavior

  • Respects server-side theme set via EMBERDOCS_THEME environment variable
  • If monochrome theme is set via environment, toggle is hidden
  • Automatically detects system theme preference on first load (if no server theme)
  • Persists user selection to localStorage with key "theme"
  • Updates data-theme attribute on document root
  • Toggles between light and dark modes (monochrome is environment-only)

Notes

  • The component is hidden if EMBERDOCS_THEME is set to monochrome
  • Theme toggle only switches between light and dark (monochrome requires environment variable)
  • Component prevents hydration mismatches by not rendering until mounted

Display navigation breadcrumb trail showing document hierarchy.

Usage

typescript
import { Breadcrumbs } from '@/components/Breadcrumbs';

export function DocPage() {
  const breadcrumbs = [
    { href: '/docs', label: 'Docs' },
    { href: '/docs/guides', label: 'Guides' },
    { href: '/docs/guides/advanced', label: 'Advanced' },
  ];

  return <Breadcrumbs items={breadcrumbs} />;
}

Props

typescript
interface BreadcrumbsProps {
  items: BreadcrumbItem[];
}

interface BreadcrumbItem {
  href: string;
  label: string;
}

Styling

Uses CSS custom properties:

  • --text for active (last) breadcrumb
  • --muted for inactive breadcrumbs
  • --accent for hover state on links

TableOfContents Component

Sticky table of contents with active heading highlighting.

Usage

typescript
import { TableOfContents } from '@/components/TableOfContents';

const toc = [
  { slug: 'overview', title: 'Overview', level: 2 },
  { slug: 'installation', title: 'Installation', level: 2 },
  { slug: 'setup', title: 'Setup', level: 3 },
];

export function Layout() {
  return <TableOfContents toc={toc} />;
}

Props

typescript
interface TableOfContentsProps {
  toc: TocEntry[];
}

interface TocEntry {
  slug: string;
  title: string;
  level: number; // 1-6 for H1-H6
}

Features

  • Uses IntersectionObserver for active heading detection
  • Smooth scroll to heading on click
  • Visual hierarchy via nested indentation
  • Sticky positioning (top: 80px offset for header)
  • Automatic active state highlighting

CodeBlock Component

Syntax-highlighted code blocks with copy functionality.

Usage

typescript
import { CodeBlock } from '@/components/CodeBlock';

export function Example() {
  return (
    <CodeBlock
      code="const greeting = 'Hello, World!'"
      language="typescript"
      highlightedHtml={/* pre-highlighted HTML */}
    />
  );
}

Props

typescript
interface CodeBlockProps {
  code: string;          // Original code text
  language: string;      // Language identifier (typescript, bash, etc)
  highlightedHtml?: string; // Optional pre-highlighted HTML (not currently used)
}

Features

  • Displays language label in top-left
  • Copy button with visual feedback
  • Horizontal scroll for long lines
  • Proper code formatting with monospace font
  • Dark mode syntax highlighting compatible

SearchPalette Component

Modal search interface for documentation search.

Usage

typescript
import { SearchPalette } from '@/components/SearchPalette';
import { Header } from '@/components/Header';
import { useCallback, useState } from 'react';

export function Layout() {
  const [isOpen, setIsOpen] = useState(false);

  const openSearch = useCallback(() => setIsOpen(true), []);
  const closeSearch = useCallback(() => setIsOpen(false), []);
  const toggleSearch = useCallback(() => setIsOpen((prev) => !prev), []);

  return (
    <>
      <Header onSearchClick={openSearch} />
      {/* Page content */}
      <SearchPalette isOpen={isOpen} onClose={closeSearch} onToggle={toggleSearch} />
    </>
  );
}

Props

PropTypeDefaultDescription
isOpenboolean-Whether the search palette is visible
onClose() => void-Called when the palette should close (Escape, backdrop click, close button)
onToggle() => void-Called on Cmd+K / Ctrl+K to toggle open/close

Features

  • Opens via header search button (when integrated) and responds to Cmd+K (macOS) or Ctrl+K (Windows/Linux)
  • Navigate results with arrow keys
  • Select with Enter key
  • Close with Escape key
  • Real-time search results from pre-built index
  • Result highlighting with visual feedback
  • Keyboard shortcuts hint display

Keyboard Shortcuts

KeyAction
Cmd+K / Ctrl+KToggle search (calls onToggle)
/ Navigate results
EnterSelect highlighted result
EscapeClose search

Custom Component Example

Creating custom components for your documentation:

typescript
interface AlertProps {
  type: 'info' | 'warning' | 'error';
  children: React.ReactNode;
}

export function Alert({ type, children }: AlertProps) {
  const styles = {
    info: 'bg-blue-100 text-blue-900 border-blue-300',
    warning: 'bg-yellow-100 text-yellow-900 border-yellow-300',
    error: 'bg-red-100 text-red-900 border-red-300',
  };

  return (
    <div className={`p-4 rounded border ${styles[type]}`}>
      {children}
    </div>
  );
}

Styling Guidelines

All components use CSS custom properties for theming:

css
--bg: Background color
--text: Text color
--muted: Muted/secondary text
--accent: Primary accent color
--border: Border color
--surface: Secondary background

This allows seamless dark/light mode switching without component modifications.

Testing Components

Unit test example using React Testing Library:

typescript
import { render, screen } from '@testing-library/react';
import { Breadcrumbs } from './Breadcrumbs';

describe('Breadcrumbs', () => {
  it('renders all breadcrumb items', () => {
    const items = [
      { href: '/docs', label: 'Docs' },
      { href: '/docs/guides', label: 'Guides' },
    ];
    render(<Breadcrumbs items={items} />);

    expect(screen.getByText('Docs')).toBeInTheDocument();
    expect(screen.getByText('Guides')).toBeInTheDocument();
  });

  it('highlights last breadcrumb as active', () => {
    const items = [
      { href: '/docs', label: 'Docs' },
      { href: '/docs/guides', label: 'Guides' },
    ];
    render(<Breadcrumbs items={items} />);

    const lastItem = screen.getByText('Guides');
    expect(lastItem.parentElement).toHaveClass('font-medium');
  });
});

Accessibility

All components follow WCAG guidelines:

  • ThemeToggle: aria-label describes toggle action
  • Breadcrumbs: aria-label="Breadcrumb" on nav element
  • TableOfContents: Semantic nav with proper heading hierarchy
  • CodeBlock: Language label visible, syntax highlighting for distinction
  • SearchPalette: ARIA labels on buttons, keyboard navigation

Next Steps