Skip to content

voilabs/react-mark

Repository files navigation

@voilabs/mark

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.

Table of Contents

Overview

@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.

Quick Start

import { Editor } from "@voilabs/mark";

Basic Usage

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}
        />
    );
}

With Upload Support

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);
            }}
        />
    );
}

With Custom Paragraph Styles

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} />;
}

With Translation Overrides

import { Editor } from "@voilabs/mark";
import trTR from "@voilabs/mark/translations/tr-TR";

export function TurkishEditor() {
    return (
        <Editor
            translations={{
                ...trTR,
                placeholder: "Yazmaya baslayin",
            }}
        />
    );
}

Highlights

Editing Experience

  • 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

Media Support

  • 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 onUpload callback is provided
  • Slash-command image insertion with file picker support or URL prompt fallback

Styling and Customization

  • 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

Packaging

  • Typed library output generated by Vite and vite-plugin-dts
  • ESM, UMD, and declaration outputs under dist

Prerequisites

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.

Installation

Library Consumers

Install the package and its peer dependencies:

npm install @voilabs/mark react react-dom typescript

Tailwind Plugin Consumers

If you use the exported Tailwind plugin helper, also ensure your project has the typography plugin available:

npm install @tailwindcss/typography

Repository Setup

To work on the library itself:

bun install

Tailwind Integration

The 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 dist files
  • enables tailwindcss-animate
  • enables @tailwindcss/typography

API Reference

Editor

import { Editor } from "@voilabs/mark";

EditorProps

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.

ParagraphStyle

Each paragraph style can define:

  • id
  • label
  • preview
  • type
  • level
  • fontFamily
  • fontSize
  • fontWeight
  • fontStyle
  • color
  • letterSpacing
  • lineHeight
  • textTransform

EditorClassNames

EditorClassNames exposes slot-level styling hooks for nearly every visible part of the editor.

Shell and Content Slots

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

Footer Slots

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

Toolbar Slots

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

Style Bar Slots

Slot Purpose
styleBar Quick style gallery container
styleBarItem Individual style card
styleBarItemActive Active style card

Style Editor Slots

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

Font Family Slots

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

Font Size Slots

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

Link Popover Slots

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

Highlight Slots

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

Shared Overlay Slots

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

Slash Command Slots

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

Drag Handle Slots

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

Image Node Slots

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.

Translations

The library ships with:

  • en-US
  • tr-TR

You can either import a full locale object or pass a partial object to override selected labels.

Development Workflow

This repository is configured as a library project, not as a local preview app.

Install Dependencies

bun install

Development Build Watch

bun run dev

This runs:

vite build --watch

Use this mode when you want incremental rebuilds of the library output while developing the package.

Production Build

bun run build

This runs:

vite build && tsc -p tsconfig.build.json

The command generates JavaScript bundles and declaration files into dist/.

Build and Distribution

The Vite configuration builds multiple library entry points:

  • src/index.ts
  • src/plugin.ts
  • src/translations/en-US.ts
  • src/translations/tr-TR.ts

The package metadata exposes:

  • main editor entry
  • Tailwind plugin entry
  • locale entries

Distributed assets include:

  • ESM bundles
  • UMD bundles
  • .d.ts type declarations

The package is configured to publish:

  • dist
  • README.md
  • LICENSE
  • package.json

Dependency Management

Peer Dependencies

The package declares the following peers:

  • react
  • react-dom
  • typescript

Runtime Dependencies

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-crop and tippy.js

Development Dependencies

Development and build-time tooling includes:

  • Babel and Rollup-related packages
  • Vite and React integration tooling
  • TypeScript
  • declaration generation plugins

Lockfiles

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.

Architecture

The codebase is organized as a reusable component library with a single primary editor shell and a set of supporting modules.

High-Level Design

  1. src/components/Editor.tsx creates the Tiptap editor instance and composes the full editor shell.
  2. src/components/editor/Toolbar.tsx provides the main authoring controls and style management UI.
  3. src/components/editor/GlobalDragHandleMenu.tsx adds block-level actions beside the editing surface.
  4. src/components/editor/extensions/* defines custom Tiptap extensions for images, video, font size, font family, and extra text-style attributes.
  5. src/components/editor/slash-command/* implements the / suggestion system.
  6. src/components/editor/EditorContext.tsx shares theme, translations, class names, and styles with nested UI primitives.
  7. src/plugin.ts exports a Tailwind helper plugin intended to help host apps include the editor's utility classes.

Content Flow

The editor follows this runtime flow:

  1. Initial value is read as HTML, Markdown, or auto-detected content.
  2. Tiptap extensions are registered, including the custom image/video and typography extensions.
  3. User actions update the internal ProseMirror document.
  4. onChange is called with editor.getMarkdown().

Important behavior:

  • Input supports HTML, Markdown, or auto-detection via the format prop.
  • Output is Markdown on every update, regardless of the original input format.
  • Auto theme mode persists the chosen theme using the VOILABS_REACT_EDITOR_THEME key.
  • Style overrides are stored locally using the voilabs-editor-style-overrides key.

Extensibility Points

  • onUpload for custom media upload pipelines
  • styles for custom paragraph style sets
  • classNames for granular visual overrides
  • translations for partial locale overrides
  • exported Tiptap extensions such as AdvancedImage, FontSize, and Video

Project Structure

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

Tech Stack and Dependencies

Core Runtime

  • React 19
  • Tiptap 3 ecosystem
  • ProseMirror through @tiptap/pm
  • Tailwind CSS 4
  • Radix UI popover, dropdown menu, and tooltip primitives
  • lucide-react for editor icons
  • react-easy-crop for image cropping
  • tippy.js for slash command popup positioning

Build Tooling

  • Vite library mode
  • TypeScript 5
  • vite-plugin-dts for declaration generation
  • @vitejs/plugin-react
  • @tailwindcss/vite

Noteworthy Implementation Details

  • The editor uses a custom image node implementation instead of the default Tiptap image extension.
  • The Video extension 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 dist files for content extraction.

Contributing

Contributions should preserve the package's library-first design, typed public API, and editor usability.

Recommended contribution process:

  1. Fork or branch from the latest mainline source.
  2. Install dependencies with npm install.
  3. Make focused changes with clear intent.
  4. Verify the package builds successfully with npm run build.
  5. Update documentation when behavior or public API changes.
  6. 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.

License

This project is licensed under the MIT License.

See LICENSE for the full text.

About

A modern React rich-text editor library by VoiLabs.

Resources

License

Stars

Watchers

Forks

Contributors