A modern React rich text editor library built on top of Tiptap, Tailwind CSS, Radix UI, and TypeScript.
It is designed as a reusable package rather than a demo application, and ships a polished editing shell with a formatting toolbar, slash commands, media support, paragraph styles, theme switching, translations, and a Tailwind integration helper.
- Overview
- Quick Start
- Highlights
- Prerequisites
- Installation
- Tailwind Integration
- API Reference
- Development Workflow
- Build and Distribution
- Dependency Management
- Architecture
- Project Structure
- Tech Stack and Dependencies
- Contributing
- License
@voilabs/mark is a library-first editor package that exposes a ready-to-use Editor component and several supporting exports for advanced integration scenarios.
The package focuses on:
- rich text authoring with Tiptap
- consistent, modern UI primitives using Radix UI
- Tailwind-friendly styling and host-project theming
- configurable typography and named paragraph styles
- image and video insertion workflows
- English and Turkish localization support
- distribution as a typed library with multiple entry points
The editor accepts initial content as HTML or Markdown, can auto-detect the incoming format, and emits content updates as Markdown through onChange.
import { Editor } from "@voilabs/mark";import { useState } from "react";
import { Editor } from "@voilabs/mark";
import translations from "@voilabs/mark/translations/en-US";
export function ArticleEditor() {
const [value, setValue] = useState("# Hello world");
return (
<Editor
value={value}
format="auto"
theme="auto"
translations={translations}
onChange={setValue}
/>
);
}import { Editor } from "@voilabs/mark";
async function uploadFile(file: File): Promise<{ url: string }> {
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/upload", {
method: "POST",
body: formData,
});
if (!response.ok) {
throw new Error("Upload failed");
}
return response.json();
}
export function MediaEnabledEditor() {
return (
<Editor
value="Start writing..."
onUpload={uploadFile}
onChange={(markdown) => {
console.log(markdown);
}}
/>
);
}import {
Editor,
DEFAULT_PARAGRAPH_STYLES,
type ParagraphStyle,
} from "@voilabs/mark";
const customStyles: ParagraphStyle[] = [
...DEFAULT_PARAGRAPH_STYLES,
{
id: "callout",
label: "Callout",
preview: "Callout",
type: "blockquote",
fontSize: "18px",
fontWeight: "600",
color: "#0f172a",
},
];
export function StyledEditor() {
return <Editor styles={customStyles} />;
}import { Editor } from "@voilabs/mark";
import trTR from "@voilabs/mark/translations/tr-TR";
export function TurkishEditor() {
return (
<Editor
translations={{
...trTR,
placeholder: "Yazmaya baslayin",
}}
/>
);
}- Toolbar with undo/redo, headings, lists, quote, code block, inline marks, text alignment, links, highlight colors, font family, and font size controls
- Slash command menu triggered by
/for quick insertion of text blocks, headings, lists, images, tables, quotes, and code blocks - Word count footer with zoom controls and fullscreen toggle
- Global drag handle for block insertion, duplication, reordering, anchor-link copying, color actions, and deletion
- Custom image node implementation with resize handles, crop mode, rotation, object-fit frame mode, pan controls, upload state, and alt text editing
- Video node support for uploaded video insertion
- Drag-and-drop and paste image upload handling when an
onUploadcallback is provided - Slash-command image insertion with file picker support or URL prompt fallback
- Tailwind-based UI with overridable class slots via
EditorClassNames - Built-in light and dark themes with persisted theme preference in cookie/localStorage when
theme="auto" - Named paragraph styles with a Word-like quick style gallery
- Right-click style editing with local persistence in
localStorage - Translation override support through
translations
- Typed library output generated by Vite and
vite-plugin-dts - ESM, UMD, and declaration outputs under
dist
Before using or developing this package, make sure you have:
- Node.js 20 or newer recommended
- npm 10 or newer recommended
- a React application using React 19
- TypeScript 5 if you want full type support
If you plan to use the Tailwind helper export, your host application should also be configured for Tailwind CSS.
Install the package and its peer dependencies:
npm install @voilabs/mark react react-dom typescriptIf you use the exported Tailwind plugin helper, also ensure your project has the typography plugin available:
npm install @tailwindcss/typographyTo work on the library itself:
bun installThe editor UI is built with Tailwind utility classes. In host projects, the package export @voilabs/mark/plugin can help Tailwind discover the library's compiled class usage.
Example configuration:
import editorTailwindPlugin from "@voilabs/mark/plugin";
export default {
plugins: [editorTailwindPlugin],
};What the plugin does:
- resolves the installed package path
- points Tailwind content scanning to the package
distfiles - enables
tailwindcss-animate - enables
@tailwindcss/typography
import { Editor } from "@voilabs/mark";| Prop | Type | Description |
|---|---|---|
value |
string |
Initial editor content. Supports HTML or Markdown depending on format. |
onChange |
(value: string) => void |
Receives Markdown output whenever the editor updates. |
format |
"html" | "markdown" | "auto" |
Controls how the initial value is interpreted. auto detects Markdown-like input. |
theme |
"light" | "dark" | "auto" |
Forces a theme or enables persisted user switching. |
onUpload |
(file: File) => Promise<{ url: string }> |
Upload handler used by toolbar uploads, slash-command image insertion, paste uploads, and drag/drop uploads. |
disableVideoUpload |
boolean |
Disables video upload actions in the toolbar. |
disableImageUpload |
boolean |
Disables image upload actions and image upload-driven insertion flows. |
classNames |
EditorClassNames |
Fine-grained class slot overrides for editor shell, toolbar, menus, footer, drag handle, and image UI. |
translations |
Partial<EditorTranslations> |
Partial translation override merged over built-in English strings. |
styles |
ParagraphStyle[] |
Replaces the default quick style set shown in the toolbar style gallery. |
Each paragraph style can define:
idlabelpreviewtypelevelfontFamilyfontSizefontWeightfontStylecolorletterSpacinglineHeighttextTransform
EditorClassNames exposes slot-level styling hooks for nearly every visible part of the editor.
| Slot | Purpose |
|---|---|
root |
Outer editor wrapper |
content |
Main scrollable content region |
contentScroller |
Content scrolling container |
contentInner |
Inner centered content wrapper |
editorContent |
EditorContent host container |
prose |
Tiptap prose typography layer |
| Slot | Purpose |
|---|---|
footer |
Footer root container |
footerWordCount |
Word count label area |
footerControls |
Footer controls wrapper |
footerButton |
Shared footer button styling |
footerButtonDisabled |
Disabled footer button state |
footerZoomGroup |
Zoom controls group |
footerZoomButton |
Zoom increment and decrement buttons |
footerZoomValue |
Zoom percentage button |
footerFullscreenButton |
Fullscreen toggle button |
| Slot | Purpose |
|---|---|
toolbar |
Toolbar root container |
toolbarInner |
Toolbar scroll container |
toolbarGroup |
Toolbar button group wrapper |
toolbarButton |
Shared toolbar button styling |
toolbarButtonActive |
Active toolbar button state |
toolbarButtonDisabled |
Disabled toolbar button state |
toolbarButtonIcon |
Toolbar button icon wrapper |
toolbarTrigger |
Dropdown trigger buttons |
toolbarTriggerActive |
Active dropdown trigger state |
toolbarTriggerIcon |
Dropdown trigger icon wrapper |
toolbarTriggerLabel |
Dropdown trigger label text |
toolbarTriggerChevron |
Dropdown trigger chevron |
toolbarSeparator |
Separator between toolbar groups |
toolbarFileInput |
Hidden media file input |
toolbarUploadButton |
Upload button |
toolbarUploadIcon |
Upload button icon |
toolbarUploadLabel |
Upload button label |
| Slot | Purpose |
|---|---|
styleBar |
Quick style gallery container |
styleBarItem |
Individual style card |
styleBarItemActive |
Active style card |
| Slot | Purpose |
|---|---|
styleEditPopover |
Right-click style edit popover |
styleEditInput |
Inputs inside the style editor |
styleEditSaveButton |
Save button inside the style editor |
styleEditResetButton |
Reset button inside the style editor |
| Slot | Purpose |
|---|---|
toolbarFontFamilyTrigger |
Font family combobox trigger |
toolbarFontFamilyPopover |
Font family popover body |
toolbarFontFamilySearch |
Font family search input |
toolbarFontFamilyList |
Font family list container |
toolbarFontFamilyItem |
Individual font option |
toolbarFontFamilyItemActive |
Active font option |
| Slot | Purpose |
|---|---|
toolbarFontSizeControl |
Font size control wrapper |
toolbarFontSizeControlActive |
Active font size control state |
toolbarFontSizeIcon |
Font size icon wrapper |
toolbarFontSizeInput |
Font size text input |
toolbarFontSizeUnit |
Font size unit label |
toolbarFontSizeStepButton |
Increase and decrease buttons |
toolbarFontSizeResetButton |
Reset font size button |
| Slot | Purpose |
|---|---|
toolbarLinkButton |
Link toolbar button |
toolbarLinkPopoverBody |
Link popover content wrapper |
toolbarLinkPopoverLabel |
Link popover heading |
toolbarLinkInput |
Link URL input |
toolbarLinkActions |
Link action buttons wrapper |
toolbarLinkApplyButton |
Apply link button |
toolbarLinkUnlinkButton |
Remove link button |
| Slot | Purpose |
|---|---|
toolbarHighlightButton |
Highlight toolbar button |
toolbarHighlightIndicator |
Color indicator under the button |
toolbarHighlightPopoverBody |
Highlight palette popover body |
toolbarHighlightPopoverLabel |
Highlight palette label |
toolbarHighlightGrid |
Highlight swatch grid |
toolbarHighlightSwatch |
Individual highlight swatch |
toolbarHighlightSwatchActive |
Active highlight swatch |
toolbarHighlightClearButton |
Clear highlight button |
| Slot | Purpose |
|---|---|
tooltipContent |
Tooltip content container |
tooltipArrow |
Tooltip arrow |
popoverContent |
Shared popover content container |
dropdownContent |
Main dropdown menu content |
dropdownSubContent |
Nested dropdown menu content |
dropdownSubTrigger |
Nested dropdown trigger item |
dropdownSubTriggerChevron |
Nested dropdown chevron |
dropdownItem |
Standard dropdown item |
dropdownItemActive |
Active dropdown item state |
dropdownItemIcon |
Dropdown item icon |
dropdownItemLabel |
Dropdown item label |
dropdownItemCheck |
Dropdown selected-state checkmark |
dropdownCheckboxItem |
Checkbox dropdown item |
dropdownRadioItem |
Radio dropdown item |
dropdownIndicator |
Dropdown indicator wrapper |
dropdownLabel |
Dropdown section label |
dropdownSeparator |
Dropdown separator |
dropdownShortcut |
Dropdown shortcut hint text |
| Slot | Purpose |
|---|---|
slashCommandMenu |
Slash command menu container |
slashCommandItem |
Slash command item |
slashCommandItemActive |
Active slash command item |
slashCommandIcon |
Slash command icon wrapper |
slashCommandTitle |
Slash command item label |
slashCommandEmpty |
Empty state wrapper |
slashCommandEmptyIcon |
Empty state icon |
| Slot | Purpose |
|---|---|
dragHandleRoot |
Floating drag handle root |
dragHandleAddButton |
Add-block button |
dragHandleMenuButton |
Grip/menu button |
dragHandleMenuContent |
Drag handle dropdown content |
dragHandleSubContent |
Nested drag handle dropdown content |
dragHandleColorSectionLabel |
Text/background color section label |
dragHandleColorTextPreview |
Text color preview glyph |
dragHandleColorSwatch |
Background color swatch |
dragHandleCheckIcon |
Selected-state check icon |
dragHandleDeleteItem |
Delete item styling |
| Slot | Purpose |
|---|---|
imageNodeWrapper |
Outer image node wrapper |
imageContainer |
Image selection frame |
imageCropArea |
Crop area wrapper |
imageCropToolbar |
Crop mode toolbar |
imageCropButton |
Shared crop toolbar button |
imageCropCancelButton |
Cancel crop button |
imageCropApplyButton |
Apply crop button |
imageFrame |
Image frame container |
imageElement |
Rendered <img> element |
imageLoadingOverlay |
Upload/loading overlay |
imageLoadingBox |
Loader badge container |
imageLoadingIcon |
Loader icon |
imagePanHintOverlay |
Pan hint overlay |
imagePanHint |
Pan hint text |
imageToolbar |
Image hover toolbar |
imageToolbarButton |
Shared image toolbar button |
imageToolbarSeparator |
Divider inside image toolbar |
imageDeleteButton |
Image delete button |
imageAltButton |
Alt text button |
imageAltPopover |
Alt text popover body |
imageAltLabel |
Alt text popover label |
imageAltInput |
Alt text input |
imageAltActions |
Alt text action row |
imageAltApplyButton |
Apply alt text button |
imageAltClearButton |
Clear alt text button |
imageResizeHandle |
Shared resize handle styling |
imageResizeHandleRight |
Right resize handle |
imageResizeHandleBottom |
Bottom resize handle |
imageResizeHandleBottomRight |
Bottom-right resize handle |
imageModeBadge |
Mode hint wrapper |
imageModeText |
Mode hint label |
This API is intended for design system integration without forcing a fork of the editor internals.
The library ships with:
en-UStr-TR
You can either import a full locale object or pass a partial object to override selected labels.
This repository is configured as a library project, not as a local preview app.
bun installbun run devThis runs:
vite build --watchUse this mode when you want incremental rebuilds of the library output while developing the package.
bun run buildThis runs:
vite build && tsc -p tsconfig.build.jsonThe command generates JavaScript bundles and declaration files into dist/.
The Vite configuration builds multiple library entry points:
src/index.tssrc/plugin.tssrc/translations/en-US.tssrc/translations/tr-TR.ts
The package metadata exposes:
- main editor entry
- Tailwind plugin entry
- locale entries
Distributed assets include:
- ESM bundles
- UMD bundles
.d.tstype declarations
The package is configured to publish:
distREADME.mdLICENSEpackage.json
The package declares the following peers:
reactreact-domtypescript
The runtime layer relies heavily on:
- Tiptap extensions and editor packages
- Radix UI primitives
- Tailwind merge helpers
- icon and motion libraries
- media and popup utilities such as
react-easy-cropandtippy.js
Development and build-time tooling includes:
- Babel and Rollup-related packages
- Vite and React integration tooling
- TypeScript
- declaration generation plugins
The repository currently includes both package-lock.json and bun.lock, which suggests npm and Bun have both been used during development. This README uses Bun for repository workflows and npm for consumer installation examples.
The codebase is organized as a reusable component library with a single primary editor shell and a set of supporting modules.
src/components/Editor.tsxcreates the Tiptap editor instance and composes the full editor shell.src/components/editor/Toolbar.tsxprovides the main authoring controls and style management UI.src/components/editor/GlobalDragHandleMenu.tsxadds block-level actions beside the editing surface.src/components/editor/extensions/*defines custom Tiptap extensions for images, video, font size, font family, and extra text-style attributes.src/components/editor/slash-command/*implements the/suggestion system.src/components/editor/EditorContext.tsxshares theme, translations, class names, and styles with nested UI primitives.src/plugin.tsexports a Tailwind helper plugin intended to help host apps include the editor's utility classes.
The editor follows this runtime flow:
- Initial
valueis read as HTML, Markdown, or auto-detected content. - Tiptap extensions are registered, including the custom image/video and typography extensions.
- User actions update the internal ProseMirror document.
onChangeis called witheditor.getMarkdown().
Important behavior:
- Input supports HTML, Markdown, or auto-detection via the
formatprop. - Output is Markdown on every update, regardless of the original input format.
- Auto theme mode persists the chosen theme using the
VOILABS_REACT_EDITOR_THEMEkey. - Style overrides are stored locally using the
voilabs-editor-style-overrideskey.
onUploadfor custom media upload pipelinesstylesfor custom paragraph style setsclassNamesfor granular visual overridestranslationsfor partial locale overrides- exported Tiptap extensions such as
AdvancedImage,FontSize, andVideo
editor/
|- src/
| |- components/
| | |- Editor.tsx
| | |- editor/
| | | |- EditorContext.tsx
| | | |- Toolbar.tsx
| | | |- StyleBar.tsx
| | | |- StyleEditPopover.tsx
| | | |- GlobalDragHandleMenu.tsx
| | | |- classNames.ts
| | | |- paragraphStyles.ts
| | | |- translations.ts
| | | |- useStyleStorage.ts
| | | |- extensions/
| | | | |- AdvancedImage.tsx
| | | | |- ImageNodeView.tsx
| | | | |- FontFamily.ts
| | | | |- FontSize.ts
| | | | |- TextStyleAttributes.ts
| | | | `- Video.tsx
| | | `- slash-command/
| | | |- commands.ts
| | | |- commands-list.tsx
| | | `- suggestion.tsx
| | `- ui/
| | |- Dropdown.tsx
| | |- DropdownMenu.tsx
| | |- Popover.tsx
| | `- Tooltip.tsx
| |- lib/
| | `- utils.ts
| |- translations/
| | |- en-US.ts
| | `- tr-TR.ts
| |- index.ts
| `- plugin.ts
|- package.json
|- vite.config.ts
|- tsconfig.json
`- tsconfig.build.json
- React 19
- Tiptap 3 ecosystem
- ProseMirror through
@tiptap/pm - Tailwind CSS 4
- Radix UI popover, dropdown menu, and tooltip primitives
lucide-reactfor editor iconsreact-easy-cropfor image croppingtippy.jsfor slash command popup positioning
- Vite library mode
- TypeScript 5
vite-plugin-dtsfor declaration generation@vitejs/plugin-react@tailwindcss/vite
- The editor uses a custom
imagenode implementation instead of the default Tiptap image extension. - The
Videoextension renders native HTML<video>nodes with controls enabled. - The toolbar uses context-aware command selection so inline formatting can apply to the current word even when no explicit range is selected.
- The drag handle menu performs block-level actions by resolving the top-level node under the cursor.
- The Tailwind helper plugin tries to resolve the installed package path dynamically and scans the built
distfiles for content extraction.
Contributions should preserve the package's library-first design, typed public API, and editor usability.
Recommended contribution process:
- Fork or branch from the latest mainline source.
- Install dependencies with
npm install. - Make focused changes with clear intent.
- Verify the package builds successfully with
npm run build. - Update documentation when behavior or public API changes.
- Open a pull request with a concise explanation of the change.
Suggested contribution guidelines:
- keep public API changes intentional and documented
- avoid breaking translation keys without updating shipped locales
- preserve Markdown output behavior unless a deliberate API change is introduced
- keep Tailwind class slot compatibility in mind when changing UI structure
- prefer incremental, reviewable pull requests
At the moment, the repository does not define dedicated lint or test scripts in package.json, so build validation is the primary verification step.
This project is licensed under the MIT License.
See LICENSE for the full text.