Build Notion-like, Craft-like, Coda-like, Medium-like editors with Yoopta
Yoopta-Editor v6
Motivation. Why Yoopta?
Yoopta is fully open source and built with all my heart and love. It was born from real pain
I've integrated rich-text editors into products more times than I'd like to admit. Every time it was the same story: wrestling with complex APIs, fighting against opinionated UI that doesn't fit your product, hacking around limitations, and writing glue code that makes you mass text your wife and check hair transplant prices (I'm currently accepting donations for the procedure โ every star on GitHub brings me one follicle closer).
Yoopta was built to end that cycle. The idea is simple: give developers a headless core when they need full control, but also ship 20+ ready-made plugins, pre-built UI components (toolbars, slash menus, block actions), and theme presets (shadcn, Material) โ so you can launch a polished editing experience without thinking about implementing a rich-text editor in your project and engage in other business tasks
Whether you're building a simple blog editor, a Notion-like workspace, a CMS for landing pages, or even a full website builder โ Yoopta gives you the primitives to get there
If Yoopta saves you time, consider starring the repo or sponsoring the project. It keeps the project alive
Yoo AI โ AI App Builder Powered by Yoopta Editor
ai.yoopta.dev โ Describe your app. AI builds it with Yoopta plugins. You edit everything directly in the preview.
Yoo AI is an AI-powered web app builder built on top of Yoopta Editor. It takes everything developers love about Yoopta โ plugins, drag & drop, theming, collaboration โ and wraps it in an AI-first experience that anyone can use without writing code.
- AI generates multi-page apps from a text prompt using plugins from the marketplace
- Click any block to edit inline โ text, images, styles. The preview is the editor
- Connect your own API for dynamic data (product catalogs, listings, blog posts)
- AI Plugin Generator โ describe a custom block, AI builds it with live preview and source code
- 200+ community plugins โ developers build and publish blocks, AI learns to use them
- Real-time collaboration โ edit together with cursors and presence, powered by Yjs
Introduction
Yoopta-Editor is a free, open-source rich-text editor built for React apps. It's packed with features that let you build an editor as powerful and user-friendly as Notion, Craft, Coda, Medium etc. โ or go further and build a CMS, landing page builder, or website builder on top of it.
Built on top of Slate.js with a powerful plugin architecture, Yoopta-Editor gives you the flexibility to customize everythingโtweak the look, add features, or craft a completely custom user interface. The core is headless by default; Yoopta also provides pre-built theme presets so you can get a full editing experience and start quickly (shadcn theme @yoopta/themes-shadcn is available now; Material theme is in progress). Pre-built UI components via @yoopta/ui (toolbars, menus, block actions) let you improve the editing experience without building everything from scratch.
Examples & Demos
See what you can build with Yoopta:
| Example | Description | Demo | Source | | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | Full Setup | Complete editor with toolbar, slash menu, block actions, drag & drop, and mentions | Live | Source | | CMS / Website Builder | Visual page builder with drag-and-drop blocks, sidebar settings, and live preview โ build landing pages and websites | Live | Source | | Word Example | Microsoft Word-inspired editor with fixed toolbar, formatting, tables, and export to HTML/Markdown/Text/JSON | Live | Source | | Email Builder | Email composition tool with templates, signatures, split-view editing, HTML preview, and email-safe export | Live | Source | | README Editor | GitHub-flavored Markdown editor with live preview, section templates, and download as README.md | Live | Source | | Slack Chat | Slack-style messaging with channel list, rich text composer, mentions, and emoji support | Live | Source | | Social Media Chat | WhatsApp/Instagram-like chat with message bubbles, attachments, reactions, and status indicators | Live | Source | | Collaboration | Real-time multi-user editing with Yjs, remote cursors, and presence awareness | Live | Source | | Nested Plugins | Injecting child elements from other plugins into Accordion, Tabs, and Carousel components | Live | Source | | Large Document | Performance stress test with a large dataset to demonstrate scalability with many blocks | Live | Source |
Features
- Easy setup โ Sensible defaults; plugins and marks passed to
createYooptaEditor, then render<YooptaEditor /> - 20+ plugins โ Paragraph, headings, lists, code, image, video, table, callout, accordion, tabs, steps, divider, embed, file, link, mention, carousel, table-of-contents, and more
- Headless core โ Full control over UI; optional pre-built theme presets (
@yoopta/themes-shadcnavailable,@yoopta/themes-materialin progress) for styled block UI - Pre-built UI components (
@yoopta/ui) โ FloatingToolbar, SlashCommandMenu, ActionMenuList, BlockOptions, ElementOptions, FloatingBlockActions, SelectionBox, BlockDndContext so you don't have to build everything from scratch - Blocks, Elements, Marks APIs โ Programmatic control: insert/update/delete blocks and elements, apply text formatting (Bold, Italic, Highlight, etc.), custom marks supported
- Undo/redo โ Built-in history with
editor.undo()/editor.redo(); batch operations for single undo step - Drag and drop โ Reorder blocks with nested depth support; optional
SortableBlockfor custom DnD - Selection box โ Multi-block selection for copy, delete, or bulk operations
- Slash command & action menu โ Type
/for block insertion; floating block actions (+, drag handle, block options) - Inline elements โ Links, @mentions, emojis, inline math (KaTeX), and custom inline nodes within text
- Export โ HTML, Markdown, plain text, email template; get/set content as Yoopta JSON
- Real-time collaboration (
@yoopta/collaboration) โ Multi-user editing with Yjs CRDT, presence (awareness), remote cursors, and WebSocket provider; optional package for collaborative documents - Events โ
editor.on('change' | 'focus' | 'blur' | 'path-change' | 'block:copy')for sync, analytics, or custom logic - Keyboard shortcuts โ Customizable hotkeys; Tab/Shift+Tab for indent/outdent; shortcuts per plugin and mark
- Read-only mode โ Use the same editor instance for viewing or editing
- TypeScript โ Full type definitions for editor, blocks, elements, and plugins
- Mobile friendly โ Touch support; works in responsive layouts
- Custom plugins & marks โ Define new block types or text formats and plug them in
- Media & large docs โ Image/video optimization, lazy loading; performant with many blocks
- Theming โ CSS variables (light/dark); theme packages for plugin element styling
Installation
# Install peer dependencies and core packages
yarn add slate slate-react slate-dom @yoopta/editor
Add plugins you need
yarn add @yoopta/paragraph @yoopta/headings @yoopta/lists @yoopta/blockquote @yoopta/code @yoopta/image @yoopta/video @yoopta/embed @yoopta/file @yoopta/callout @yoopta/divider @yoopta/accordion @yoopta/table @yoopta/tabs @yoopta/steps @yoopta/mention @yoopta/math @yoopta/links
Add marks for text formatting
yarn add @yoopta/marks
Add UI components
yarn add @yoopta/ui
Optional: theme for styled block UI (Shadcn or Material)
yarn add @yoopta/themes-shadcn
Optional: real-time collaboration (Yjs, awareness, remote cursors)
yarn add @yoopta/collaboration
Quick Start
Plugins, initial value and marks are passed to createYooptaEditor
import { useMemo, useEffect } from 'react';
import YooptaEditor, { createYooptaEditor, type YooptaContentValue } from '@yoopta/editor';
import Paragraph from '@yoopta/paragraph';
import Headings from '@yoopta/headings';
import { Bold, Italic, Underline, Strike, CodeMark, Highlight } from '@yoopta/marks';
const PLUGINS = [Paragraph, Headings.HeadingOne, Headings.HeadingTwo, Headings.HeadingThree]; const MARKS = [Bold, Italic, Underline, Strike, CodeMark, Highlight];
const initialValue = {} as YooptaContentValue;
const EDITOR_STYLES = { width: 750, // useful when you want to create default block by clicking on empty are paddingBottom: 150, };
export default function Editor() { const editor = useMemo( () => createYooptaEditor({ plugins: PLUGINS, marks: MARKS, value: initialValue }), [], );
return ( <YooptaEditor editor={editor} style={EDITOR_STYLES} placeholder="Type / to open menu" onChange={(value) => console.log('onChange', value)} /> ); }
Themes
The editor and plugins are headless by default. For styled block UI you can use a theme package:
@yoopta/themes-shadcnโ Shadcn UI styled components (production ready)@yoopta/themes-materialโ Material Design (in progress)
import { applyTheme } from '@yoopta/themes-shadcn';
const plugins = applyTheme([ Paragraph, Callout, Headings.HeadingOne, Headings.HeadingTwo, Headings.HeadingThree, ]); const editor = createYooptaEditor({ plugins, marks: MARKS });
Option 2: Apply theme UI to a single plugin
import Callout from '@yoopta/callout';
import { CalloutUI } from '@yoopta/themes-shadcn/callout';
const CalloutWithUI = Callout.extend({ elements: CalloutUI }); // Use CalloutWithUI in your plugins array
See docs/core/themes for the full concept.
Adding UI Components
All UI (toolbar, slash menu, block actions) must be children of <YooptaEditor> so they can use useYooptaEditor(). Yoopta provides ready-to-use components from @yoopta/ui:
import { useMemo, useState, useRef } from 'react';
import YooptaEditor, { createYooptaEditor, Blocks, Marks, useYooptaEditor } from '@yoopta/editor';
import { FloatingToolbar, FloatingBlockActions, BlockOptions, SlashCommandMenu } from '@yoopta/ui';
// Floating toolbar for text formatting function MyToolbar() { const editor = useYooptaEditor();
return ( <FloatingToolbar> <FloatingToolbar.Content> <FloatingToolbar.Group> {editor.formats.bold && ( <FloatingToolbar.Button onClick={() => Marks.toggle(editor, { type: 'bold' })} active={Marks.isActive(editor, { type: 'bold' })}> B </FloatingToolbar.Button> )} </FloatingToolbar.Group> </FloatingToolbar.Content> </FloatingToolbar> ); }
// Floating block actions (plus button, drag handle) function MyFloatingBlockActions() { const editor = useYooptaEditor(); const [blockOptionsOpen, setBlockOptionsOpen] = useState(false); const dragHandleRef = useRef<HTMLButtonElement>(null);
return ( <FloatingBlockActions frozen={blockOptionsOpen}> {({ blockId }) => ( <> <FloatingBlockActions.Button onClick={() => { if (!blockId) return; const block = Blocks.getBlock(editor, { id: blockId }); if (block) editor.insertBlock('Paragraph', { at: block.meta.order + 1, focus: true }); }}> + </FloatingBlockActions.Button> <FloatingBlockActions.Button ref={dragHandleRef} onClick={() => setBlockOptionsOpen(true)}> โฎโฎ </FloatingBlockActions.Button>
<BlockOptions open={blockOptionsOpen} onOpenChange={setBlockOptionsOpen} anchor={dragHandleRef.current}> <BlockOptions.Content>{/ Block options menu items /}</BlockOptions.Content> </BlockOptions> </> )} </FloatingBlockActions> ); }
export default function Editor() { const editor = useMemo( () => createYooptaEditor({ plugins: PLUGINS, marks: MARKS, }), [], );
return ( <YooptaEditor editor={editor} autoFocus placeholder="Type / to open menu" style={{ width: 750 }}> <MyToolbar /> <MyFloatingBlockActions /> <SlashCommandMenu /> </YooptaEditor> ); }
Packages
Core
| Package | Description | | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | @yoopta/editor | Core editor component and API (headless) | | @yoopta/ui | UI components (FloatingToolbar, ActionMenuList, SlashCommandMenu, FloatingBlockActions, BlockOptions, ElementOptions, SelectionBox, BlockDndContext, SortableBlock) | | @yoopta/exports | Serializers for HTML, Markdown, PlainText, Email | | @yoopta/collaboration | Real-time collaboration with Yjs CRDT (awareness, remote cursors, WebSocket provider) | | @yoopta/marks | Text formatting marks (Bold, Italic, Underline, Strike, Code, Highlight) |
Themes
| Package | Description | | ----------------------------------------------------- | --------------------------------------------------- | | @yoopta/themes-shadcn | Shadcn UI styled block elements (production) | | @yoopta/themes-material | Material Design styled block elements (in progress) |
Plugins
| Package | Description | | ----------------------------------------------------------------- | ---------------------------------------------------- | | @yoopta/paragraph | Basic text paragraph | | @yoopta/headings | H1, H2, H3 headings | | @yoopta/lists | Bulleted, numbered, and todo lists | | @yoopta/blockquote | Block quotes | | @yoopta/callout | Callout/alert boxes with themes | | @yoopta/code | Code blocks with syntax highlighting | | @yoopta/image | Images with optimization | | @yoopta/video | Video embeds (YouTube, Vimeo, etc.) | | @yoopta/embed | Generic embeds (Figma, Twitter, etc.) | | @yoopta/file | File attachments | | @yoopta/table | Tables with headers | | @yoopta/accordion | Collapsible accordion sections | | @yoopta/tabs | Tabbed content panels | | @yoopta/steps | Step-by-step instructions | | @yoopta/divider | Visual dividers | | @yoopta/link | Inline links | | @yoopta/math | Math expressions with KaTeX (MathInline + MathBlock) | | @yoopta/mention | @mentions | | @yoopta/carousel | Image carousels | | @yoopta/table-of-contents | Table of contents block |
Marks (Text Formatting)
All marks are available from @yoopta/marks:
- Bold -
Cmd/Ctrl + B - Italic -
Cmd/Ctrl + I - Underline -
Cmd/Ctrl + U - Strike -
Cmd/Ctrl + Shift + S - CodeMark -
Cmd/Ctrl + E - Highlight - Text highlighting with colors
Styling
UI components from @yoopta/ui use CSS variables for theming. For styled block elements (callout, code, image, etc.), use a theme package: @yoopta/themes-shadcn or @yoopta/themes-material (see Themes above).
Editor API
Editor Instance
The editor instance provides programmatic control over content
const editor = useMemo(
() =>
createYooptaEditor({
plugins: PLUGINS,
marks: MARKS,
// value: initialValue, // optional; or set later with setEditorValue()
}),
[],
);
// Element builder - create complex nested structures const elements = editor.y('paragraph', { children: [editor.y.text('Hello '), editor.y.text('world', { bold: true, italic: true })], });
// Inline elements (e.g., links) const linkElement = editor.y.inline('link', { props: { url: 'https://example.com', target: '_blank' }, children: [editor.y.text('Click here', { bold: true })], });
// Insert block with elements editor.insertBlock('Paragraph', { elements, at: 0, focus: true });
// Toggle block type (preserves content) editor.toggleBlock('HeadingOne', { at: editor.path.current, focus: true });
// Batch multiple operations editor.batchOperations(() => { editor.insertBlock('HeadingOne', { at: 0 }); editor.insertBlock('Paragraph', { at: 1 }); });
// Export content const html = editor.getHTML(); const markdown = editor.getMarkdown(); const plainText = editor.getPlainText();
// History editor.undo(); editor.redo();
// Content getter (or use value from onChange) const value = editor.getEditorValue();
// Events editor.on('change', ({ value, operations }) => console.log(value)); editor.on('focus', () => console.log('focused')); editor.on('blur', () => console.log('blurred'));
Blocks API
Namespace for block-level operations. Import: import { Blocks } from '@yoopta/editor'
Blocks.insertBlock(editor, { ... }) // Insert a new block
Blocks.deleteBlock(editor, { ... }) // Delete a block
Blocks.updateBlock(editor, { ... }) // Update block properties
Blocks.moveBlock(editor, { ... }) // Move block to new position
Blocks.duplicateBlock(editor, { ... }) // Duplicate a block
Blocks.toggleBlock(editor, { ... }) // Change block type
Blocks.focusBlock(editor, { ... }) // Focus a specific block
Blocks.splitBlock(editor, { ... }) // Split block at cursor
Blocks.mergeBlock(editor, { ... }) // Merge with adjacent block
Blocks.increaseBlockDepth(editor, { ... }) // Indent block
Blocks.decreaseBlockDepth(editor, { ... }) // Outdent block
Blocks.getBlock(editor, { ... }) // Get block by ID
Blocks.getBlockSlate(editor, { ... }) // Get Slate instance for block
Blocks.buildBlockData(editor, { ... }) // Build block data structure
Elements API
Namespace for element-level operations within blocks. Import: import { Elements } from '@yoopta/editor'
Elements.insertElement(editor, { ... }) // Insert element in block
Elements.updateElement(editor, { ... }) // Update element properties
Elements.deleteElement(editor, { ... }) // Delete element
Elements.getElement(editor, { ... }) // Get element by matcher
Elements.getElements(editor, { ... }) // Get multiple elements
Elements.getElementEntry(editor, { ... }) // Get element with path
Elements.getElementPath(editor, { ... }) // Get path to element
Elements.getParentElementPath(editor, { ... }) // Get parent path
Elements.getElementChildren(editor, { ... }) // Get child elements
Elements.getRootElement(editor, { ... }) // Get root element definition for a plugin
Elements.isElementEmpty(editor, { ... }) // Check if element is empty
Marks API
Namespace for text formatting operations. Import: import { Marks } from '@yoopta/editor'
// Apply marks to text at specific block positions
Marks.update(editor, {
type: 'bold',
value: true,
at: [0, 1, 2], // block indices
});
// Apply highlight with custom styles Marks.update(editor, { type: 'highlight', value: { color: 'red', backgroundColor: '#ffff00' }, at: [0], });
Creating Custom Marks
import { createYooptaMark } from '@yoopta/editor';
const CustomMark = createYooptaMark({ type: 'custom', hotkey: 'mod+shift+c', render: (props) => <span className="custom-mark">{props.children}</span>, });
API Reference
createYooptaEditor Options
const editor = createYooptaEditor({
plugins: YooptaPlugin[]; // Required. List of plugins
marks?: YooptaMark[]; // Optional. Text formatting marks
value?: YooptaContentValue; // Optional. Initial content (or use setEditorValue later)
readOnly?: boolean;
id?: string;
});
YooptaEditor Props
plugins, marks, and value are not props of <YooptaEditor>; they belong to createYooptaEditor
type YooptaEditorProps = {
editor: YooEditor; // Required. From createYooptaEditor()
onChange?: (value: YooptaContentValue, options: { operations }) => void;
onPathChange?: (path: YooptaPath) => void;
placeholder?: string;
autoFocus?: boolean;
className?: string;
style?: React.CSSProperties;
renderBlock?: (props) => React.ReactNode; // e.g. for SortableBlock
children?: React.ReactNode; // UI components: FloatingToolbar, SlashCommandMenu, etc.
};
Project Structure
packages/
โโโ core/
โ โโโ editor/ # @yoopta/editor - Main editor
โ โโโ ui/ # @yoopta/ui - UI components
โ โโโ exports/ # @yoopta/exports - Serializers
โโโ plugins/ # Block plugins
โโโ marks/ # Text formatting marks
โโโ themes/ # Theme packages
โโโ development/ # Dev playground
Development
# Install dependencies
yarn install
Build all packages
yarn build
Start dev server with watch mode
yarn dev
Run tests
yarn test
Lint
yarn lint
Roadmap
- Simplified plugin API
- Additional plugins
- More theme presets
@yoopta/collaboration (Yjs, awareness, remote cursors); see docs and the Collaboration example.
AI-powered features (generation, inline editing, plugin generator, marketplace) are available through Yoo AI โ the AI app builder built on Yoopta Editor.
Support
If you find Yoopta-Editor useful, consider supporting the project:
- Star this repository
- Sponsor on GitHub
- Buy me a coffee
- Share with others
