---
title: "Blok Docs — Block Editor for React, Vue & Angular"
description: "Guides, API reference, and 29 built-in block and inline tools for Blok. Start in five minutes."
source: https://blokeditor.com/docs/
lastmod: 2026-08-31
---

Framework JavaScript

Select section

# Blok documentation

Guides, the full API reference, and every built-in block and inline tool. New here? Start with the quick start and have an editor running in five minutes.

## Getting started

- [Quick Start Get up and running with Blok in just a few simple steps.](https://blokeditor.com/docs/quick-start/)
- [Build your first editor Mount Blok, capture some content, and save it as JSON you can store and load back — the full round-trip in five steps.](https://blokeditor.com/docs/tutorial/)
- [Everything is a block Blok has one core idea. Understand it, and the rest of the API falls into place.](https://blokeditor.com/docs/concepts/)
- [Create a custom block tool Build a block tool from scratch — a callout box that renders, edits, and saves like any built-in block.](https://blokeditor.com/docs/custom-block-tool/)

## Core

- [Blok Class The main editor class that initializes and manages the Blok editor instance. Every namespace a tool reaches through `api.*` is also reachable on the instance as `editor.*` — the properties below are that same surface, plus the `width`, `placeholder`, `tokens` and `i18n` namespaces the class declares itself.](https://blokeditor.com/docs/core/)
- [Configuration The configuration object passed to the Blok constructor. It is formally split into two types: `BlokMountOptions` — options fixed for the instance's life (holder, tools, i18n, …) — and `BlokState`, the LIVE fields: `readOnly` (including `hideControls`), `hideToolbar`, `toolbarPosition`, `inlineToolbar`, and the editor callbacks `onChange`, `onSave`, `onEnter`, `onSubmit`, `onBeforeRender` and `onAfterRender`. Every `BlokState` field maps to a documented runtime setter (`readOnly.set`, `toolbar.setHidden`, `toolbar.setPosition`, `tools.setInlineToolbar`, `handlers.set`), so changing it never requires recreating the editor — and the React, Vue and Angular adapters react to these props/inputs in place. Callback PRESENCE is itself load-bearing (an `onSubmit` turns Enter into serialize-and-submit; an `onSave` arms the change pipeline), which is why `handlers.set` also accepts `undefined` to unset one. `BlokConfig = BlokMountOptions & BlokState`, so existing code compiles unchanged.](https://blokeditor.com/docs/config/)
- [Blocks Manage blocks in the editor — create, delete, update, and reorder content.](https://blokeditor.com/docs/blocks-api/)
- [BlockAPI Interface for working with individual blocks. Returned by blocks.getById(), blocks.getBlockByIndex(), and blocks.insert().](https://blokeditor.com/docs/block-api/)
- [Saver Save and export editor content.](https://blokeditor.com/docs/saver-api/)
- [View renderer Display saved documents without paying for an editor. The @bloklabs/core/view subpath renders OutputData to semantic HTML or plain text synchronously and DOM-free — it runs in Node, workers, and React Server Components — so display-only surfaces (published pages, previews, search indexing, emails) no longer need an editor instance, its bundle, or its async ready latch. Every inline-content field is sanitized against the composed allowlist before interpolation, with a URL scheme policy identical to the editor's; pair the functions with defineBlokSchema and documents are displayed under the same sanitize composition that produced them (if you later change the inline-tool set at runtime via tools.setInlineToolbar, recompose the schema so the view keeps up). For React, <BlokView> (and the wrapper-free useBlokView) is the obvious read-only path — reach for it instead of <BlokEditor readOnly>, which ships the full editing runtime (toolbar, history, mutation machinery) to every viewer. Output is unstyled by default: opt into classes + root together with the opt-in @bloklabs/core/view.css for editor parity, or toolAttributes alone with that stylesheet for the classless baseline that reproduces the editor's block spacing from the same --blok-block-padding-* tokens; enable blockIds for copy-link-to-block deep links, and pass transformUrl to rewrite hrefs / CDN image URLs.](https://blokeditor.com/docs/view-api/)

## Editing

- [Caret Control cursor position and selection within the editor.](https://blokeditor.com/docs/caret-api/)
- [Selection Work with text selection within the editor.](https://blokeditor.com/docs/selection-api/)
- [Marks Range-aware inline-mark operations for building inline formatting tools. Where selection.findParentTag only inspects the selection's two boundary nodes (anchor and focus) and their ancestors, api.marks operates on the WHOLE range: has answers "is every text node in the selection covered", apply and remove split partially-covered wrappers at the range boundaries, update fully-covering wrappers in place, and restore the selection afterwards — and apply and remove extend the range over trailing whitespace browsers exclude from double-click selections. A mark is described declaratively by a MarkSpec (tag, aliasTags, className, attributes, style); aliasTags lets legacy tag variants (e.g. <b> next to <strong>, <em> next to <i>) match as the SAME mark while new wrappers always use the canonical tag. String values are static and participate in the mark's identity; function-form values are resolved from the state passed to apply/toggle and are deliberately EXCLUDED from identity — that is what makes a colour picker ONE mark updating in place rather than N mutually-cancelling marks. Two specs sharing tag, classNames and static attributes belong to the same family and compose on a single element — e.g. a text-colour spec and a background-colour spec both on one <mark>. Every method defaults to the live selection's first range when no range is passed. The core export markSanitizerConfig(spec) derives the sanitizer rule a mark produces — allowlist the spec's tag, strip style properties and classes the spec does not declare, keep declared attributes, with function-form values handled by property name so dynamic values are never dropped on save. The React adapter's createReactInlineTool applies the same derivation automatically when a tool declares a mark spec.](https://blokeditor.com/docs/marks-api/)
- [Styles Access CSS class names for styling custom tools and UI elements, and customize the editor's layout and chrome via public CSS custom properties. The primary way to override theme tokens is `style.tokens` in the Blok constructor config — pass `--blok-*` keys and values and Blok injects a per-instance stylesheet that reaches the editor AND UI portaled to `document.body` (popovers, tooltips, top-layer elements) automatically; invalid keys are skipped with a warning, and the stylesheet is removed on destroy. Injected `style.tokens` values are static per application — they apply identically in light and dark themes and across read-only state, so state-dependent tokens like the editor gutter belong in CSS instead; `style.tokens` ignores `--blok-editor-gutter-*` keys with a warning. They are not, however, frozen at construction: `editor.tokens.set(tokens)` rewrites the injected stylesheet at runtime, which is what a host light/dark toggle needs — without it, flipping a token meant recreating the editor or hand-writing a global stylesheet targeting the portal scopes yourself. `set()` takes the complete token set (replace, not merge), mirroring `style.tokens`, so tokens omitted from the new palette stop applying and `{}` removes the stylesheet; `editor.tokens.get()` returns what is currently applied. The API is available synchronously after construction (calls before `isReady` are buffered and replayed), and the React/Vue/Angular adapters drive it reactively — pass `style={{ tokens }}` (React/Vue) or `[styleTokens]` (Angular) and changes sync in place without recreating the editor. As a CSS-only alternative, Blok's own palette is declared at zero specificity via `:where()`, so a single plain selector like `[data-blok-interface] { --blok-popover-bg: … }` wins regardless of stylesheet order — but since popovers portal to `document.body`, that global stylesheet must also target `[data-blok-popover], [data-blok-top-layer]` to reach them. `--blok-content-max-width` stays authoritative in both width modes — `width='full'` only swaps its fallback to `none`. Blok reserves 56px of gutter automatically in edit mode for the floating +/⠿ block controls, and the wrapper carries `data-blok-readonly` while read-only is active. Plain read-only KEEPS the gutter — the block-hover copy-link control lives there, and `readOnly.set()` flips modes in place, so collapsing it would shift the document sideways on every toggle. The gutter collapses to 0 automatically only when it is genuinely dead space: chromeless read-only (`readOnly: { hideControls: true }`, wrapper carries `data-blok-controls-hidden`) and `hideToolbar: true` in the constructor config — the hover toolbar never opens and the wrapper carries `data-blok-toolbar-hidden`, so no gutter space is reserved. `--blok-editor-gutter-start` is an override hook, not a required incantation — set it to any value (including `0px` to remove the gutter) to change the default. The gutter override contract is guaranteed, not incidental: Blok declares the gutter default and both state collapses at zero specificity via `:where()` (enforced by a unit contract test), so a host declaration of the gutter tokens at any positive specificity always wins the cascade. Declare them on the wrapper element itself (e.g. `[data-blok-interface] { --blok-editor-gutter-start: 16px }`), not only on an ancestor — the controls-hidden and toolbar-hidden collapses re-declare the tokens on the wrapper, and custom properties resolve from the nearest declaration, so an ancestor-level value loses to the collapse while a wrapper-level one survives it. The content column's horizontal position is also configurable at the API level via `style.contentAlign?: 'left' | 'center' | 'right'` (default `'left'`) in the Blok constructor config. Blok also repaints native text selection inside the editor with `--blok-selection-inline` — override that token to recolor it, or pass `style.nativeSelection: true` (default `false`) to opt out entirely and fall back to the browser/host-defined selection colors (a token override cannot express CSS-wide keywords like `revert`, so reverting needs this flag). With the flag on, the wrapper carries `data-blok-native-selection`, Blok's `::selection` rules skip the editor, and the fake-background highlight (shown while a menu input holds focus) follows the UA `Highlight` color; popovers keep Blok's selection color. Background surfaces are public tokens too: most hover/light UI surfaces follow `--blok-bg-light`, media empty-state cards use `--blok-bg-secondary` (bordered by `--blok-border-secondary`), and the image/file loading skeletons and upload placeholders use `--blok-bg-tertiary`, which defaults to `--blok-bg-light` so it tracks the theme — recoloring the skeleton surface means overriding `--blok-bg-tertiary` directly, not overloading `--blok-bg-light` and dragging every other surface along with it. Like all palette-backed color tokens, the surface tokens are re-declared by Blok on the editor wrapper at zero specificity, so apply overrides via `style.tokens` / `editor.tokens.set()` or a CSS selector matching the wrapper (`[data-blok-interface]`) itself — a custom-property declaration on an ancestor container is shadowed by the wrapper's own declaration and silently does nothing (layout hooks such as `--blok-content-max-width` and the list, heading, embed, block-padding and placeholder-color tokens are instead read with fallbacks and never declared by Blok, which is why those DO inherit from any ancestor; the gutter tokens and `--blok-search-input-placeholder` are wrapper-declared like the palette, so they too need a wrapper-level rule). Also note the injected token stylesheets target Blok's scope attributes globally: with several editor instances on one page, each instance's `style.tokens` / `tokens.set()` stylesheet applies to ALL Blok UI on the page, not just its own instance (each is removed when its own instance is destroyed; where sets conflict between instances the stylesheet order in `<head>` — not application recency — decides, so give every instance one shared set instead of relying on conflict order) — scope per-instance differences with a CSS rule on each editor's own wrapper instead (body-mounted popover UI always follows the page-wide sheets). The sheets are injected at the start of `<head>`, so a host stylesheet rule of equal specificity — a plain `[data-blok-interface] { … }` — still beats `style.tokens` for the tokens it declares. Block rhythm is public too: `--blok-block-padding-top`, `--blok-block-padding-bottom` and `--blok-block-padding-inline` drive the padding of every block tool wrapper (paragraph, heading, list, toggle, quote). Each tool keeps its historical value as the fallback — 7px/7px/2px for most blocks, 0.2em vertical for quotes — so one override retunes all blocks at once, which is exactly what a read-only host needs for tight inline-style rendering (previously only possible by overriding `[data-blok-tool]` internals). The callout panel is the deliberate exception: its card inset is `--blok-callout-padding-block` (default 5px), NOT the rhythm tokens, so tightening rhythm cannot collapse the callout card onto its text — while the emoji stays on the first text line because its button follows `--blok-block-padding-top` together with the child text. Note that non-default padding slightly shifts derived geometry such as the toggle-heading arrow offset, which follows `--blok-block-padding-top`. Column layout is public in the same way: a columns row is `[data-blok-columns]` and each column holder is one of its direct `[data-blok-element]` children (a read-only row also carries `data-blok-columns-static-gutter`, since published rows take their gutter from the container instead of from the `[data-blok-column-resizer]` separators that only exist while editing). `--blok-column-gutter` sets the gap (default `min(2rem, 4vw)`) and `--blok-column-min-width` sets how far a column may be squeezed (default `0`, i.e. a column can be dragged all the way to collapse). The floor is honored by BOTH layout and the resizer drag — the drag reads the resolved value back at pointer-down — so raising it stops the handle at the floor instead of persisting a width the layout refuses to render. Block nesting is public the same way: a block nested under another (Tab at root level) is indented by `--blok-block-indent-step` per level (default `24px`), and it is real CSS rather than an inline style, so a plain host rule retunes or removes it with no `!important`. Blok zeroes the step inside every `[data-blok-nested-blocks]` child slot — the marker every container tool renders for its children, first-party and third-party alike — so blocks a container already positions are never pushed sideways by their depth on top of it; a container that DOES want the indent declares the step back on its own slot. The reset rides on inheritance rather than on a JS check precisely so it also holds for a slot that is created after the child was inserted, which is what a framework adapter's portal does. Text size is public per block AND per scenario through `style.fontSize` — the supported alternative to targeting Blok's internal class names. Every key writes one public token: `fontSize.paragraph` → `--blok-paragraph-font-size`, `fontSize.heading[1]` → `--blok-heading-1-font-size` (headings reuse the pre-existing heading tokens rather than minting parallel ones), `fontSize.list.checklist` → `--blok-checklist-font-size`, and likewise for both quote variants, callout, code, toggle, the two table densities (`compact` / `comfortable`), every media caption (image, video, audio, file, embed) and the three bookmark parts (title, description, link). Omitted keys keep Blok's built-in size, so an editor renders exactly as before everywhere it does not opt in. Per-tool size settings still outrank it: a paragraph tool configured with `styles.size`, or a list with `itemSize`, writes that size as an inline style on the block, which no token can override — so scenarios you want to drive from `style.fontSize` must not also carry a per-tool size. Values may be absolute or relative (`px`, `rem`, `em`, `%`): every ornament sitting beside sized text — list bullet, checkbox, callout emoji, toggle arrow — derives its own metrics from the same token, so it stays optically aligned at any scale with no extra CSS. Because these tokens are read with fallbacks and never declared by Blok on its own, an editor that does NOT configure `style.fontSize` also accepts them from a plain CSS rule on any ancestor or from `style.tokens` / `editor.tokens.set()`. The token NAMES ship as a constant — `import { BLOK_FONT_SIZE_TOKENS } from '@dodopizza/blok'` gives you a map shaped exactly like the config (`BLOK_FONT_SIZE_TOKENS.paragraph`, `BLOK_FONT_SIZE_TOKENS.heading[1]`, `BLOK_FONT_SIZE_TOKENS.bookmark.link`…), so a host that scopes typography from CSS never hand-copies the strings and a rename becomes a compile error rather than a silent no-op. The channels compose: `style.fontSize` is the construction-time value, and `editor.tokens.set({ [BLOK_FONT_SIZE_TOKENS.paragraph]: '18px' })` overrides it at runtime — the theme-token sheet is injected directly after the fontSize sheet at equal specificity, so it wins. That is the channel for a size that must change after mount (a density, zoom or accessibility toggle); `style.fontSize` itself is read once at construction. Unlike `style.tokens`, the injected fontSize sheet is scoped to its own editor: the wrapper carries `data-blok-instance` and the sheet's editor selector is keyed to it, so a second editor on the page keeps Blok's built-in sizes (or its own config) instead of inheriting the first one's. The one part that stays page-wide is body-mounted UI — popovers and tooltips render outside every editor's subtree, so those rules follow `<head>` order when instances disagree. One nesting rule is worth knowing: a callout renders its body text as a child paragraph block, so callout text follows `fontSize.callout` and falls back to `fontSize.paragraph` when that key is unset — setting only `paragraph` resizes callout bodies along with body text, and making the two differ means setting `fontSize.callout` explicitly. Finally, the view renderer (`@bloklabs/core/view`) emits semantic HTML and its stylesheet carries only the class-based scenarios: paragraph, headings, list, checklist, both quote sizes, callout, code and toggle respond in view output, while the caption, table-cell and bookmark sizes are editor-only.](https://blokeditor.com/docs/styles-api/)
- [History Control undo/redo functionality for editor operations.](https://blokeditor.com/docs/history-api/)

## Interface

- [Toolbar Control the block toolbar and its state.](https://blokeditor.com/docs/toolbar-api/)
- [InlineToolbar Control the inline formatting toolbar (bold, italic, etc.).](https://blokeditor.com/docs/inline-toolbar-api/)
- [UI Access to Blok UI elements and state.](https://blokeditor.com/docs/ui-api/)
- [Notifier Display notification messages to users. Rendering is pluggable: pass `notifier: (options) => …` in the constructor config and Blok calls your handler instead of rendering anything — the built-in toast is skipped entirely (including its i18n `okText`/`cancelText` defaults), and any error your handler throws propagates to the `show()` call site. `notifierPosition` places the built-in container: 'bottom-left' | 'bottom-right' | 'bottom-center' | 'top-left' | 'top-right' | 'top-center' (default 'bottom-center').](https://blokeditor.com/docs/notifier-api/)
- [Tooltip Display tooltip hints on UI elements.](https://blokeditor.com/docs/tooltip-api/)
- [Theme Read and switch the editor color theme at runtime. The configured mode and the theme actually painted are two different questions — `get()` answers the first, `getResolved()` the second. To be notified instead of polling, pass the core config option `onThemeChange`, which fires with the resolved theme when it changes (including OS-preference flips while the mode is 'auto').](https://blokeditor.com/docs/theme-api/)
- [Width Control the editor content width mode — 'narrow' keeps content inside the default `--max-width-content`, 'full' drops the constraint so it fills its container.](https://blokeditor.com/docs/width-api/)
- [Placeholder Read and change the editor-level placeholder (the hint shown on the empty default block) at runtime, without recreating the editor.](https://blokeditor.com/docs/placeholder-api/)

## Extending & system

- [Tools Access and manage editor tools.](https://blokeditor.com/docs/tools-api/)
- [Uploader Upload an asset through the pipeline that owns its KIND, instead of whichever tool happens to be asking. Tools call this rather than reaching into their own `config.uploader`, which is why an audio block's cover art reaches your image pipeline instead of the audio endpoint that would reject it. Resolution order for a kind: the tool whose static `assetKind` matches (e.g. `tools.image.config.uploader` for `'image'`), then the editor-level `uploader` config, then a local fallback — a `blob:` URL for files, the URL verbatim for links. See the storage presets page for ready-made `uploader` implementations — Supabase, S3-compatible storage, Cloudinary, and IndexedDB — that need no backend of your own.](https://blokeditor.com/docs/uploader-api/)
- [Events Subscribe to and manage editor lifecycle events.](https://blokeditor.com/docs/events-api/)
- [Listeners Manage custom DOM event listeners with automatic cleanup.](https://blokeditor.com/docs/listeners-api/)
- [Sanitizer Clean and sanitize HTML content to prevent XSS attacks. A tool's `static get sanitize()` may also map a data field to the string `'plaintext'` instead of a tag map, which marks that field as literal source text rather than markup.](https://blokeditor.com/docs/sanitizer-api/)
- [ReadOnly Control the read-only state of the editor. Toggling is in-place as long as every registered block tool implements `setReadOnly(state)` on its prototype — every bundled tool does — so the same editor instance flips modes, preserving caret position, undo history and scroll, and an edit/view toggle is `readOnly.set(!isEditing)` on ONE instance instead of destroying one editor and constructing another. The check is all-or-nothing: install a single block tool without `setReadOnly` and every toggle falls back to a save → clear → re-render cycle, which recreates all block instances and does not restore the caret (scroll is restored, and the undo history is deliberately left untouched).](https://blokeditor.com/docs/readonly-api/)
- [I18n Internationalization support for translating UI strings, plus the runtime `i18n.update()` mutator that switches language in place. The locale catalogue itself ships as a separate published entry point, `@bloklabs/core/locales`: only English is bundled, the other 68 locales load on demand, and `normalizeLocale()` is the pre-flight check for a locale you did not hard-code — `i18n.update({ locale })` with an unsupported tag keeps the current locale and warns on the console instead of throwing.](https://blokeditor.com/docs/i18n-api/)
- [Dev override seam A development seam every published entry ships with — how it works, why it's safe, and how to remove it from your bundle.](https://blokeditor.com/docs/dev-override-seam/)

## Data types

- [OutputData The data structure returned by the save() method. Input positions — the `data` config option, `render()`, `blocks.render()`, and `blocks.insertMany()` — also accept the loose wire variants `LooseOutputData` / `LooseOutputBlockData`, where block `data`, `id`, `parent`, `content`, and `time` may be `null`: a `null` `data` becomes `{}`, a `null`/empty `id` gets a generated one, and a `null` `parent` / `null`-or-empty `content` is treated as absent (root-level, childless). Saved output is always the strict shape.](https://blokeditor.com/docs/output-data/)
- [BlockData The structure of each block in the blocks array.](https://blokeditor.com/docs/block-data/)

## Framework adapters

- [BlokEditor component The all-in-one editor component shipped by the framework adapters — <BlokEditor> in @bloklabs/react and @bloklabs/vue, <blok-editor> (BlokEditorComponent) in @bloklabs/angular. React and Vue accept every editor config option as a prop and forward unknown props/attributes to the container div. Angular is different: it declares a curated set of `@Input()`s — tools, data, readOnly, hideToolbar, toolbarPosition, inlineToolbar, theme, width, placeholder, styleTokens, i18n, autofocus, migrations, onBeforeRender, onBeforePaste, onError — plus a `[config]` escape hatch for every other config key (sanitizer, minHeight, defaultBlock, dataModel, link, linkPaste, tunes, user, resolveUser, uploader, server, ticket, persistence, collaboration, notifier, logLevel, onEnter, onSubmit, scrollToBlock, …), and it does not forward host attributes onto the container div. The live Blok instance is read via ref/onReady (React), the `instance` on a template ref or the `@ready` emit (Vue), and the `instance` signal or the `(ready)` output (Angular). The props below cover the adapter-specific surface; everything else matches the Configuration options.](https://blokeditor.com/docs/blok-editor/)
- [useBlocks A reactive snapshot of the block tree plus a full manipulation API, from the framework adapters: the useBlocks(editor, options?) hook in @bloklabs/react, the useBlocks(editor, options?) composable in @bloklabs/vue, and `injectBlocks(editor, options?)` in @bloklabs/angular — pass the `instance` signal of BlokEditorComponent/BlokContentDirective, and call it from an injection context (a field initializer or the constructor). Reads re-render reactively as the document changes; writers are atomic (one undo step) and safe to call before the editor is ready (they no-op). Returned BlockNode objects ({ id, type, parentId, contentIds }) are fresh-snapshot volatile — read them now, don't stash them in dep arrays. Reactivity is document-wide by default: pass `{ within: blockId }` to re-render only for changes inside that block's subtree (the block itself or any descendant). Reach for it in a container block that renders only its own children — unscoped, such a block re-renders on every keystroke anywhere in the document, and a page of N containers turns one keystroke into N re-renders. The scope bounds re-renders, not reads: a scoped handle still sees the whole tree, so getById/getChildren keep working on anything. In Vue the scope also accepts a ref/getter and in Angular a signal, read at emit time so changing it needs no re-subscription.](https://blokeditor.com/docs/use-blocks/)
- [useBlokReady Live readiness of the Blok editors inside a DOM subtree, as a boolean you can render from — the useBlokReady(options) hook in @bloklabs/react, the useBlokReady(options) composable in @bloklabs/vue (returns a ref), and injectBlokReady(options) in @bloklabs/angular (returns a signal). All three wrap the same core registry behind Blok.readyState() and Blok.subscribeReady(), so they cannot drift. It answers the question a comments list or a form actually has: are MY editors ready? Scope it with the ref you already hold on the container, so an unrelated editor elsewhere on the page cannot hold your gate closed. It is a live signal, not a one-shot latch: an editor mounted later re-closes the gate, and with settleOn: 'rendered' so does every re-render from a changed data prop. A scope holding no editors is ready, so the empty-list case needs no special-casing. It starts false and takes its first real reading once the scope element is attached (React: the mount effect; Vue: onMounted; Angular: afterNextRender), and a scope you asked for that has not resolved yet reports false rather than silently falling back to the whole page — over-waiting is safe, under-waiting is a bug.](https://blokeditor.com/docs/use-blok-ready/)

## Block Tools

- [Paragraph The default text block. Supports rich inline formatting (bold, italic, links, colour). Empty top-level paragraphs are excluded from saved output unless `preserveBlank` is enabled; empty paragraphs nested inside another block (callout, toggle, column, …) are always kept.](https://blokeditor.com/docs/paragraph/)
- [Header Heading blocks from H1 to H6. Supports multiple toolbox entries (one per heading level), keyboard shortcuts (# ## ### etc.), and optional toggle (collapse/expand children) at every level — the toolbox lists "Toggle heading 1" through "Toggle heading 6", reachable with the markdown shortcuts `>#` through `>######`. Converting an existing block into a toggle heading (via "Turn into" or `blocks.convert` with `isToggleable: true`) adopts its section — every following sibling until the next heading of the same or higher rank becomes a child of the new toggle, matching Notion.](https://blokeditor.com/docs/header/)
- [List Bulleted, numbered, and to-do (checklist) lists with unlimited nesting. Each list item is a separate block. The toolbox shows three entries by default — one for each style — and items can be converted between styles via the block settings menu.](https://blokeditor.com/docs/list/)
- [Table A full-featured table block. Each cell contains its own block editor (any block type except `header`, `table` and `column_list`, which are always restricted inside cells). Supports merging and splitting cells (`colspan`/`rowspan`, with covered cells recorded as `mergedInto`), heading rows, heading columns, column resizing, cell background/text colours, row/column add and delete controls, copy/paste, and a text density switch (compact or comfortable) in the block settings menu.](https://blokeditor.com/docs/table/)
- [Toggle A collapsible toggle block with a clickable arrow. Child blocks are nested inside the toggle and hidden when collapsed. Toggling is controlled by clicking the arrow icon, or programmatically via the public Block API: `api.blocks.getById(id)?.call("expand")` / `.call("collapse")`. Toggle headings (Header blocks with `isToggleable: true`) accept the same two commands. These are string-addressed commands routed through `BlockAPI.call()` — they are not declared as methods on the exported tool classes. The open/collapsed state is persisted via `isOpen` and restored on reload; toggles default to open.](https://blokeditor.com/docs/toggle/)
- [Callout A container block for highlighted content with an emoji icon. Supports customisable text and background colours via a colour picker. Child blocks are nested inside the callout. Useful for tips, warnings, notes, and other call-to-action content. Enter adds a line inside the panel; pressing it again on the empty last line leaves the callout, so the blank line becomes the paragraph below instead of padding the panel out.](https://blokeditor.com/docs/callout/)
- [Database A multi-view database block supporting board (Kanban) and list views. Stores a schema of typed properties (text, select, multiSelect, date, checkbox, etc.) and view configurations. Rows are stored as child `database-row` blocks. Supports grouping, drag-and-drop reordering, inline editing, and an optional backend sync adapter. (`sorts` and `filters` are persisted in the view config but are not applied yet.)](https://blokeditor.com/docs/database/)
- [Database Row An internal block tool that stores a single database row. Not user-insertable — rows are created and managed by the parent Database block. Each row stores property values conforming to the parent database schema and a position string for ordering.](https://blokeditor.com/docs/database-row/)
- [Divider A horizontal line separator. Renders a semantic `<hr>` element. Has no editable content or settings. Can be inserted via the toolbox or by typing `---` in an empty paragraph.](https://blokeditor.com/docs/divider/)
- [Spacer An adjustable vertical gap. Drag either edge grip — or focus one and press ArrowUp/ArrowDown — to resize. Its main job is lining up content across sibling columns of unequal length, replacing piles of empty paragraphs. Invisible in read-only mode.](https://blokeditor.com/docs/spacer/)
- [Quote A blockquote with a left border accent. Supports two sizes (default and large) switchable via the block settings menu. Pasting a `<blockquote>` element automatically creates a quote block.](https://blokeditor.com/docs/quote/)
- [Code A syntax-highlighted code block with a language picker, an optional line-number gutter, and a copy-to-clipboard button. Supports 30+ languages via Prism. LaTeX and Mermaid languages include a live preview tab. Pasting markdown fenced code blocks (```) or `<pre>` elements automatically creates a code block, and the language comes across whenever the pasted source names it — a fence that opens with ```sql, or a labelled code block copied out of an app such as Gemini.](https://blokeditor.com/docs/code/)
- [Image Embed an image via URL upload or file paste.](https://blokeditor.com/docs/image/)
- [Columns A layout block that arranges its children into side-by-side columns. The column list itself holds no content — each column is a child `column` block, and the blocks you write live inside those columns (via `contentIds`). Columns can be created three ways: from the toolbox · by dragging a block beside another · by selecting multiple blocks and choosing "Turn into columns". Column widths are resizable via the separators between columns. Both tools can be registered at once with the `Columns` group handle — `tools: { columns: Columns }` expands to the `column_list` and `column` tools; saved JSON still contains `column_list` and `column` blocks.](https://blokeditor.com/docs/column_list/)
- [Column A single column inside a column list. Not user-insertable on its own — columns are created and managed by the parent `column_list` block. Child blocks are nested inside the column via `contentIds`. The optional `widthRatio` controls the column’s width relative to its siblings (applied as flex-grow); omit it for equal width.](https://blokeditor.com/docs/column/)
- [Embed A live interactive iframe for a pasted provider URL (YouTube, Vimeo, Figma, CodePen, and 100+ other services), like Notion’s "Create embed". Pure client-side: the URL is matched against a built-in embed registry and resolved into a provider-sanctioned iframe URL. By default only registry-matched URLs are embedded; set the editor-level `linkPaste.allowGenericEmbed: true` to also embed unmatched https URLs in a generic sandboxed iframe (saved with an empty `service`). Supports resizing (document-style providers such as Google Docs, Sheets, Slides, Forms and Drive also get a bottom handle for adjusting the embed height), alignment (left/center/right), and an optional caption.](https://blokeditor.com/docs/embed/)
- [Bookmark A static OpenGraph card for a pasted link, like Notion’s "Create bookmark". Shows the page title, description, preview image, favicon, and domain. Metadata is fetched from a consumer-supplied unfurl endpoint (CORS makes a backend mandatory) — Blok ships only the contract.](https://blokeditor.com/docs/bookmark/)
- [File An attachment card for any uploaded file. Shows a type icon, filename, human-readable size, a download action, and an optional caption. Files are sent through a consumer-supplied uploader; when none is provided the tool falls back to a local blob URL (uploadByFile) or the pasted URL itself (uploadByUrl). An optional MIME allowlist and max size can gate what is accepted.](https://blokeditor.com/docs/file/)
- [Audio A music-player style audio block. Renders an uploaded or linked audio file with a custom control bar (play/pause, a waveform scrubber, volume, playback speed, loop), optional cover art, title/artist metadata, and an optional caption you switch on from the block settings menu (`captionVisible`). Waveform peaks and duration are decoded once and cached in the saved data so playback renders instantly on reload. Audio is sent through a consumer-supplied uploader; when none is provided the tool falls back to a local blob URL (uploadByFile) or the pasted URL (uploadByUrl). Share links from Dropbox, GitHub, GitLab, Hugging Face, Google Cloud Storage, and the Internet Archive are rewritten to their direct-content form automatically; Google Drive and OneDrive links additionally require an `uploadByUrl` backend because those hosts block anonymous browser hotlinking. An optional MIME allowlist and max size gate what is accepted.](https://blokeditor.com/docs/audio/)
- [Video A full-featured video player block. Renders an uploaded or linked video with a custom control bar (play/pause, scrubber with buffered range and hover preview, volume, playback speed, loop, picture-in-picture, theater and fullscreen modes), an optional caption, and an ambient glow behind the player. Videos are sent through a consumer-supplied uploader; when none is provided the tool falls back to a local blob URL (uploadByFile) or the pasted URL (uploadByUrl). An optional MIME allowlist and max size gate what is accepted.](https://blokeditor.com/docs/video/)

## Inline Tools

- [Bold Wraps selected text in `<strong>`. Activated with Cmd/Ctrl+B or by clicking the B button in the inline toolbar. Supports nested bold ranges and normalises overlapping markup on paste.](https://blokeditor.com/docs/bold/)
- [Italic Wraps selected text in `<i>` (pasted `<em>` is also preserved). Activated with Cmd/Ctrl+I or by clicking the I button in the inline toolbar.](https://blokeditor.com/docs/italic/)
- [Link Wraps selected text in `<a href="...">`. Activated with Cmd/Ctrl+K. Clicking the button on existing linked text opens the URL input allowing the link to be edited or removed. `target` and `rel` are always written alongside `href` and come from `BlokConfig.link` — defaults `_blank` and `nofollow`, with `target="_self"` forced for same-page hrefs (a `#anchor`, or a URL resolving to the current origin and pathname). A `link.transform` can override any of href, target and rel.](https://blokeditor.com/docs/link/)
- [Marker Applies text colour or background colour to selected text using `<mark style="color:...">` or `<mark style="background-color:...">`. Click the toolbar button, then choose the Text color or Background tab. Each tab shows preset swatches, a selected-colour preview, and a Default reset. Recently used colours appear below the palette. Every colour is normalised to a CSS custom property (`var(--blok-color-<name>-<text|bg>)`) so themes can restyle it: the picker offers only the nine presets, and any other CSS colour applied programmatically is snapped to the perceptually nearest preset. There is no distance threshold — only values already written as `var(...)`, values the colour parser cannot read (a CSS named colour such as `rebeccapurple`), and the default page background colours pass through untouched. Raw colours found on `<mark>` elements — e.g. pasted from another editor — are rewritten to the nearest preset var on load, so arbitrary hex values are not preserved. Cmd/Ctrl+Shift+H does not open the picker: it re-applies the last colour picked in this session straight to the selection, defaulting to a yellow highlight (`var(--blok-color-yellow-bg)`) on first use, and does nothing when the selection is collapsed.](https://blokeditor.com/docs/marker/)
- [Underline Wraps selected text in `<u>`. Activated with Cmd/Ctrl+U or by clicking the U button in the inline toolbar.](https://blokeditor.com/docs/underline/)
- [Strikethrough Wraps selected text in `<s>`. Activated with Cmd/Ctrl+Shift+S or by clicking the S button in the inline toolbar.](https://blokeditor.com/docs/strikethrough/)
- [Inline Code Wraps selected text in `<code>`. Activated with Cmd/Ctrl+E or by clicking the code button in the inline toolbar. Useful for marking up variable names, function calls, and short code snippets within text.](https://blokeditor.com/docs/inlineCode/)
- [Equation Renders inline math (LaTeX) with KaTeX. Activated with Cmd/Ctrl+Shift+E — wraps the selected text, or a formula typed into the popover input, in a `<span data-latex="...">`. The `data-latex` attribute is the formula: the KaTeX markup is derived from it, is stripped on save, and is regenerated whenever the block renders (load, paste, undo). Read-only surfaces that never mount an editor — `blocksToHtml` / `<BlokView>` — display the source instead; pass an `inlineRenderers` entry to render the math there too.](https://blokeditor.com/docs/equation/)
- [Clear Format Removes inline formatting (bold, italic, underline, strikethrough, inline code, highlight) from the selected text while keeping links intact. Applied by clicking the Tx button in the inline toolbar.](https://blokeditor.com/docs/clearFormat/)
- [Superscript & Subscript One toolbar button with a two-option popover that toggles superscript (`<sup>`) or subscript (`<sub>`) on the selection. The two modes are mutually exclusive — applying one removes the other. Shortcuts: Cmd/Ctrl+Period for superscript, Cmd/Ctrl+Comma for subscript.](https://blokeditor.com/docs/supSub/)
