Web SDKv1.0.0

Bring your own chrome

Render no toolbar and no navigation bar, keep the SDK's pages, panels and per-tool option bars, and drive the tools from buttons of your own through the viewer API.
Commontoolbar.layout: 'none'navigation.pageControlsTOOL_NAMESviewer.activateTool()viewer.deactivateTool()viewer.isToolEnabled()viewer.toolBlockedReason()toolChangedviewer.openPanel()panelChangedPANEL_TOOL_NAMESsidebar.rail
toolbar.layout: 'none' renders no toolbar at all; the nav bar, panels and page area render as usual. It is meant for a host building its own chrome and driving the SDK through the viewer API: your buttons, the SDK's pages and tools.

What you get

  • The SDK's toolbar and navigation bar are gone; the pages, the sidebar and each tool's options bar stay.
  • Every button mirrors what the SDK would offer: isToolEnabled() for whether a tool may run, toolBlockedReason() for why the document forbids it.
  • Your chrome follows the viewer rather than the other way round: toolChanged and documentChanged drive its state.

1. Render no chrome

layout: 'none' and the older enabled: false both render no toolbar and are interchangeable; prefer layout. With pageControls, zoomControls and toolControls all off, no navigation bar is mounted at all. Leave the sidebar panels on if you want the SDK's thumbnails and comments, or switch each off.
viewer.tsTypeScript
const viewer = new PDFViewer(container, doc, {
  toolbar: { layout: 'none' },
  navigation: { pageControls: false, zoomControls: false, toolControls: false },
  sidebar: { rail: false, pages: { defaultOpen: false } }, // your own Pages button, not ours beside it
});
Dropping a tool from your chrome does not switch it off: keyboard shortcuts still reach it (navigation.keyboardShortcuts turns them all off), and viewer.activateTool() still accepts it. Use tools to switch a tool off entirely.

2. One button per tool, from TOOL_NAMES

The exported TOOL_NAMES array lists every tool in the toolbar's order. isToolEnabled(tool) says whether a tool may run: covered by the license, not false in tools, and offered by the mode (Select, Pan and Search in every mode, Sign in FORM_FILL and FULL, the rest in FULL only). toolBlockedReason(tool) says why the open document forbids a tool, { reason, message } with reason one of 'permission', 'certified' or 'downloading', or null when it allows it; the SDK grays such a tool out with the message, and a host drawing its own chrome shows the same.
chrome.tsTypeScript
import { TOOL_NAMES, type ToolName } from '@kernstra/pdf-web-sdk';

for (const tool of TOOL_NAMES) {
  const button = buttons.get(tool); // your own elements
  if (!button) continue;
  button.hidden = !viewer.isToolEnabled(tool);
  const blocked = viewer.toolBlockedReason(tool);
  button.disabled = blocked !== null;
  button.title = blocked?.message ?? '';
  button.onclick = () => void toggle(tool);
}

// Activating the active tool again puts it away, back to the resting tool.
async function toggle(tool: ToolName) {
  try {
    if (viewer.activeTool === tool) await viewer.deactivateTool();
    else await viewer.activateTool(tool);
  } catch (err: unknown) {
    // INVALID_ARGUMENT: no such tool, or off here; FEATURE_NOT_LICENSED: not in the license;
    // PERMISSION_DENIED: the document forbids it; UNKNOWN_ERROR: it could not start.
    console.warn(err);
  }
}
Two names are panels rather than tools: activateTool('search') and activateTool('managePages') open their panel and resolve, and activeTool stays what it was. openPanel('search') / openPanel('pages') are the same action by their own name. The exported PANEL_TOOL_NAMES names those two, so buttons marked pressed from activeTool or toolChanged need not hard-code them.

3. Follow the viewer, do not assume

toolChanged carries { tool, previous } once the tool has started; between tools it names the resting tool ('selectText' by default). documentChanged carries canUndo and canRedo for your own history buttons, and viewer.undo() / viewer.redo() stay available with the toolbar gone. Panels open with openPanel(panel) ('pages', 'outline', 'attachments', 'signatures', 'comments', or null to close the sidebar); it returns false if that panel is not offered, and viewer.activePanel is the one open. panelChanged reports every change of it, whoever made it — the drawer's backdrop, Escape inside the drawer, a resize — so your own Pages button stays right; with sidebar.rail: false the SDK draws neither the rail of tabs nor the panel's collapse button, and that button is the way in and out.
chrome.tsTypeScript
import { PANEL_TOOL_NAMES } from '@kernstra/pdf-web-sdk';

// Between tools the resting tool runs — Select by default — so toolChanged reports
// 'selectText' from the first paint. Search and Manage Pages are panels: activating
// them leaves activeTool as it was.
viewer.on('toolChanged', ({ tool }) => {
  for (const [name, button] of buttons) {
    button.ariaPressed = String(!PANEL_TOOL_NAMES.includes(name) && tool === name);
  }
});

viewer.on('documentChanged', ({ canUndo, canRedo }) => {
  undoButton.disabled = !canUndo;
  redoButton.disabled = !canRedo;
});
undoButton.onclick = () => void viewer.undo();
redoButton.onclick = () => void viewer.redo();

// Your Pages button: open or close through the API, and follow every change.
thumbnailsButton.onclick = () => {
  viewer.openPanel(viewer.activePanel === 'pages' ? null : 'pages');
};
viewer.on('panelChanged', ({ panel }) => {
  thumbnailsButton.ariaPressed = String(panel === 'pages');
});

Gotchas

  • Edit Text left with edits pending writes them first when another tool is activated or deactivateTool() is called; a refused write keeps the tool, and activeTool tells. commit() and cancel() on viewer.tools.editText both deactivate the tool.
  • viewer.config.tools names every tool: false for one you, the mode or the license switched off, otherwise your options for it or true. It follows the rights of the viewer's document.
  • After viewer.destroy(), every member that acts (activateTool(), openPanel(), undo() and the rest) throws INVALID_STATE rather than doing nothing; a stale handler that still reaches the viewer is a bug this makes visible.
  • The SDK styles its own parts through tokens (--ks-*) and a few structural classes; every other class is internal. Position and hide its regions through the structural classes and the data-ks-* attributes, never by overriding its class names.