Whiteboard styling

The board adds only the CSS that it needs to work. Colors, fonts and cursors are up to your app, which styles the board with the data attributes on its elements.

The board's CSS

boardStyles holds the CSS that the board needs to work: the size of the board and the area around it, how shapes hold their text, outline or component, where their handles and the cursors of other users show, and which parts of a frame take presses. The rules are in the whiteboard cascade layer, so your app's CSS outside of layers overrides them.

The board adds its CSS when its editor starts, so the whiteboard layer comes after the layers that your app declared before, like Tailwind's, and wins over them. To override the board from those layers, declare the whiteboard layer first:

/* With Tailwind, before @import 'tailwindcss' */
@layer whiteboard, theme, base, components, utilities;

A doc frame is as tall as its document. Its height: auto !important also wins over !important rules of your app outside of layers.

The board adds this CSS to the page itself, in a <style data-tiptap-style-whiteboard> element. It uses the editor's injectNonce. An editor with injectCSS: false leaves the CSS to your app:

import { boardStyles } from '@tiptap-pro/extension-whiteboard'

const style = document.createElement('style')
style.textContent = boardStyles
document.head.append(style)

Shapes have the size of their attributes. An app that sizes a shape by its content also sets contain: none on it.

Zoom

Set --zoom to the zoom of the camera on the element that holds the editor. The handles and the cursors of other users read it to keep their size at every zoom. Divide your own outlines and borders by it too:

[data-shape][data-selected] {
  outline: calc(2px / var(--zoom, 1)) solid #6a00f5;
  outline-offset: calc(2px / var(--zoom, 1));
}

Shapes

Every shape element has data-shape, and a data-type with the kind of shape: sticky-note, geo, free-text, freehand, doc-frame, frame, board-image or arrow. Custom shapes have the data-type from their shapeNodeView. The board element has data-type="board".

AttributeOnDescription
data-idEvery shapeThe id of the shape
data-colorShapes with a colorThe name of its color, like blue. Custom shapes have it with a color attribute
data-font-sizeShapes with texts, m, l, xl or a name of your own
data-text-alignShapes with textstart, center or end
data-fontShapes with textsans, serif, mono or a name of your own
data-kindGeometric shapesrectangle, ellipse, diamond or triangle
data-selectedSelected shapesPresent while the shape is selected
data-editingThe edited shapePresent while its text, or the document of a doc frame, is edited
data-remote-selectedShapes that other users selectPresent while another user selects the shape. That user's color is in --remote-selection
data-erasingEvery shapePresent while the eraser is about to erase the shape
data-arrow-targetShapes that arrows connectPresent while a dragged arrow end would connect the shape

The shapeElementAttributes option of WhiteboardKit adds attributes of your own, like the data-suggestion-* attributes of Tracked Changes.

Parts of shapes

SelectorDescription
[data-shape-content]The element that holds the text of a shape, or the document of a doc frame
[data-shape-outline]The SVG outline behind the text of a geometric shape. It stretches over the whole shape
[data-shape-handle='resize']A resize handle on a corner of a selected shape. data-corner is top-left, top-right, bottom-left or bottom-right
[data-arrow-handle]A handle on an end of a selected arrow. Its value is from or to
[data-arrow-label]The label of an arrow, in the middle of its line. Without text, it has data-empty and shows only while it is edited
[data-shape-component]The element of a custom shape's node view. It has the size of the shape, and so does a NodeViewWrapper in it
[data-shape-interactive]Your mark on parts of a custom shape's component that take their own presses, like the legend of a chart
[data-frame-title]The name of a frame, above its top left corner and as wide as the frame at most. A press on it selects and moves the frame, and a double click renames it
[data-frame-title-field]The field in the name of a frame while the frame is renamed
[data-frame-edge]An SVG over a frame, whose edge takes the presses on the frame's edge. The rest of the frame lets presses through to the board

A shape in a frame has an inline clip-path, which cuts it off at the edges of the frame while it is not selected.

An image shape holds an img, which fills the whole shape. For a shape with other proportions than its image, like one that insertShape adds with a size of your own, give the img an object-fit:

[data-type='board-image'] img {
  object-fit: cover;
}

Arrows and pen lines are drawn in the current color, so set their color. The outline of a geometric shape is a path that your CSS fills and strokes:

[data-type='arrow'],
[data-type='freehand'] {
  color: var(--stroke);
}

[data-shape-outline] path {
  fill: var(--fill);
  stroke: var(--stroke);
  stroke-width: 2px;
  vector-effect: non-scaling-stroke;
}

Colors

Shapes, text colors and highlights store the name of a board color, so your CSS decides how each one looks. One way is a few variables for each color:

[data-color='blue'] {
  --stroke: #0d74ce;
  --fill: #ebf5ff;
  --note: #bfe0ff;
}

[data-type='sticky-note'] {
  background: var(--note);
}

Text colors are span elements with data-color, and highlights mark elements with data-color. See the example for the styles of all colors.

To add a color of your own, add its name to the colors option of WhiteboardKit, and style it like the others:

import { shapeColors, WhiteboardKit } from '@tiptap-pro/extension-whiteboard'

WhiteboardKit.configure({ colors: [...shapeColors, 'brand'] })

Commands take any name. The list decides which text colors and highlights pasted HTML keeps: other text colors go away, and other highlights become yellow.

Stage

attachStage marks the stage, so your CSS can show the tool and the pan:

AttributeDescription
data-toolThe active tool, like select or pen
data-panready while space is held, and active while the board pans

While a drag runs, the stage holds a preview element:

SelectorDescription
[data-selection-box]The box of a selection drag
[data-shape-preview]The box of a shape while a tool draws it. Its value is the tool, like note, and data-color holds the chosen color, if any
[data-arrow-preview]The line of an arrow while the arrow tool draws it, in the current color
[data-pen-preview]The line of the pen while it draws, in the current color. data-color holds the color of the line

The previews are not scaled with the board, so they need no --zoom:

[data-selection-box],
[data-shape-preview] {
  background: rgba(106, 0, 245, 0.1);
  border: 1px dashed #6a00f5;
}

[data-arrow-preview] {
  color: #6a00f5;
}

Cursors of other users

createRemoteCursors puts the cursors in a data-remote-cursors element next to the editor's element, over the shapes:

SelectorDescription
[data-remote-cursor]A cursor, with its user's color in --remote-cursor. It has data-idle after a minute without moving, and data-leaving while it goes. Both fade it out
[data-remote-cursor-arrow]The SVG arrow, with its tip where the user points
[data-remote-cursor-name]The user's name, next to the arrow. It doesn't show without a name

An app that sets --zoom only once zooming pauses can set it at once on the data-remote-cursors element, so the cursors keep their size while the board zooms.

Keyboard

While keys act on the board, the editor element has data-keyboard="board". While the text of a shape is edited, it doesn't. It stays while the document of a doc frame is edited.

Read-only

While the board is read-only, the editor element has data-read-only, and selected shapes show no handles. Use it for cursors that promise a change, like a move cursor on shapes:

.tiptap[data-read-only] [data-shape] {
  cursor: default;
}