---
title: "BlokEditor React Component — Props Reference"
description: "Every prop on BlokEditor: data, tools, onChange, onSave, readOnly, onReady, and the imperative ref API."
source: https://blokeditor.com/docs/blok-editor/
lastmod: 2026-08-02
---

Framework JavaScript

Framework adapters BlokEditor component

On this page useBlok(config, deps?)

# The BlokEditor React 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, 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, 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.

Last updated Jul 17, 2026 [Edit this page on GitHub](https://github.com/JackUait/blok/blob/main/docs/src/components/api/api-data.ts)

### Reaching the editor instance

The methods below run on the editor you created with new Blok(). They are available once editor.isReady resolves.

TypeScript

```
// You already hold the instance returned by the constructor.
const editor = new Blok({ holder: 'editor' });
await editor.isReady;

// Call any API method on it.
editor.caret.setToLastBlock('end');
```

## Methods

### useBlok(config, deps?)

Blok | null (React) | Ref<Blok | null> (Vue)

The split mount path behind `<BlokEditor>`: create the instance yourself and hand it a mount point. `useBlok` takes the SAME options as the component — its config type is `UseBlokConfig`, which is `BlokConfig` minus `holder` (the adapter owns the mount element) plus an adapter-level `width`. The reactive-after-mount subset is documented on `UseBlokConfig` itself: `readOnly`, `hideToolbar`, `inlineToolbar`, `autofocus`, `theme`, `width`, `placeholder`, `style.tokens`, `i18n` and `data` sync in place on the same instance; every other option is consumed once at editor creation. It returns null until the editor exists (SSR, first render). React takes a `deps` dependency LIST as the second argument; Vue takes a reactive config source (ref or getter) and a SINGLE `recreateKey` as the second argument. Angular's equivalent is the `[blokContent]` directive (`BlokContentDirective`), which builds the instance into its own host element and exposes it as the `instance` signal / `(ready)` output.

TypeScript

```
import { useBlok, BlokContent } from '@bloklabs/react';
import { Header, Paragraph } from '@bloklabs/core/tools';

export function Editor() {
  const editor = useBlok({
    tools: { paragraph: Paragraph, header: Header },
    readOnly: false,
  });

  return <BlokContent editor={editor} className="my-editor" />;
}
```

### BlokContent

React/Vue component

The mount point for an instance created by `useBlok`. Renders a `<div>` and adopts the editor's detached holder into it. Its only own prop is `editor: Blok | null` (`BlokContentProps`) — pass null before the instance exists and it simply renders the empty container. In React it also extends `React.HTMLAttributes<HTMLDivElement>`, so `className`, `id` and the rest are forwarded to that div, and it forwards a ref to it. Angular's counterpart is the `[blokContent]` directive, which creates the instance itself rather than receiving one.

TypeScript

```
import { useBlok, BlokContent } from '@bloklabs/react';

const editor = useBlok({ tools });

// `editor` is null until the instance exists — BlokContent handles that
<BlokContent editor={editor} className="prose" />
```

### provideBlok(defaults)

void | EnvironmentProviders

Registers app-wide Blok defaults so every editor beneath it inherits a shared tools registry, theme or i18n config instead of repeating them per instance. React spells it as `<BlokProvider defaults={…}>` (with `useBlokDefaults()` to read them back), Vue as `provideBlok(defaults)` called in a parent's `setup` (backed by the `BLOK_DEFAULT_CONFIG` injection key, with `useBlokDefaults()` to read), and Angular as `provideBlok(defaults)` returning `EnvironmentProviders` for a `providers` array (backed by the `BLOK_DEFAULT_CONFIG` injection token). Merge rule, identical in all three: a defined per-instance config value overrides the default, EXCEPT `tools`, where the two registries are merged — the shared registry composes with per-instance additions rather than being replaced.

TypeScript

```
// React
import { BlokProvider } from '@bloklabs/react';

<BlokProvider defaults={{ theme: 'dark', tools: sharedTools }}>
  <App />
</BlokProvider>

// Vue — inside a parent component's setup()
import { provideBlok } from '@bloklabs/vue';
provideBlok({ theme: 'dark', tools: sharedTools });

// Angular
import { provideBlok } from '@bloklabs/angular';
bootstrapApplication(AppComponent, {
  providers: [provideBlok({ theme: 'dark', tools: sharedTools })],
});
```

TypeScript

```
import { useState } from 'react';
import { BlokEditor } from '@bloklabs/react';
import { Header, Paragraph, List } from '@bloklabs/core/tools';
import type { OutputData } from '@bloklabs/core';

export function Editor() {
  const [data, setData] = useState<OutputData>();

  // data + onSave form a controlled component: onSave fires (debounced)
  // with the serialized document; echoing it back is deduped and
  // caret-stable, while genuine external data changes re-render in place.
  return (
    <BlokEditor
      tools={{ paragraph: Paragraph, header: Header, list: List }}
      data={data}
      onSave={setData}
      theme="auto"
      className="my-editor"
    />
  );
}
```

## BlokEditor component

| Property | Description |
| --- | --- |
| `tools` | `Record<string, ToolConstructable | ToolSettings>` | Block tools to register. React only: functions anywhere inside a tool's config (e.g. an uploader callback) are re-bound to the latest render automatically, so inline closures are safe and only a changed tool CLASS needs a `deps` entry. Vue and Angular have no equivalent — a closure in a tool config is captured when the editor is constructed and goes stale, so keep it in a stable ref/field, or force a rebuild by changing `recreateKey`. |
| `data` | `OutputData | LooseOutputData | null` | Editor content (reactive). Seeds the initial document; after mount, new content — including transitions to and from empty content — re-renders in place on the same instance (never recreates the editor). Updates are deep-equal–deduped, so echoing the editor's own output back never clobbers the caret. A whole-document `null` is the controlled "clear to empty" value (route it through `toRenderableData` when you call render() yourself), and loose backend DTOs are accepted as-is. Angular widens it to `… | undefined`; Vue's prop is declared `PropType<OutputData>` and is the narrow outlier. |
| `onSave` | `(data: OutputData, api: API) => void` | The output half of the controlled component: fires (debounced) with the full serialized document on every content change — no manual save() polling. Wiring onSave={setData} is safe and recursion-free. The `(data, api)` arity is React's, where the prop is passed straight through to the core config. Vue maps it to the `save` emit and Angular to the `save` output, both carrying `OutputData` only: `@save="(data) => …"` / `(save)="…"` — or use `v-model:data` / `[(data)]`, backed by Vue's `update:data` emit and Angular's `dataChange` output. |
| `onChange` | `(api: API, event: BlockMutationEvent | BlockMutationEvent[]) => void` | Low-level mutation events (block added/changed/moved/removed), for when you need per-mutation granularity instead of serialized output. A batch of mutations arrives as an ARRAY, so branch on `Array.isArray(event)` before reading `event.detail`. Two positional arguments is React's arity; Vue's `change` emit and Angular's `change` output deliver ONE object instead — `@change="({ api, event }) => …"` / `(change)="…"` with `$event.api` and `$event.event`. |
| `onReady` | `(editor: Blok) => void` | Called with the live Blok instance, exactly once per editor instance. Fires after the forwarded ref commits, so ref.current is also populated. The editor is recreated (and onReady re-fired) only when deps/recreateKey change or the component remounts — data changes, including to/from empty content, re-render in place and never re-fire it. Vue and Angular spell it as the `ready` emit/output (`@ready` / `(ready)`). |
| `deps / recreateKey` | `DependencyList (React) | unknown (Vue, Angular)` | Values whose identity change destroys and recreates the editor (for structural config like tool classes). React takes an array — `deps` — and recreates when any entry's identity changes. Vue (`:recreate-key`) and Angular ([recreateKey]) take a SINGLE value instead and recreate when that value's identity changes; pass a fresh object/array literal or a bumped counter. `deps` does not exist on Vue/Angular. Keep each value referentially stable. Functions inside tool configs do NOT belong here on React — they are re-bound to the latest render automatically. |
| `readOnly` | `boolean | ReadOnlyModeConfig` | Read-only mode. Reactive: toggles in place after mount, without remounting. |
| `theme` | `'light' | 'dark' | 'auto'` | Color theme (reactive). Don't wrap the component in styled() or any HOC that reserves the theme prop — it would never reach the editor. |
| `onThemeChange` | `(resolvedTheme: 'light' | 'dark') => void` | Called with the resolved theme whenever it changes (e.g. when 'auto' follows the OS). Vue and Angular spell it as the `theme-change` emit / `themeChange` output (`@theme-change` / `(themeChange)`). |
| `width` | `'narrow' | 'full'` | Content width mode (reactive). Synced after mount via editor.width.set() — see the Width API for the imperative surface (get / set / toggle). |
| `style` | `BlokConfig['style']` | Styling config. `style.tokens` is reactive: changed `--blok-*` overrides sync in place after mount via editor.tokens.set() (deep-equal deduped), so a host light/dark toggle needs no remount. Replace semantics — pass the whole palette; tokens dropped from it stop applying. Angular has no `style` input: it exposes only `style.tokens`, as the separate `[styleTokens]` input (`Record<string, string>`). The remaining `style` keys — `fontSize`, `contentAlign`, `nativeSelection` — must go through Angular's `[config]` escape hatch. |
| `i18n` | `BlokConfig['i18n']` | Internationalization config (reactive). A changed `locale`, `messages` or `direction` syncs in place after mount via editor.i18n.update() (deep-equal deduped), so a language switcher relabels the editor without remounting it — caret, focus, selection and undo history survive. `defaultLocale` is the exception and is read only at construction. Angular exposes this as the [i18n] input. |
| `locale` | `string` | React only. A library-neutral BCP-47 shorthand for `i18n.locale`: it is folded into the i18n config and applied in place via editor.i18n.update({ locale }), so a language switch keeps caret, focus and undo history. It WINS over `i18n.locale` when both are given. Pair it with `getDirection` / `normalizeLocale`, re-exported from @bloklabs/react, to compute `dir` and validate tags yourself. Vue and Angular have no such prop — pass the locale inside the `i18n` prop/input. |
| `autofocus` | `boolean` | Focus the editor after it mounts. |
| `placeholder` | `string | false` | Placeholder text handed to every block of the default tool — not only the first block; with the built-in paragraph it shows while a block is empty and focused. See the Configuration table for what `false` does (and does not) disable, and the Placeholder API to change it at runtime via editor.placeholder.set(). |
| `onBlocksRendered` | `(payload: BlocksRenderedPayload) => void` | Called after a batch render completes (core blocks:rendered event) — the declarative analog of editor.on('blocks:rendered', …). Vue and Angular spell it as the `blocks-rendered` emit / `blocksRendered` output. |
| `onBlockRendered` | `(payload: BlockRenderedPayload) => void` | Called for each block rendered into the DOM (core block:rendered event). Vue and Angular spell it as the `block-rendered` emit / `blockRendered` output. |
| `ref` | `Ref<Blok | null>` | Forwarded to the live Blok instance for imperative calls (save, render, blocks, caret, …). Null until the editor mounts, so calls must guard on ref.current. For the common shortcuts without the guards, @bloklabs/react's useBlokHandle() returns a stable, null-safe handle — attach it via ref={handle.ref} and call handle.focus()/save()/clear()/render()/setReadOnly() directly (each safely no-ops until ready); handle.current is the escape hatch to the full instance. |
| `className, id, …` | `HTMLAttributes<HTMLDivElement>` | Any prop that is not an editor config option is forwarded to the container div. Style the editor through className (style keeps its editor-config meaning). |
