# 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.

- Level: Common
- Uses: `toolbar.layout: 'none'`, `navigation.pageControls`, `TOOL_NAMES`, `viewer.activateTool()`, `viewer.deactivateTool()`, `viewer.isToolEnabled()`, `viewer.toolBlockedReason()`, `toolChanged`, `viewer.openPanel()`, `panelChanged`, `PANEL_TOOL_NAMES`, `sidebar.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.ts`

```ts
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.ts`

```ts
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.ts`

```ts
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.

## Related

- [Toolbar](https://kernstra.com/docs/web-sdk/config/toolbar/layout)
- [Navigation bar](https://kernstra.com/docs/web-sdk/config/navigation)
- [PDFViewer tools](https://kernstra.com/docs/web-sdk/api/pdfviewer/activatetool)
- [Events](https://kernstra.com/docs/web-sdk/api/events#ev-toolchanged)
- [Tool reference](https://kernstra.com/docs/web-sdk/guides/tool-reference)
- [React to the viewer with events](https://kernstra.com/docs/web-sdk/recipes/events-not-polling)

Web page: https://kernstra.com/docs/web-sdk/recipes/own-chrome
