The BlokEditor React component
The all-in-one editor component shipped by the framework adapters. It is <BlokEditor> in @bloklabs/react and @bloklabs/vue, and <blok-editor> (BlokEditorComponent) in @bloklabs/angular.
React and Vue accept every editor config option as a prop. They forward unknown props and attributes to the container div.
Angular works differently. It declares a fixed set of @Input()s: tools, data, readOnly, hideToolbar, toolbarPosition, inlineToolbar, theme, width, placeholder, styleTokens, i18n, autofocus, migrations, onBeforeRender, onBeforePaste and onError.
Every other config key goes through the [config] escape hatch. That covers sanitizer, minHeight, defaultBlock, dataModel, link, linkPaste, tunes, user, resolveUser, uploader, server, ticket, persistence, collaboration, notifier, logLevel, onEnter, onSubmit, scrollToBlock and so on. Angular also does not forward host attributes onto the container div.
You read the live Blok instance in each adapter.
- In React use ref/onReady.
- In Vue use the
instanceon a template ref or the@readyemit. - In Angular use the
instancesignal or the(ready)output.
The props below cover the adapter-specific surface. Everything else matches the Configuration options.
Reaching the editor instance
The methods below run on the editor you created with new Blok(). They are available once editor.isReady resolves.
// 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>: you 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.
UseBlokConfig itself documents the subset that stays reactive after mount: readOnly, hideToolbar, toolbarPosition, inlineToolbar, autofocus, theme, width, placeholder, style.tokens, i18n and data sync in place on the same instance. Every other option is read once at editor creation.
It returns null until the editor exists (SSR, first render).
- React takes a
depsdependency LIST as the second argument. - Vue takes a reactive config source (ref or getter) and a SINGLE
recreateKeyas the second argument. - The Angular equivalent is the
[blokContent]directive (BlokContentDirective).- It builds the instance into its own host element and exposes it as the
instancesignal /(ready)output.
- It builds the instance into its own host element and exposes it as the
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 componentThe mount point for an instance created by useBlok. It 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 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. The Angular counterpart is the [blokContent] directive, which creates the instance itself instead of receiving one.
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 | EnvironmentProvidersRegisters app-wide Blok defaults. Every editor beneath it then inherits a shared tools registry, theme or i18n config instead of repeating them per instance.
- React spells it as
<BlokProvider defaults={…}>, withuseBlokDefaults()to read them back. - Vue spells it as
provideBlok(defaults)called in a parent'ssetup, backed by theBLOK_DEFAULT_CONFIGinjection key, withuseBlokDefaults()to read. - Angular spells it as
provideBlok(defaults)returningEnvironmentProvidersfor aprovidersarray, backed by theBLOK_DEFAULT_CONFIGinjection token.
The merge rule is identical in all three: a defined per-instance config value overrides the default. tools is the exception. There the two registries are merged, so the shared registry composes with per-instance additions instead of being replaced.
// 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 })],
});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 (for example an uploader callback) are re-bound to the latest render automatically. So inline closures are safe, and only a changed tool CLASS needs a Vue and Angular have no equivalent. There a closure in a tool config is captured when the editor is constructed, and then goes stale. Keep it in a stable ref/field, or force a rebuild by changing |
data | OutputData | LooseOutputData | null | Editor content (reactive). It seeds the initial document. After mount, new content re-renders in place on the same instance and never recreates the editor. That includes transitions to and from empty content. Updates are deduped with the same structural lens as equalsOutputData, so echoing the editor's own output back never clobbers the caret. That holds even after a persistence layer strips it: a fresh Imperative content calls stay in the same world. React's useBlokHandle().clear() / .render() and Angular's BlokEditorComponent.render() update that baseline once they land. So setting A whole-document Loose backend DTOs are accepted as-is. Angular widens the type to |
onSave | (data: OutputData, api: API) => void | The output half of the controlled component. It fires (debounced) with the full serialized document on every content change, so there is no manual save() polling. Wiring onSave={setData} is safe and recursion-free.
You can also use |
onChange | (api: API, event: BlockMutationEvent | BlockMutationEvent[]) => void | Low-level mutation events (block added/changed/moved/removed). Use it when you need per-mutation granularity instead of serialized output. A batch of mutations arrives as an ARRAY, so branch on The two positional arguments are React's arity. Vue's |
onReady | (editor: Blok) => void | Called with the live Blok instance, exactly once per editor instance. It fires after the forwarded ref commits, so ref.current is also populated. The editor is recreated, and onReady fires again, only when deps/recreateKey change or the component remounts. Changes to data, including to and from empty content, re-render in place and never re-fire it. Vue and Angular spell it as the |
deps / recreateKey | DependencyList (React) | unknown (Vue, Angular) | Values whose identity change destroys and recreates the editor. Use it for structural config like tool classes.
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, for example when 'auto' follows the OS. Vue and Angular spell it as the |
width | 'narrow' | 'full' | Content width mode (reactive). It is synced after mount via editor.width.set(). See the Width API for the imperative surface (get / set / toggle). |
style | BlokConfig['style'] | Styling config. It replaces rather than merges: pass the whole palette, because tokens dropped from it stop applying. Angular has no |
i18n | BlokConfig['i18n'] | Internationalization config (reactive). A changed
|
locale | string | React only. A library-neutral BCP-47 shorthand for When both are given, it WINS over Vue and Angular have no such prop. There you pass the locale inside the |
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 |
onBlocksRendered | (payload: BlocksRenderedPayload) => void | Called after a batch render completes (the core blocks:rendered event). It is the declarative analog of editor.on('blocks:rendered', …). Vue and Angular spell it as the |
onBlockRendered | (payload: BlockRenderedPayload) => void | Called for each block rendered into the DOM (core block:rendered event). Vue and Angular spell it as the |
ref | Ref<Blok | null> | Forwarded to the live Blok instance for imperative calls (save, render, blocks, caret, …). It is 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 one safely no-ops until ready, and handle.current is the escape hatch to the full instance. When you also drive content through the |
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). |