https://github.com/markrahimi/mandoo-editor
A modern WYSIWYG editor for React & Next.js
https://github.com/markrahimi/mandoo-editor
block-editor contenteditable dark-mode nextjs react-editor rich-text typescript wysiwyg
Last synced: 6 days ago
JSON representation
A modern WYSIWYG editor for React & Next.js
- Host: GitHub
- URL: https://github.com/markrahimi/mandoo-editor
- Owner: markrahimi
- License: mit
- Created: 2026-06-04T10:17:29.000Z (14 days ago)
- Default Branch: main
- Last Pushed: 2026-06-08T21:39:32.000Z (10 days ago)
- Last Synced: 2026-06-08T23:21:31.877Z (10 days ago)
- Topics: block-editor, contenteditable, dark-mode, nextjs, react-editor, rich-text, typescript, wysiwyg
- Language: TypeScript
- Homepage: https://mandooeditor.markrahimi.com
- Size: 91.8 KB
- Stars: 9
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# MandooEditor
A modern, lightweight WYSIWYG rich text editor for React & Next.js
Feature-flagged · Fully typed · Zero runtime dependencies · <400KB
---
## Features
| Feature | Description |
| -------------------- | --------------------------------------------------------------------------------- |
| **Visual editor** | `contenteditable` WYSIWYG — no iframe, no Flash |
| **Block mode** | Drag-and-drop block editor with per-block type selector |
| **Text / HTML mode** | Raw HTML editing with syntax highlighting |
| **HTML & Markdown** | `onChange` fires in whichever format you choose |
| **Feature flags** | Enable or disable every toolbar button individually |
| **Media upload** | Wire any S3 / MinIO / custom API — just pass callbacks |
| **Plugins** | Link checker, tables, image editor, history, YouTube embed, subscript/superscript |
| **Fully typed** | End-to-end TypeScript with imperative ref handle |
| **Zero deps** | No runtime dependencies beyond React |
---
## Installation
```bash
npm install mandoo-editor
# or
yarn add mandoo-editor
# or
pnpm add mandoo-editor
```
> **⚠️ Required — add this import wherever you use the editor:**
```tsx
import 'mandoo-editor/styles';
```
Add it in your layout, page, or component — wherever `MandooEditor` is rendered. Without it the editor has no styling.
---
## Quick Start
```tsx
"use client";
import MandooEditor from "mandoo-editor";
export default function MyPage() {
return (
console.log(html)}
height={400}
/>
);
}
```
---
## API Reference
### Props
| Prop | Type | Default | Description |
| -------------- | ------------------------- | --------------------------- | ---------------------------------------- |
| `value` | `string` | — | Controlled HTML value |
| `defaultValue` | `string` | `''` | Uncontrolled initial HTML value |
| `onChange` | `(value: string) => void` | — | Fires on every change with current value |
| `outputFormat` | `'html' \| 'markdown'` | `'html'` | Format for `onChange` and `getValue()` |
| `placeholder` | `string` | `'Start writing…'` | Placeholder shown when empty |
| `tabs` | `TabId[]` | `['visual','text','block']` | Which tabs to display |
| `defaultTab` | `TabId` | `'visual'` | Initially active tab |
| `features` | `Features` | all enabled | Granular toolbar feature flags |
| `plugins` | `Plugins` | none | Optional plugin flags |
| `media` | `MediaConfig` | — | File upload / library config |
| `theme` | `'classic' \| 'modern'` | `'classic'` | Visual theme |
| `colorScheme` | `'light' \| 'dark'` | `'light'` | Color scheme |
| `defaultDir` | `'rtl' \| 'ltr'` | — | Default text direction for the editor |
| `height` | `number` | `400` | Min height of editor content area (px) |
| `className` | `string` | — | Extra CSS class on root element |
| `apiToken` | `string` | — | Token for future paid pro features |
### Imperative Handle (ref)
```tsx
import { useRef } from "react";
import MandooEditor, { MandooEditorHandle } from "mandoo-editor";
const ref = useRef(null);
// Methods:
ref.current?.getValue(); // → string (respects outputFormat)
ref.current?.getHTML(); // → raw HTML string
ref.current?.getMarkdown(); // → Markdown string
ref.current?.setValue(html); // set content programmatically
ref.current?.focus(); // focus the editor
ref.current?.clear(); // clear content
```
---
## Form Integration
MandooEditor outputs HTML or Markdown. There are two ways to use it in a form:
### Option 1 — `name` prop (native forms, FormData, Server Actions)
Add a `name` prop and a hidden `` is automatically rendered. Works with any form library or native HTML form submission.
```tsx
// Native HTML form
Save
// Next.js Server Action
async function save(formData: FormData) {
'use server';
const content = formData.get('content'); // ← HTML or Markdown
}
Save
```
### Option 2 — `onChange` (controlled state, react-hook-form, Zustand…)
```tsx
// useState
const [content, setContent] = useState('');
// react-hook-form
const { setValue } = useForm();
setValue('content', v)} outputFormat="markdown" />
// Zustand / Redux
dispatch(setContent(v))} />
```
---
## Feature Flags
Disable any toolbar button by setting its flag to `false`:
```tsx
```
Full list of flags: `bold`, `italic`, `strikethrough`, `lists`, `blockquote`, `hr`, `align`, `link`, `code`, `direction`, `fullscreen`, `kitchenSink`, `underline`, `justify`, `foreColor`, `pasteAsText`, `removeFormat`, `charMap`, `indent`, `undo`, `help`, `media`, `subscript`, `superscript`
---
## Code Formatting
The `code` feature adds a **Code** button to the toolbar. It has two modes depending on the selection:
| Context | Result |
|---|---|
| Text selected | Wraps in inline `` |
| No selection / cursor in a block | Converts block to `` (code block) |
| Click again inside `` or `` | Removes the formatting |
Both `` and `` share the same visual style — monospace font, subtle background from `--me-textarea-bg`, and a matching border — so inline and block code look like a family.
```tsx
// Disable the code button
```
---
## RTL / LTR Direction
The `direction` feature adds **RTL** and **LTR** toggle buttons to the toolbar. Direction is applied per block — each paragraph or heading can have its own direction independently.
| Action | Result |
|---|---|
| Click RTL | Sets `dir="rtl" style="direction:rtl; text-align:right"` on the current block |
| Click LTR | Sets `dir="ltr" style="direction:ltr; text-align:left"` on the current block |
| Click the active button again | Removes direction from the block (toggle off) |
One button is always highlighted: the active block's direction, or `defaultDir` if set, or LTR by default.
```tsx
// RTL-first editor (e.g. Persian / Arabic content)
// Disable the direction buttons entirely
```
Direction is stored inline in the HTML output so it renders correctly anywhere, without requiring the editor's stylesheet:
```html
متن فارسی
English paragraph
```
---
## Plugins
```tsx
```
---
## Media Upload
Wire any storage backend — S3, MinIO, Cloudflare R2, or your own API:
```tsx
```
### MinIO / S3 Server Route (Next.js App Router)
```ts
// app/api/upload/route.ts
import { Client } from "minio"; // npm install minio
import { NextRequest, NextResponse } from "next/server";
const minio = new Client({
endPoint: process.env.MINIO_ENDPOINT!,
useSSL: true,
accessKey: process.env.MINIO_ACCESS_KEY!,
secretKey: process.env.MINIO_SECRET_KEY!,
});
export async function POST(req: NextRequest) {
const form = await req.formData();
const file = form.get("file") as File;
const buf = Buffer.from(await file.arrayBuffer());
const name = `uploads/${Date.now()}-${file.name}`;
await minio.putObject(process.env.MINIO_BUCKET!, name, buf, buf.length, {
"Content-Type": file.type,
});
const url = await minio.presignedGetObject(
process.env.MINIO_BUCKET!,
name,
604800
);
return NextResponse.json({ url, name: file.name });
}
```
---
## Output Formats
```tsx
// HTML output (default)
{
// "
Hello world
"
console.log(html);
}}
/>
// Markdown output
{
// "Hello **world**"
console.log(md);
}}
/>
```
---
## Tabs Configuration
```tsx
// Only show Visual and Text tabs (no Block editor)
// Start on Block tab
// Only Block editor
```
---
## Theming
### Built-in themes
MandooEditor ships with two visual themes and two color schemes — mix and match any combination:
```tsx
// Classic theme (default) — dense toolbar, serif content font
// Classic dark
// Modern theme — minimal toolbar, rounded corners, sans-serif content font
// Modern dark
```
### CSS customization
Every color, radius, and font in MandooEditor is driven by CSS custom properties set on the root container. You can override any of them from your own CSS:
```css
/* globals.css or any stylesheet loaded after mandoo-editor/styles */
.mandoo-editor-container {
--me-accent: #e11d48; /* links, active buttons, focus rings */
--me-container-radius: 4px; /* outer border radius */
--me-content-font: 'Vazirmatn', sans-serif; /* content area font */
}
```
You can also scope overrides to a specific theme or color scheme:
```css
/* Only affect the modern theme */
.mandoo-editor-container[data-mandoo-theme="modern"] {
--me-accent: #7c3aed;
--me-toolbar-bg: #fafafa;
}
/* Only affect dark mode */
.mandoo-editor-container[data-mandoo-scheme="dark"] {
--me-bg: #18181b;
--me-border: #27272a;
}
```
### Full list of CSS variables
| Variable | Controls | Classic light default |
|---|---|---|
| `--me-bg` | Editor & modal background | `#ffffff` |
| `--me-border` | All borders | `#dddddd` |
| `--me-color` | UI text | `#444444` |
| `--me-color-strong` | Headings, modal titles | `#23282d` |
| `--me-toolbar-bg` | Toolbar row background | `#ebebeb` |
| `--me-tools-bg` | Media/tabs bar background | `#f1f1f1` |
| `--me-btn-hover` | Button hover background | `#d5d5d5` |
| `--me-btn-active` | Active/pressed button background | `#b8b8b8` |
| `--me-btn-bg` | Inactive button background | `#f3f5f6` |
| `--me-statusbar-bg` | Status bar background | `#ebebeb` |
| `--me-textarea-bg` | HTML textarea background | `#f9f9f9` |
| `--me-modal-bg` | Modal body background | `#ffffff` |
| `--me-modal-header-bg` | Modal header background | `#f1f1f1` |
| `--me-accent` | Links, focus rings, active state | `#0073aa` |
| `--me-muted` | Placeholder, counts, labels | `#888888` |
| `--me-sep` | Toolbar separators | `#cccccc` |
| `--me-content-color` | Content area text | `#333333` |
| `--me-content-font` | Content area font family | `Georgia, serif` |
| `--me-btn-size` | Toolbar button width & height | `26px` |
| `--me-btn-radius` | Toolbar button border radius | `2px` |
| `--me-container-radius` | Outer container border radius | `0px` |
---
## Pro Features (Coming Soon)
The following features require an `apiToken` and will be available in a future paid tier:
- **Export to PDF** — one-click export via Mandoo cloud API
- **Word Import/Export** — read and write `.docx` files
- **AI Assistant** — chat with AI to rewrite, summarise, or extend content
```tsx
// Reserve your token now — setting it has no effect until pro plugins are released
```
---
## Token Infrastructure
```ts
import { mandooFetch, validateToken } from "mandoo-editor";
// Validate a token format
const valid = validateToken("mk_live_abc123...");
// Call Mandoo API (for pro features)
const result = await mandooFetch(
"/export/pdf",
{ method: "POST", body: fd },
{
token: "mk_live_...",
baseUrl: "https://api.mandooeditor.com/v1", // optional override
}
);
```
---
## TypeScript Types
```ts
import type {
MandooEditorProps,
MandooEditorHandle,
Features,
Plugins,
MediaConfig,
MediaFile,
MediaUploadResult,
TabId,
OutputFormat,
TokenConfig,
} from "mandoo-editor";
```
---
## Links
| | |
| -------------- | ------------------------------------------------------------------------------------------------ |
| 🌍 **Website** | [mandooeditor.markrahimi.com](https://mandooeditor.markrahimi.com) |
| 📦 **npm** | [npmjs.com/package/mandoo-editor](https://www.npmjs.com/package/mandoo-editor) |
| 🐙 **GitHub** | [github.com/markrahimi/mandoo-editor](https://github.com/markrahimi/mandoo-editor) |
| 🐛 **Issues** | [github.com/markrahimi/mandoo-editor/issues](https://github.com/markrahimi/mandoo-editor/issues) |
| ☕ **Support** | [ko-fi.com/markrahimi](https://ko-fi.com/E1E11W0EQP) |
| 👤 **Author** | [markrahimi.com](https://markrahimi.com) |
---
## License
MIT © [Mohammad Ali Rahimi](https://markrahimi.com)