npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rj-editor

v1.2.0

Published

React rich text editor: toolbar tabs, table, image, link, YouTube, i18n, theme va Ant Design Form integratsiyasi.

Readme

RJ Editor

rj-editor is a React rich 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, language selection, 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 code blocks keep their language header and copy button. Importing rj-editor automatically registers the lightweight delegated copy handler, so code block copy controls also work inside content rendered with dangerouslySetInnerHTML.

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.

Footer Status Bar

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, the relevant image controls show loading and are disabled.
  • 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.

Links

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;
  • language selection;
  • a copy button that remains visible when the block is not focused;
  • a persistent copy button in exported HTML rendered with dangerouslySetInnerHTML;
  • HTML round trips through value and defaultValue without copying toolbar controls into the editable content.

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-image-border | Image selection border color. | | --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