Skip to content

React

React components and hooks for Inkbar. The components are unstyled primitives built on @inkbar/core.

Install the packages as the Quickstart shows.

React 19 and React DOM 19 are peer dependencies. Add the Vite plugin from @inkbar/core/vite so PDF.js can load its worker and fonts; Loading assets covers it. The package is marked 'use client', so a React Server Components app can import it from a server component.

import { useState } from 'react'
import {
InkRoot,
InkViewport,
InkToolbar,
InkToolButton,
InkToolbarSeparator,
InkUndo,
InkRedo,
InkPrevPage,
InkPageInput,
InkPageCount,
InkNextPage,
InkZoomSelect,
InkNotePopover,
InkSuggestionCard,
} from '@inkbar/react'
import type { Annotation } from '@inkbar/core'
import { assets } from 'virtual:inkbar-assets'
import '@inkbar/theme/theme.css'
export function Review({
signedUrl,
documentId,
}: {
signedUrl: string
documentId: string
}) {
const [annotations, setAnnotations] = useState<Annotation[]>([])
return (
<InkRoot
src={{ url: signedUrl, key: documentId }}
assets={assets}
annotations={annotations}
onAnnotationsChange={setAnnotations}
>
<InkToolbar>
<InkToolButton tool="view" />
<InkToolButton tool="select" />
<InkToolButton tool="highlight" />
<InkToolButton tool="redact-area" />
<InkToolButton tool="note" />
<InkToolbarSeparator />
<InkUndo />
<InkRedo />
</InkToolbar>
<InkViewport />
<InkToolbar label="Page and zoom">
<InkPrevPage />
<InkPageInput />
<InkPageCount />
<InkNextPage />
<InkZoomSelect />
</InkToolbar>
<InkNotePopover />
<InkSuggestionCard />
</InkRoot>
)
}

Each control is a separate component, so put them in any order or leave some out. Buttons show icons from @inkbar/icons. Pass your own as children to replace one, and use InkIcon to put the same icons on your own buttons. Toolbars lists every control.

annotations with onAnnotationsChange makes your list the source of truth. Leave them off and Inkbar keeps the state itself, with undo and redo.

InkNotePopover opens the thread of the activated note. A new note has no text yet, so the form first writes the note’s own text (labelled “Comment”); after that it adds replies.

A root note with no text and no replies is removed when its popover closes, or when activation moves to another annotation, so abandoned notes do not stay on the page. Locked notes are never removed. If you add notes yourself (add({ type: 'note', ... })), give them contents, or they will be removed the first time someone opens and closes them.

Slots are render props. InkViewport takes loading, empty, error, renderAnnotation and renderPageError; InkNotePopover and InkSuggestionCard take a function as children; InkToolButton takes a node or a function of { active, label }. The props each receives are typed as AnnotationSlotProps, NotePopoverSlotProps and SuggestionCardSlotProps, the same types the Vue and Svelte packages export.

InkRoot, InkToolbar, InkToolButton, InkUndo, InkRedo, InkPrevPage, InkNextPage, InkZoomIn and InkZoomOut take asChild to render your own element in place of theirs, such as a button from your design system. Pass that element as the only child. The control keeps its behaviour and accessible name.

The scroll area takes focus. From there:

  • With the highlight or redact-area tool active, Enter turns the text selection into a mark.
  • With the note tool active, Enter places a note at the centre of the current page and opens it.

On a focused annotation:

  • Enter or Space selects and activates it. A note opens its thread and a suggested redaction its card; other types are only selected.
  • ArrowUp and ArrowDown move to the previous or next annotation on the page.
  • Shift + arrow moves it by 1 pt, or by 10 pt with Alt as well.
  • Ctrl or Cmd + Shift + arrow resizes an area redaction, text box or shape.
  • Delete or Backspace removes it.

The full table, with the zoom and page keys, is in Annotations.

Components carry state on data attributes, for example [data-annotation][data-state="selected"] and [data-tool="highlight"][data-state="on"]. @inkbar/theme gives a default look. Your own CSS overrides it without !important. InkRoot, InkViewport, InkToolbar and the toolbar controls accept className.

useDocument, useAnnotations, useTool, useSelection, useZoom, usePage, useHistory and useEmbedded read the state of the nearest InkRoot. Build your own controls or side panel from them.