Recipes
BYO-UI with useDocumentState
Skip Editor.Root entirely and build your own UI on top of the headless state hook.
useDocumentState is the headless ("bring your own UI") hook. Same reducer, same history timeline, no compound primitives. Use it when you want full control over the visual surface.
The hook
import { useDocumentState } from '@docmosaic/react';
import { createDocument } from '@docmosaic/core';
const { document, formattedDate, canUndo, canRedo, actions } = useDocumentState({
initialDocument: createDocument(),
});It returns:
document- the current snapshot. Re-renders on every change.formattedDate-updatedAtformatted for display.canUndo/canRedo- booleans for button enabled states.actions- a stable, referentially-equal object of dispatcher functions.
A minimal custom editor
'use client';
import { useDocumentState } from '@docmosaic/react';
import { createDocument } from '@docmosaic/core';
export function CustomEditor() {
const { document, canUndo, canRedo, actions } = useDocumentState({
initialDocument: createDocument(),
});
return (
<div className="flex flex-col gap-4 p-6">
<header className="flex items-center gap-2">
<input
value={document.name}
onChange={(e) => actions.updateName(e.target.value)}
className="border rounded px-2 py-1"
/>
<button disabled={!canUndo} onClick={actions.undo}>
Undo
</button>
<button disabled={!canRedo} onClick={actions.redo}>
Redo
</button>
</header>
<button onClick={() => actions.addSection({ type: 'image' })}>Add image section</button>
<p className="text-sm text-muted-foreground">
{document.sections.length} sections across {document.pages.length} pages
</p>
</div>
);
}That's the whole editor. No <Editor.Root>, no compound primitives - just the hook.
Wiring compound primitives onto the hook
If you want to use some compound primitives but own the rest of the layout, wrap your tree in EditorProvider with the hook's state:
import { EditorProvider, useDocumentState, Editor } from '@docmosaic/react';
function HybridEditor() {
const state = useDocumentState();
return (
<EditorProvider value={state}>
{/* Your custom toolbar */}
<MyCustomToolbar />
{/* Bundled primitives still work - same context */}
<Editor.Canvas>
<Editor.Section />
</Editor.Canvas>
</EditorProvider>
);
}See also
- Designer concept - the mental model
- Controlled vs uncontrolled
- Hooks reference