rj-editor
v1.2.0
Published
React rich text editor: toolbar tabs, table, image, link, YouTube, i18n, theme va Ant Design Form integratsiyasi.
Maintainers
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.
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.Itemintegration. - Built-in
uz,en, andrutranslations. - Custom locale and partial translation override support.
- Theme customization through CSS custom properties.
Installation
npm install rj-editorreact and react-dom are peer dependencies. They must already exist in your application:
npm install react react-domQuick 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,
) => voidRendering 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
onUploadis not provided, base64 fallback keeps local/demo usage working. onUploadis 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
valueanddefaultValuewithout 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
