This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/user-interface/ui-customization/replace-main-toolbar-with-ui-slots.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Replace the main toolbar with UI slots | Nutrient Web SDK

When you need full control over the primary toolbar (not just reordering or hiding items on the default toolbar), use the UI customization main tools slot together with the minimal preset as your baseline. That hides Nutrient’s built-in chrome so your DOM strip can sit in the main tools region while you drive behavior through public APIs.

This guide ties together patterns that are documented across several articles so you can reproduce built-in behavior consistently.

Prefer tuning the built-in toolbar first (remove, rearrange, custom tools) if that meets your needs — less code and fewer APIs to wire.

1. Start with minimal UI and define your main toolbar

The minimal UI preset applies a baseline configuration that hides almost all UI, including the default main and contextual tool strips. You then override only the slots you need; merges are deep, so nested keys combine with the preset.

Typical pattern:

NutrientViewer.load({
// ...
ui: {
preset: "minimal",
tools: {
main: (getInstance, id) => ({
render: () => {
const root = document.createElement("div");
// Build your toolbar DOM; call `getInstance()` inside handlers when you need the viewer.
return root;
},
}),
contextual: (getInstance, id) => ({ render: () => null }),
},
},
});

Use instance.setUI() the same way if you attach the strip after load.

2. Restore the built-in sidebar shell (thumbnails, outline, …)

The minimal preset also hides sidebar.container — the layout shell that hosts the built-in thumbnails, outline, annotations list, and bookmarks panels. If you only set sidebarMode on the view state, nothing visible opens until that container exists again.

Override the preset entry so the default sidebar UI comes back:

ui: {
preset: "minimal",
tools: {
/* ... */
},
sidebar: {
container: undefined,
},
},

Setting container to undefined clears the preset’s hidden slot for that key after merge, which restores the stock sidebar chrome. You still control which panel is open via sidebarMode (strings such as "THUMBNAILS", "DOCUMENT_OUTLINE", "ANNOTATIONS", "BOOKMARKS", or null to close). See hide or show the sidebar and custom sidebars.

3. Map toolbar actions to APIs

There is no single internal “toolbar item ID → one method” registry in public documentation. In practice, you combine Instance, ViewState, and (for annotation tools) interaction mode + annotation preset ID the same way the default toolbar does.

GoalTypical approach (from a slot, use your getInstance argument)
Sidebar panelsgetInstance()?.setViewState(vs => vs.set("sidebarMode", modeOrNull))
Page navigationgetInstance()?.setViewState(vs => vs.goToPreviousPage()) / goToNextPage()
ZoomgetInstance()?.setViewState(vs => vs.zoomIn()) / zoomOut(), or set "zoom" to "FIT_TO_VIEWPORT" / "FIT_TO_WIDTH"
Pan/selection/marquee zoomSet "interactionMode" on view state to the same string values as InteractionMode (for example, "PAN", "MULTI_ANNOTATIONS_SELECTION", "MARQUEE_ZOOM")
Annotation tools (arrow, highlighter, dashed rect, …)On the instance from getInstance(), set interactionMode and setCurrentAnnotationPreset(id) together — see replicate built-in annotation tool variants
Search/document editor/cropSet "interactionMode" to "SEARCH", "DOCUMENT_EDITOR", "DOCUMENT_CROP" as needed
Print/exportgetInstance()?.print(), getInstance()?.exportPDF() (subject to license/permissions)
Undo/redogetInstance()?.history

Strings are often enough in plain JavaScript; TypeScript projects can use enums from the SDK where imported.

Reflecting the active tool in your UI

viewState.change carries view state updates (including interactionMode). currentAnnotationPreset isn’t part of ViewState — refresh highlighted tool state after calling setCurrentAnnotationPreset, not only when viewState.change fires.