npm.io
1.3.2 • Published yesterday

rj-editor

Licence
MIT
Version
1.3.2
Deps
17
Size
948 kB
Vulns
0
Weekly
294

RJ Editor

rj-editor is a React text editor package for building article editors, admin panels, CMS forms, learning platforms, notes, documentation tools, and other content-heavy interfaces.

It provides a ready-to-use RJTextEditor component with a tabbed toolbar, text formatting, tables, links, images, YouTube embeds, fullscreen mode, i18n, theming, and Ant Design Form support.

Live demo

Features

  • Text formatting: bold, italic, underline, strikethrough, subscript, superscript.
  • Style controls: font size, font family, text color, background color, clear formatting.
  • Paragraph tools: alignment, ordered list, unordered list, indent, outdent, line and paragraph spacing.
  • Insert tools: table, link, image, YouTube video, and editable code blocks.
  • Code blocks: editable code blocks, custom headers, language selection, syntax highlighting, lightweight completions, and persistent copy controls in both the editor and rendered HTML.
  • Table editing: add/remove rows and columns, merge/split cells, header row/column, cell background, vertical alignment, delete table.
  • Image editing: upload, drag-and-drop, paste from clipboard, resize, align, alt text, caption, link, border, border radius, shadow, object fit, wrapping, rotate, replace, delete.
  • Browser fullscreen mode.
  • Optional footer status bar with words, characters, selection, language, mode, and zoom indicators.
  • Ant Design Form.Item integration.
  • Built-in uz, en, and ru translations.
  • Custom locale and partial translation override support.
  • Theme customization through CSS custom properties.

Installation

npm install rj-editor

react and react-dom are peer dependencies. They must already exist in your application:

npm install react react-dom

Quick Start

import { RJTextEditor } from 'rj-editor'

export function App() {
  return (
    <RJTextEditor
      autofocus
      locale="en"
      placeholder="Write your content..."
      onChange={(html) => {
        console.log(html)
      }}
    />
  )
}

You do not need to import a separate stylesheet. The package styles are included when RJTextEditor is imported.

Saving Content

onChange returns the editor content as an HTML string in the first argument. This is the value you usually store in your database.

<RJTextEditor
  onChange={(html) => {
    saveDraft(html)
  }}
/>

The callback also exposes a JSON string and the current editor state for advanced use cases:

type OnChange = (
  html: string,
  json: string,
  editorState: unknown,
) => void

Rendering Saved HTML

Render the saved HTML string anywhere in your application:

export function Article({ html }: { html: string }) {
  return <article dangerouslySetInnerHTML={{ __html: html }} />
}

Exported content includes inline styles for editor-only elements such as code blocks, inline code, tables, images, YouTube embeds, and equations. This lets saved HTML render correctly in pages that do not mount the editor stylesheet.

Exported code blocks keep their header and copy button. The exported copy button includes a small inline fallback, but many applications sanitize HTML or block inline handlers with CSP. For reliable code-block copy controls on pages that only render saved HTML, import the lightweight rendered HTML helper in that client application:

import 'rj-editor/rendered-html'

export function Article({ html }: { html: string }) {
  return <article dangerouslySetInnerHTML={{ __html: html }} />
}

This helper does not mount the editor. It only registers the delegated copy handler for exported code blocks. If you sanitize saved HTML before rendering it, keep the exported style, data-rj-editor-code-block, data-rj-editor-code-copy, and data-rj-editor-code-content attributes if you want the built-in rendered styles and copy controls to remain active. The onclick attribute is optional when the helper is imported.

Only render trusted or sanitized HTML. Sanitize untrusted user-provided HTML before passing it to dangerouslySetInnerHTML.

Initial Content

Use defaultValue when you want to load saved HTML once when the editor mounts:

<RJTextEditor defaultValue="<p>Hello world</p>" />

Use value when your application controls the editor content from React state:

import { useState } from 'react'
import { RJTextEditor } from 'rj-editor'

export function ControlledEditor() {
  const [content, setContent] = useState('<p>Initial content</p>')

  return (
    <RJTextEditor
      value={content}
      onChange={(html) => setContent(html)}
    />
  )
}

Ant Design Form

RJTextEditor works inside Ant Design Form.Item. The form receives the HTML string as the field value.

import { Button, Form } from 'antd'
import { RJTextEditor } from 'rj-editor'

export function ArticleForm() {
  return (
    <Form
      layout="vertical"
      onFinish={(values) => {
        console.log(values.content)
      }}
    >
      <Form.Item
        label="Content"
        name="content"
        rules={[{ required: true, message: 'Content is required' }]}
      >
        <RJTextEditor placeholder="Write your article..." />
      </Form.Item>

      <Button htmlType="submit" type="primary">
        Save
      </Button>
    </Form>
  )
}

When validation fails, the editor border follows the Ant Design error state.

Props

Prop Type Default Description
autofocus boolean false Focuses the editor after mount.
className string undefined Adds a custom class to the editor root.
defaultValue string undefined Initial HTML content. Applied once on mount.
footer (stats) => React.ReactNode undefined Custom footer renderer. Works when showFooter is enabled.
footerItems RJEditorFooterItem[] built-in status items Controls which default footer items are shown.
footerLanguageLabel string derived from locale Language label shown in the footer.
footerZoom number 100 Zoom value shown in the footer. Display-only for now.
id string undefined Adds an id to the editor root.
locale 'uz' | 'en' | 'ru' | string 'en' Active editor language.
locales Record<string, DeepPartial<RJEditorTranslations>> undefined Adds custom locales.
namespace string 'RJEditor' Unique editor namespace. Useful when multiple editor instances exist on one page.
onBlur React.FocusEventHandler<HTMLDivElement> undefined Called when the editor root loses focus.
onChange (html, json, editorState) => void undefined Called whenever editor content changes.
onFocus React.FocusEventHandler<HTMLDivElement> undefined Called when the editor root receives focus.
onUpload (file: File) => Promise<string> base64 fallback Uploads image files and returns the image URL to store in editor content.
placeholder string Locale-based text Placeholder shown when the editor is empty.
showFooter boolean false Shows the optional editor footer status bar.
translations DeepPartial<RJEditorTranslations> undefined Overrides translations for the active locale.
theme 'light' | 'dark' | 'auto' 'auto' Controls the editor color theme. Auto follows the document color scheme and system preference.
value string undefined Controlled HTML content.

Toolbar

The toolbar is organized by tabs:

  • Home: text formatting, styles, lists, alignment, indentation, and spacing.
  • Insert: table, link, image, YouTube video, and code block insertion.
  • Image: shown when an image is selected.
  • Table: shown when the cursor is inside a table.

Contextual tabs are only visible when they are useful, keeping the editor interface focused.

Enable the optional footer when you want Word-like status information under the editor:

<RJTextEditor showFooter />

The default footer shows word count, character count, selected text count, language, editor mode, and zoom. You can choose a smaller set:

<RJTextEditor
  showFooter
  footerItems={['words', 'characters', 'selection']}
/>

For a fully custom footer, pass a render function:

<RJTextEditor
  showFooter
  footer={(stats) => (
    <div>
      {stats.words} words · {stats.characters} characters
    </div>
  )}
/>

Images

Images can be inserted from the local file picker, drag-and-drop, or clipboard paste. By default, the editor converts images to base64 data URLs and stores them inside the HTML content. For production, pass onUpload so images are uploaded to your own server or storage service and only the returned URL is stored in the content.

Important details:

  • Maximum image size is 5MB.
  • If onUpload is not provided, base64 fallback keeps local/demo usage working.
  • onUpload is used for image insert, paste, drop, and replace actions.
  • While the upload promise is pending, image controls show loading and are disabled, including uploads started by clipboard paste and drag-and-drop.
  • Duplicate clipboard file entries are de-duplicated before insertion and active uploads for the same file share one promise.
  • If the upload promise rejects, the image is not inserted/replaced and the editor shows the localized upload error.

Upload example:

<RJTextEditor
  onUpload={async (file) => {
    const formData = new FormData()
    formData.append('file', file)

    const response = await fetch('/api/uploads/images', {
      method: 'POST',
      body: formData,
    })

    if (!response.ok) {
      throw new Error('Image upload failed')
    }

    const data = await response.json()
    return data.url
  }}
/>

The returned string must be the final image URL used in exported HTML:

<img src="https://cdn.example.com/uploads/image.webp" alt="..." />

Image tools include:

  • left, center, and right alignment;
  • custom width percentage;
  • quick resize: 25%, 50%, 100%;
  • alt text;
  • caption;
  • link;
  • border;
  • border radius;
  • shadow;
  • object fit: contain, cover, fill;
  • text wrapping: top-bottom and square;
  • rotate left/right;
  • replace image;
  • delete image.

Tables

Tables can be created from the Insert tab with a grid picker or by entering row and column counts.

Available table tools:

  • add row above/below;
  • add column left/right;
  • delete row/column;
  • merge cells;
  • split cells;
  • toggle header row;
  • toggle header column;
  • set cell background;
  • set vertical alignment;
  • delete table.

Exported tables include inline border, header background, selected-cell cleanup, and rounded corner styles. The exported table palette uses neutral translucent colors so saved tables remain readable when the consuming page switches between light and dark mode.

Links are created with a custom modal. The modal supports URL and display text fields and works correctly in fullscreen mode.

YouTube Embeds

Paste a YouTube URL from the Insert tab to add an embedded video. The editor converts supported YouTube links into an iframe embed.

Code Blocks

Insert a code block from the Insert tab. Code blocks support:

  • direct editing inside the document;
  • a custom header/title, for example package.json, Terminal, or npm;
  • searchable language selection with presets such as Plain Text, Terminal, JSON, Bash, Shell, PNPM, NPM, Yarn, Bun, JavaScript, JSX, TypeScript, TSX, HTML, CSS, YAML, Dockerfile, SQL, Python, Rust, and more;
  • syntax highlighting when a Prism grammar is available;
  • Bash-style highlighting aliases for Terminal, Shell, PNPM, NPM, Yarn, and Bun;
  • lightweight code completions for supported languages and command snippets;
  • a copy button that remains visible when the block is not focused;
  • a persistent copy button in exported HTML rendered with dangerouslySetInnerHTML;
  • normalized paste handling for common documentation code panels, including tabbed package-manager examples;
  • HTML round trips through value and defaultValue without copying toolbar controls into the editable content.

The code block header title is independent from the selected language. Use the title for the label users should see, and use the language selector for syntax highlighting and completions.

Completions appear while typing inside a code block. Use ArrowUp and ArrowDown to move through suggestions, Enter or Tab to apply one, and Escape to close the menu.

Fullscreen

The fullscreen button uses the browser Fullscreen API. The editor is opened in real fullscreen mode, not only enlarged with CSS.

Internationalization

Built-in locales:

<RJTextEditor locale="uz" />
<RJTextEditor locale="en" />
<RJTextEditor locale="ru" />

Override only the text you need:

<RJTextEditor
  locale="en"
  translations={{
    placeholders: {
      editor: 'Start writing...',
    },
  }}
/>

Add a custom locale:

<RJTextEditor
  locale="kaa"
  locales={{
    kaa: {
      placeholders: {
        editor: 'Maqalanı usı jerge jazıń...',
      },
      tabs: {
        home: 'Bas bet',
        insert: 'Qosıw',
      },
    },
  }}
/>

Theme

Use the theme prop to force a theme or allow the editor to resolve it automatically:

<RJTextEditor theme="dark" />
<RJTextEditor theme="light" />
<RJTextEditor theme="auto" />

light and dark always override the document and system theme. auto is the default. It follows the HTML document's active color-scheme and then the user's system preference:

html {
  color-scheme: light dark;
}

Applications can also change the active document scheme at runtime:

document.documentElement.style.colorScheme = 'dark'
// Also supported: data-theme, data-color-scheme, or data-rj-editor-theme.

The same theme attributes and dark / light classes can be placed on html or body.

Control the theme from an application action:

import { useState } from 'react'
import { RJTextEditor, type RJEditorTheme } from 'rj-editor'

export function ThemeableEditor() {
  const [theme, setTheme] = useState<RJEditorTheme>('light')

  return (
    <>
      <button
        onClick={() => setTheme((current) => (
          current === 'dark' ? 'light' : 'dark'
        ))}
        type="button"
      >
        Toggle theme
      </button>

      <RJTextEditor theme={theme} />
    </>
  )
}

The built-in dark theme uses a neutral gray palette. rj-editor also exposes CSS custom properties so you can match your design system.

:root {
  --rj-color-brand: #2563eb;
  --rj-color-brand-tint: #eaf2ff;
  --rj-color-surface: #ffffff;
  --rj-color-surface-soft: #f8fafc;
  --rj-color-border: #d9dde5;
  --rj-color-text: #111827;

  --rj-editor-bg: var(--rj-color-surface);
  --rj-editor-border: var(--rj-color-border);
  --rj-editor-text: var(--rj-color-text);
  --rj-editor-toolbar-bg: var(--rj-color-surface-soft);
}

Common variables:

Variable Description
--rj-editor-bg Editor content background.
--rj-editor-border Editor border color.
--rj-editor-text Main text color.
--rj-editor-placeholder Placeholder color.
--rj-editor-toolbar-bg Toolbar background.
--rj-editor-toolbar-button-bg Toolbar button background.
--rj-editor-toolbar-button-active-bg Active toolbar button background.
--rj-editor-link Link color.
--rj-editor-focus-border Focus border color.
--rj-editor-error-border Validation error border color.
--rj-editor-warning-border Validation warning border color.
--rj-editor-table-border Table border color.
--rj-editor-table-header-bg Table header background.
--rj-editor-table-selected-bg Selected table cell background.
--rj-editor-inline-code-bg Inline code background.
--rj-editor-inline-code-border Inline code border color.
--rj-editor-inline-code-text Inline code text color.
--rj-editor-code-bg Code block background.
--rj-editor-code-header-bg Code block header background.
--rj-editor-code-border Code block border and scrollbar color.
--rj-editor-code-text Code block base text color.
--rj-editor-code-muted Code block header and muted text color.
--rj-editor-code-keyword Code token keyword color.
--rj-editor-code-string Code token string color.
--rj-editor-code-number Code token number color.
--rj-editor-code-comment Code token comment color.
--rj-editor-code-function Code token function/class color.
--rj-editor-code-operator Code token operator/property/punctuation color.
--rj-editor-code-tag Code token tag/deleted color.
--rj-editor-image-border Image selection border color.
--rj-editor-image-shadow Image shadow value.
--rj-editor-danger Dangerous action color.

Override only the dark theme by targeting the editor theme attribute:

[data-rj-editor-theme='dark'] {
  --rj-color-surface: #1d1d1d;
  --rj-color-surface-soft: #252525;
  --rj-color-border: #444444;
  --rj-color-text: #eeeeee;
}

SSR

rj-editor depends on browser APIs such as DOM selection, clipboard, file input, and fullscreen. In SSR frameworks, render the editor only on the client.

Example for Next.js:

import dynamic from 'next/dynamic'

const RJTextEditor = dynamic(
  () => import('rj-editor').then((mod) => mod.RJTextEditor),
  { ssr: false },
)

License

MIT

Keywords