My App
BoardKitExamples

React Whiteboard Example

Complete React whiteboard with canvas, toolbar, collaboration, and all features

A complete React whiteboard application using all BoardKit hooks and components.

App.tsx

import { BoardKitProvider } from '@hfu.digital/boardkit-react';
import { Whiteboard } from './Whiteboard';

const BOARD_ID = 'demo-board';

export function App() {
    return (
        <BoardKitProvider
            config={{
                apiUrl: 'http://localhost:3000',
                wsUrl: 'ws://localhost:3000/board',
                authToken: localStorage.getItem('token') ?? undefined,
                theme: {
                    selectionColor: '#3b82f6',
                    handleColor: '#1d4ed8',
                    cursorLabelFont: '12px sans-serif',
                },
            }}
        >
            <Whiteboard boardId={BOARD_ID} />
        </BoardKitProvider>
    );
}

Whiteboard.tsx

import {
    BoardCanvas,
    Toolbar,
    PageNavigator,
    ParticipantList,
    CursorOverlay,
    SelectionHandles,
    Minimap,
    DropZone,
    useBoard,
    useCollaboration,
    useTool,
    useViewport,
    useHistory,
    usePresence,
    useGrid,
    useExport,
    useImageImport,
    useErrorRecovery,
    useKeyboardShortcuts,
} from '@hfu.digital/boardkit-react';

export function Whiteboard({ boardId }: { boardId: string }) {
    const { board, loading, error } = useBoard(boardId);
    const { connectionState } = useCollaboration(boardId);
    const { activeTool, setTool } = useTool();
    const { viewport, zoomTo, fitToContent, resetZoom } = useViewport();
    const { undo, redo, canUndo, canRedo } = useHistory();
    const { participants } = usePresence();
    const { gridConfig, toggleGrid, toggleSnap } = useGrid();
    const { exportPng, exportPdf, isExporting } = useExport(boardId);
    const { isDragging } = useImageImport(boardId);
    const { lastError, clearError } = useErrorRecovery();

    useKeyboardShortcuts();

    if (loading) {
        return <div style={styles.loading}>Loading board...</div>;
    }

    if (error) {
        return <div style={styles.error}>Error: {error}</div>;
    }

    return (
        <div style={styles.container}>
            {/* Top bar */}
            <header style={styles.header}>
                <h1 style={styles.title}>{board?.name}</h1>

                <div style={styles.headerActions}>
                    <button onClick={undo} disabled={!canUndo}>Undo</button>
                    <button onClick={redo} disabled={!canRedo}>Redo</button>
                    <span style={styles.separator} />
                    <button onClick={toggleGrid}>
                        Grid: {gridConfig.enabled ? 'On' : 'Off'}
                    </button>
                    <button onClick={toggleSnap}>
                        Snap: {gridConfig.snapEnabled ? 'On' : 'Off'}
                    </button>
                    <span style={styles.separator} />
                    <button onClick={() => zoomTo(viewport.zoom - 0.1)}>-</button>
                    <span>{Math.round(viewport.zoom * 100)}%</span>
                    <button onClick={() => zoomTo(viewport.zoom + 0.1)}>+</button>
                    <button onClick={fitToContent}>Fit</button>
                    <button onClick={resetZoom}>Reset</button>
                </div>

                <div style={styles.headerRight}>
                    <ParticipantList />
                    <span style={styles.connectionDot(connectionState)} />
                </div>
            </header>

            {/* Toolbar (left side) */}
            <Toolbar activeTool={activeTool} onToolChange={setTool} />

            {/* Canvas area */}
            <main style={styles.canvas}>
                <BoardCanvas />
                <CursorOverlay />
                <SelectionHandles />
            </main>

            {/* Bottom bar */}
            <footer style={styles.footer}>
                <PageNavigator />

                <div style={styles.footerRight}>
                    <button onClick={exportPng} disabled={isExporting}>PNG</button>
                    <button onClick={exportPdf} disabled={isExporting}>PDF</button>
                </div>
            </footer>

            {/* Minimap (bottom-right corner) */}
            <Minimap width={200} height={150} />

            {/* Drop zone overlay */}
            <DropZone boardId={boardId} />

            {/* Error banner */}
            {lastError && (
                <div style={styles.errorBanner}>
                    <span>{lastError.message}</span>
                    {lastError.recoverable && (
                        <button onClick={clearError}>Dismiss</button>
                    )}
                </div>
            )}
        </div>
    );
}

const styles = {
    container: { position: 'relative', width: '100vw', height: '100vh', overflow: 'hidden' } as const,
    loading: { display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh' } as const,
    error: { color: 'red', padding: 20 } as const,
    header: { display: 'flex', alignItems: 'center', padding: '8px 16px', borderBottom: '1px solid #e5e7eb', gap: 16 } as const,
    title: { fontSize: 16, fontWeight: 600, margin: 0 } as const,
    headerActions: { display: 'flex', alignItems: 'center', gap: 8 } as const,
    headerRight: { marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 8 } as const,
    separator: { width: 1, height: 20, backgroundColor: '#e5e7eb' } as const,
    canvas: { flex: 1, position: 'relative' } as const,
    footer: { display: 'flex', alignItems: 'center', padding: '4px 16px', borderTop: '1px solid #e5e7eb' } as const,
    footerRight: { marginLeft: 'auto', display: 'flex', gap: 8 } as const,
    errorBanner: { position: 'fixed', bottom: 20, left: '50%', transform: 'translateX(-50%)', backgroundColor: '#fef2f2', border: '1px solid #fca5a5', borderRadius: 8, padding: '8px 16px', display: 'flex', gap: 8, alignItems: 'center' } as const,
    connectionDot: (state: string) => ({
        width: 8,
        height: 8,
        borderRadius: '50%',
        backgroundColor: state === 'connected' ? '#22c55e' : state === 'connecting' ? '#eab308' : '#ef4444',
    }) as const,
};

Features Demonstrated

  • Board loading with error handling
  • Realtime collaboration with connection status
  • All 9 drawing tools
  • Undo/redo
  • Zoom controls (zoom in/out, fit to content, reset)
  • Grid and snap controls
  • PNG and PDF export
  • Image drag-and-drop
  • Page navigation
  • Participant avatars with colors
  • Remote cursor overlay
  • Selection handles
  • Minimap
  • Error banner with dismiss
  • Keyboard shortcuts (via useKeyboardShortcuts)

On this page