How to use
TextEditor is a block-based rich text editor in the Notion mold — content is a flat array of typed blocks (paragraphs, headings, lists, tables, media embeds, and more), each independently editable, reorderable, and convertible to a different block type. It's built on native contentEditable, not a third-party rich-text engine, so there's nothing extra to bundle.
import { TextEditor, type Block } from "oks-ui";Pass value/onChange to own the block array yourself; omit onChange and TextEditor manages its own internal state instead. Either way, value is what seeds the initial content on mount.
function Notes() {
const [blocks, setBlocks] = useState<Block[]>([
{ id: "1", type: "paragraph", props: {}, content: [], children: [] },
]);
return <TextEditor value={blocks} onChange={setBlocks} />;
}Type / at the start of an empty block to open the slash menu — all 14 block types by default, or a filtered subset via allowedBlocks if a given editor shouldn't offer everything (a comments field that only needs paragraph/bulletList, say).
<TextEditor
value={blocks}
onChange={setBlocks}
allowedBlocks={["paragraph", "heading", "bulletList", "numberedList"]}
/>TextEditor doesn't own file storage — pass onImageUpload and ImageBlock's drop zone calls it as a delegating callback, displaying whatever URL it resolves to. Without onImageUpload, ImageBlock still accepts a pasted/typed image URL directly; only the drop zone itself needs the callback.
<TextEditor
value={blocks}
onChange={setBlocks}
onImageUpload={async (file) => {
const url = await uploadToStorage(file);
return url;
}}
/>readOnly disables every block's editable region (text, table cells, code blocks) and hides the drag handle and per-block controls — for displaying already-saved content without any editing chrome at all.
<TextEditor value={savedBlocks} readOnly />blockToHtml (exported alongside TextEditor) serializes a single block to an HTML string — call it per block to build a full document, for anywhere you need static HTML output rather than the live editable component (an email, a printed view, a public read-only page).
import { blockToHtml } from "oks-ui";
const html = blocks.map(blockToHtml).join("");All available props for TextEditor. See the full component page for an interactive playground.
| Prop | Type | Default | Description |
|---|---|---|---|
value | Block[] | [] | The current block array. Uncontrolled internally (updates flow through onChange), but value seeds the initial content. |
placeholder | string | "Type / for blocks..." | Aria-label for the editor root and the empty-paragraph placeholder text. |
value
Block[]
Default: []
The current block array. Uncontrolled internally (updates flow through onChange), but value seeds the initial content.
placeholder
string
Default: "Type / for blocks..."
Aria-label for the editor root and the empty-paragraph placeholder text.
| Prop | Type | Default | Description |
|---|---|---|---|
allowedBlocks | BlockType[] | all 14 types | Restricts the slash menu and the drag-handle's convert-cycle button to this list. |
readOnly | boolean | false | Disables every block's contentEditable region and hides the drag handle/per-block controls. |
autoFocus | boolean | false | Focuses the first block on mount. |
allowedBlocks
BlockType[]
Default: all 14 types
Restricts the slash menu and the drag-handle's convert-cycle button to this list.
readOnly
boolean
Default: false
Disables every block's contentEditable region and hides the drag handle/per-block controls.
autoFocus
boolean
Default: false
Focuses the first block on mount.
| Prop | Type | Default | Description |
|---|---|---|---|
onChange | (blocks: Block[]) => void | — | Called with the full block array whenever content changes. |
onImageUpload | (file: File) => Promise<string> | — | Delegating upload callback for ImageBlock's drop zone — oks-ui doesn't own file storage, it just calls this and displays the returned URL. Without it, ImageBlock accepts a pasted URL only. |
onChange
(blocks: Block[]) => void
Default: —
Called with the full block array whenever content changes.
onImageUpload
(file: File) => Promise<string>
Default: —
Delegating upload callback for ImageBlock's drop zone — oks-ui doesn't own file storage, it just calls this and displays the returned URL. Without it, ImageBlock accepts a pasted URL only.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Class applied to the root element. |
className
string
Default: —
Class applied to the root element.