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.

For a production editor with a toolbar and all features:

  1. Import the plugins you need

    import {
    createEditor,
    TextFormattingPlugin,
    HeadingPlugin,
    ListPlugin,
    LinkPlugin,
    BlockquotePlugin,
    TablePlugin,
    CodeBlockPlugin,
    StrikethroughPlugin,
    TextColorPlugin,
    AlignmentPlugin,
    HorizontalRulePlugin,
    FontPlugin,
    FontSizePlugin,
    } from '@notectl/core';
    import { STARTER_FONTS } from '@notectl/core/fonts';
  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: [
    // Group 1: Text formatting
    [new TextFormattingPlugin({ bold: true, italic: true, underline: true })],
    // Group 2: Block types
    [new HeadingPlugin()],
    // Group 3: Block elements
    [new BlockquotePlugin(), new LinkPlugin()],
    // Group 4: Lists
    [new ListPlugin()],
    // Group 5: Tables
    [new TablePlugin()],
    // Group 6: Code
    [new CodeBlockPlugin()],
    // Group 7: Inline formatting
    [new HorizontalRulePlugin(), new StrikethroughPlugin(), new TextColorPlugin()],
    // Group 7: Typography
    [
    new AlignmentPlugin(),
    new FontPlugin({ fonts: [...STARTER_FONTS] }),
    new FontSizePlugin({ sizes: [12, 16, 24, 32, 48], defaultSize: 16 }),
    ],
    ],
    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 = editor.getHTML();
// Get plain text
const text = editor.getText();
// Check if editor is empty
const empty = editor.isEmpty();
// Set content from HTML
editor.setHTML('<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} />;
}