Skip to content

Quick Start

This guide walks you through creating a fully-featured rich text editor with toolbar, text formatting, headings, lists, and custom fonts.

notectl editor with toolbar

The simplest setup — a text editor with bold, italic, and underline:

import { createEditor } from '@notectl/core';
const editor = await createEditor({
placeholder: 'Start typing...',
autofocus: true,
});
document.getElementById('app').appendChild(editor);

That’s it. The createEditor factory auto-registers a TextFormattingPlugin with bold, italic, and underline enabled by default.

Runtime styling defaults to strict CSP-safe mode. For CSP policies and nonce wiring, see Content Security Policy.

The fastest way to get a production editor with all plugins is using createFullPreset():

import { createEditor, ThemePreset } from '@notectl/core';
import { createFullPreset } from '@notectl/core/presets';
const editor = await createEditor({
...createFullPreset(),
theme: ThemePreset.Light,
placeholder: 'Start typing...',
autofocus: true,
});
document.getElementById('app').appendChild(editor);

This gives you 8 toolbar groups with all standard plugins (font, text formatting, headings, lists, tables, code blocks, images, and more) plus HardBreakPlugin for Shift+Enter line breaks.

You can override individual plugin configs:

const editor = await createEditor({
...createFullPreset({
list: { interactiveCheckboxes: true },
heading: { levels: [1, 2, 3] },
}),
theme: ThemePreset.Dark,
placeholder: 'Start typing...',
});

See the Plugin Presets guide for all available options.

If you need full control over which plugins appear and how they’re grouped, configure the toolbar manually:

  1. Import the plugins you need

    import { createEditor } from '@notectl/core';
    import { TextFormattingPlugin } from '@notectl/core/plugins/text-formatting';
    import { HeadingPlugin } from '@notectl/core/plugins/heading';
    import { ListPlugin } from '@notectl/core/plugins/list';
    import { LinkPlugin } from '@notectl/core/plugins/link';
    import { BlockquotePlugin } from '@notectl/core/plugins/blockquote';
    import { TablePlugin } from '@notectl/core/plugins/table';
    import { CodeBlockPlugin } from '@notectl/core/plugins/code-block';
  2. Configure the toolbar layout

    The toolbar option takes an array of arrays. Each inner array is a visual group separated by dividers:

    const editor = await createEditor({
    toolbar: [
    [new TextFormattingPlugin()],
    [new HeadingPlugin()],
    [new BlockquotePlugin(), new LinkPlugin()],
    [new ListPlugin()],
    [new TablePlugin()],
    [new CodeBlockPlugin()],
    ],
    placeholder: 'Start typing...',
    autofocus: true,
    });
  3. Add it to the DOM

    document.getElementById('app').appendChild(editor);
  4. Listen for changes

    editor.on('stateChange', ({ newState }) => {
    console.log('Content changed:', newState.doc);
    });
    editor.on('ready', () => {
    console.log('Editor is ready!');
    });

Here’s a complete HTML page:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Editor</title>
<style>
#app {
max-width: 800px;
margin: 2rem auto;
}
notectl-editor {
display: block;
border: 1px solid #e0e0e0;
border-radius: 8px;
min-height: 400px;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./main.ts"></script>
</body>
</html>

notectl provides multiple output formats:

// Get structured JSON (Document model)
const json = editor.getJSON();
// Get sanitized HTML
const html = await editor.getContentHTML();
// Get plain text
const text = editor.getText();
// Check if editor is empty
const empty = editor.isEmpty();
// Set content from HTML
await editor.setContentHTML('<h1>Hello</h1><p>Welcome to <strong>notectl</strong>!</p>');
// Set content from JSON (Document model)
editor.setJSON({
children: [
{
type: 'paragraph',
id: 'block-1',
children: [{ type: 'text', text: 'Hello world', marks: [] }],
},
],
});
// Toggle formatting
editor.commands.toggleBold();
editor.commands.toggleItalic();
editor.commands.toggleUnderline();
// Undo / Redo
editor.commands.undo();
editor.commands.redo();
// Execute any registered command by name
editor.executeCommand('toggleStrikethrough');
editor.executeCommand('insertHorizontalRule');

Since notectl is a Web Component, it works in any framework:

import { useEffect, useRef } from 'react';
import { createEditor } from '@notectl/core';
import type { NotectlEditor } from '@notectl/core';
export function Editor() {
const containerRef = useRef<HTMLDivElement>(null);
const editorRef = useRef<NotectlEditor | null>(null);
useEffect(() => {
let mounted = true;
createEditor({
placeholder: 'Start typing...',
autofocus: true,
}).then((editor) => {
if (mounted && containerRef.current) {
containerRef.current.appendChild(editor);
editorRef.current = editor;
}
});
return () => {
mounted = false;
editorRef.current?.destroy();
};
}, []);
return <div ref={containerRef} />;
}