npm.io
7.0.4 • Published 1h ago

rich-text-lite

Licence
MIT
Version
7.0.4
Deps
6
Size
942 kB
Vulns
0
Weekly
1.2K

rich-text-lite

A lightweight, zero-dependency (besides React) rich text editor React component with a fully customizable toolbar.

Table of Contents


Installation

npm install rich-text-lite

Quick Start

import { useState } from "react";
import { RichTextEditor } from "rich-text-lite";
import "rich-text-lite/dist/style.css";

function App() {
  const [html, setHtml] = useState("");

  return (
    <RichTextEditor
      value={html}
      onChange={setHtml}
    />
  );
}

Props

Prop Type Default Description
value string "" HTML content for the editor
onChange (html: string) => void Called with the updated HTML string on each edit
onEditorChange (event: React.FormEvent<HTMLDivElement>, html: string) => void Called on editor input with both the native React event and latest HTML
onFocus (event: React.FocusEvent<HTMLDivElement>) => void Called when the editor receives focus
onBlur (event: React.FocusEvent<HTMLDivElement>) => void Called when the editor loses focus
onKeyDown (event: React.KeyboardEvent<HTMLDivElement>) => void Called on keydown inside the editor (after internal shortcuts are handled)
onKeyPress (event: React.KeyboardEvent<HTMLDivElement>) => void Called on keypress inside the editor
onSelect (event: React.SyntheticEvent<HTMLDivElement>) => void Called when a selection event is fired by the editor element
getEditorHTML (getter: () => string) => void Called with a getter function that returns the latest editor HTML on demand
isNonceEnabled boolean false Whether to add a nonce attribute to injected <style> tags (for CSP)
nonceValue string "" The nonce value to use when isNonceEnabled is true
nonceHeaders string "" CSP policy string applied to the nonce meta tag when isNonceEnabled is true
placeholder string "Start typing..." Placeholder text shown when editor is empty
cleanPaste boolean true Sanitizes pasted HTML while keeping common rich text tags
toolbarConfig.editor.placeholder string "Start typing..." Editor placeholder from config JSON (overrides default placeholder)
toolbarConfig.editor.events object {} Alternate place to provide editor events (onChange, onFocus, onBlur, onKeyDown, onKeyPress, onSelect)
toolbarConfig.selectComponent React.ComponentType built-in button dropdown Custom UI component for toolbar dropdowns (heading, font, size, line-height)
toolbarConfig.selectOptionComponent React.ComponentType built-in <option> Option item component for selectComponent (e.g. MUI MenuItem)
toolbarConfig.dialogComponent React.ComponentType built-in Material UI Dialog Custom UI component for the link dialog wrapper
toolbarConfig.lineHeight.values Array<string|number|{ value, label }> ['1', '1.15', '1.5', '1.75', '2'] Configurable values for the line-height dropdown
toolbarConfig.linkConfig object {} Configures link dialog fields and link-hover quick action popup
toolbarConfig object {} Configuration object for customizing the toolbar (see below)

Editor Events

You can subscribe to editor events using either top-level props or toolbarConfig.editor.events. Top-level props take precedence when both are provided.

<RichTextEditor
  value={html}
  onChange={(nextHtml) => setHtml(nextHtml)}
  onEditorChange={(event, nextHtml) => {
    // event target + latest HTML in one callback
    console.log("changed", nextHtml);
  }}
  onFocus={() => console.log("focus")}
  onBlur={() => console.log("blur")}
  onKeyDown={(e) => console.log("keydown", e.key)}
  onKeyPress={(e) => console.log("keypress", e.key)}
  onSelect={() => console.log("select")}
  toolbarConfig={{
    editor: {
      events: {
        // Used only when corresponding top-level prop is not provided
        onFocus: () => console.log("focus from config"),
      },
    },
  }}
/>
import { useRef } from "react";

const getHtmlRef = useRef(() => "");

<RichTextEditor
  value={html}
  onChange={setHtml}
  getEditorHTML={(getter) => {
    getHtmlRef.current = getter;
  }}
/>

// Call this whenever you need current editor HTML
const latestHtml = getHtmlRef.current();

Notes:

  • onChange remains the HTML-first callback: (html) => void
  • onEditorChange is event-first and also provides HTML: (event, html) => void
  • onKeyDown still includes built-in undo/redo shortcut handling by the editor

Features

Feature Description
Headings Normal, H1, H2, H3, H4
Font Family Sans Serif, Serif, Monospace
Font Size 10px–30px (2px increments), or "Default" to reset
Line Height Dropdown with configurable values from toolbarConfig.lineHeight.values
Bold Toggle bold
Italic Toggle italic
Underline Toggle underline
Strikethrough Toggle strikethrough
Superscript Toggle superscript
Subscript Toggle subscript
Text Color Color palette + custom hex + custom RGB input
Background Color Color palette + custom hex + custom RGB input
Hyperlinks Insert, edit, and remove links (opens in new tab)
Bullet List Unordered list with style options: disc, circle, square
Numbered List Ordered list with style options: decimal, upper-roman, lower-roman, lower-latin, upper-latin, lower-greek
Alignment Align left, center, right, or justify
Indent / Outdent Increase or decrease indentation
Text Direction Toggle LTR / RTL
Insert Line Inserts a horizontal rule (<hr>)
Undo / Redo Toolbar buttons + keyboard shortcuts (Ctrl/Cmd+Z, Ctrl+Y, Cmd/Ctrl+Shift+Z)
Clear Formatting Removes inline formatting from selected content
Clean Paste Removes unsafe/noisy pasted markup while preserving common rich text

Toolbar Configuration

All toolbar customization is done through the toolbarConfig prop.

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    lineHeight: {
      values: [
        "1",
        { value: "1.2", label: "Normal" },
        { value: "1.5", label: "Comfortable" },
        { value: "2", label: "Double" },
      ],
    },
    tooltipComponent: MyTooltip,  // optional custom tooltip
    options: {
      bold: { visible: true, tooltip: "Bold (Ctrl+B)", icon: <MyBoldIcon /> },
      italic: { visible: false },  // hides the italic button
      // ... other buttons
    },
  }}
/>
Hiding Buttons

Set visible: false on any button to hide it:

toolbarConfig={{
  options: {
    strikethrough: { visible: false },
    direction: { visible: false },
  }
}}
Custom Tooltips

Override the tooltip text for any button:

toolbarConfig={{
  options: {
    bold: { tooltip: "Make Bold (Ctrl+B)" },
    link: { tooltip: "Add Hyperlink" },
  }
}}
Custom Icons

Pass any React element as the icon for a button:

import { FaBold, FaItalic } from "react-icons/fa";

toolbarConfig={{
  options: {
    bold: { icon: <FaBold /> },
    italic: { icon: <FaItalic /> },
  }
}}

For dropdown controls (heading, font, size, lineHeight), if an icon is provided then the trigger shows icon-only mode. You can control trigger width with width in the same option object.

toolbarConfig={{
  options: {
    heading: { icon: <span>H</span>, width: 42 },
    font: { icon: <span>F</span>, width: 40 },
    size: { icon: <span>T</span>, width: 38 },
    lineHeight: { icon: <span>LH</span>, width: 46 },
    table: {
      tableActions: {
        rowActions: { icon: <span>R</span>, tooltip: "Row Actions", width: 40 },
        columnActions: { icon: <span>C</span>, tooltip: "Column Actions", width: 40 },
        deleteTable: { icon: <span>Del</span>, tooltip: "Delete Table" },
      },
    },
  }
}}
Table Action Popup Config

When you click a table cell, a compact table action popup appears in the editor. These actions are configurable through toolbarConfig.options.table.tableActions and follow the same pattern as toolbar buttons.

Supported keys:

  • cellActions (alias: tableCellActions) - cell action dropdown trigger
  • cellStyleActions (aliases: tableCellStyleActions, cellStyles) - cell style dropdown trigger
  • cellProperties (alias: tableCellProperties) - cell properties popup trigger
  • tableProperties (alias: tableStyleProperties) - table properties popup trigger
  • verticalAlign (aliases: tableVerticalAlignActions, tableVerticalAlign) - vertical align dropdown trigger
  • horizontalAlign (aliases: tableHorizontalAlignActions, tableHorizontalAlign) - horizontal align dropdown trigger
  • rowActions (alias: tableRowActions) — row action dropdown trigger
  • columnActions (alias: tableColumnActions) — column action dropdown trigger
  • deleteTable (alias: tableDelete) — delete table button

Each key accepts:

{
  visible?: boolean;   // default: true
  tooltip?: string;    // custom tooltip
  icon?: ReactNode;    // custom icon
  width?: number;      // icon trigger width in px (dropdown triggers)
}

Example:

toolbarConfig={{
  options: {
    table: {
      tableActions: {
        cellActions: {
          visible: true,
          tooltip: "Cell Actions",
          icon: <MyCellIcon />,
          width: 42,
        },
        cellStyleActions: {
          visible: true,
          tooltip: "Cell Style Actions",
          icon: <MyCellStyleIcon />,
          width: 42,
        },
        cellProperties: {
          visible: true,
          tooltip: "Cell Properties",
          icon: <MyCellPropertiesIcon />,
          panel: {N
            fields: {
              borderStyle: {
                visible: true,
                options: ["solid", "inset", "dashed", "double"],
              },
              borderColor: { visible: true },
              borderWidth: { visible: true, min: 0, max: 10 },
              backgroundColor: { visible: true },
            },
          },
        },
        tableProperties: {
          visible: true,
          tooltip: "Table Properties",
          icon: <MyTablePropertiesIcon />,
          panel: {
            fields: {
              borderStyle: {
                visible: true,
                options: ["solid", "inset", "dashed", "double"],
              },
              borderColor: { visible: true },
              borderWidth: { visible: true, min: 0, max: 10 },
              backgroundColor: { visible: true },
              fontColor: { visible: true },
              align: { visible: true, options: ["left", "center", "right"] },
            },
          },
        },
        verticalAlign: {
          visible: true,
          tooltip: "Vertical Align",
          icon: <MyVerticalAlignIcon />,
          width: 42,
        },
        horizontalAlign: {
          visible: true,
          tooltip: "Horizontal Align",
          icon: <MyHorizontalAlignIcon />,
          width: 42,
        },
        rowActions: {
          visible: true,
          tooltip: "Row Actions",
          icon: <MyRowIcon />,
          width: 42,
        },
        columnActions: {
          visible: true,
          tooltip: "Column Actions",
          icon: <MyColumnIcon />,
          width: 42,
        },
        deleteTable: {
          visible: true,
          tooltip: "Delete Table",
          icon: <MyDeleteIcon />,
        },
      },
    },
  },
}}
Custom Tooltip Component

Replace the built-in tooltip with your own component (e.g., Material UI Tooltip, Radix Tooltip, etc.). Your component must accept title and children props:

import { Tooltip as MuiTooltip } from "@mui/material";

function MyTooltip({ title, children }) {
  return (
    <MuiTooltip title={title} arrow placement="top">
      {children}
    </MuiTooltip>
  );
}

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    tooltipComponent: MyTooltip,
  }}
/>

The custom component receives these props:

Prop Type Description
title string The tooltip text
children ReactNode The button element to wrap
arrow boolean Always true (hint for arrow display)
placement string Always "top"
Custom Select Component (Dropdown UI)

You can replace the built-in button dropdown controls (heading, font, size, line-height) with your own UI component.

Your component receives these props:

  • className
  • value
  • onChange
  • onBlur
  • disabled
  • options (array of { value, label, disabled?, hidden? })
  • icon (icon configured in toolbarConfig.options for that dropdown)
  • tooltip (resolved tooltip text for that dropdown)
  • iconOnly (boolean, true when icon-mode trigger is active)
  • triggerWidth (resolved icon-mode width in px)
  • children (native <option> elements for compatibility)

Example:

function MySelect({ className, value, onChange, disabled, options }) {
  return (
    <select
      className={className}
      value={value}
      onChange={(e) => onChange(e)}
      disabled={disabled}
    >
      {options.map((opt) => (
        <option key={opt.value} value={opt.value} disabled={opt.disabled} hidden={opt.hidden}>
          {opt.label}
        </option>
      ))}
    </select>
  );
}

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    selectComponent: MySelect,
  }}
/>

For MUI Select, MenuItem is auto-used by default. You can still override with selectOptionComponent if needed:

import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    selectComponent: Select,
    selectOptionComponent: MenuItem,
  }}
/>
Custom Dialog Component

You can replace the default link dialog wrapper by passing toolbarConfig.dialogComponent.

Your dialog component receives these props:

  • open
  • onClose
  • className
  • paperClassName
  • title
  • children

Example:

function MyDialog({ open, onClose, className, children }) {
  if (!open) return null;

  return (
    <div className={className} role="dialog" aria-modal="true">
      <div className="my-dialog-backdrop" onClick={onClose} />
      <div className="my-dialog-panel">{children}</div>
    </div>
  );
}

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    dialogComponent: MyDialog,
  }}
/>

The link system supports extra fields in the insert/edit dialog:

  • Text to display
  • Title
  • Open in new window
  • Download link

It also supports a hover quick-action popup on links (enabled by default) with:

  • Edit
  • Copy
  • Preview
  • Unlink

All of this is configurable through toolbarConfig.linkConfig:

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    editor: {
      placeholder: "Write your content here...",
    },
    linkConfig: {
      dialog: {
        text: {
          title: "Insert Link",
          urlLabel: "URL",
          urlPlaceholder: "Enter URL...",
          textLabel: "Text To Display",
          textPlaceholder: "Displayed text",
          titleLabel: "Title",
          titlePlaceholder: "Tooltip title",
          openInNewWindowLabel: "Open In New Window",
          downloadLabel: "Download Link",
        },
        fields: {
          text: { visible: true },
          title: { visible: true },
          openInNewWindow: { visible: true },
          download: { visible: true },
        },
        actions: {
          apply: {
            visible: true,
            icon: "",
            label: "Apply",
            tooltip: "Apply Link",
            showLabel: false,
          },
          copy: {
            visible: true,
            icon: "",
            label: "Copy",
            tooltip: "Copy Link",
            showLabel: false,
          },
          preview: {
            visible: true,
            icon: "",
            label: "Preview",
            tooltip: "Open Link",
            showLabel: false,
          },
          unlink: {
            visible: true,
            icon: "",
            label: "Unlink",
            tooltip: "Remove Link",
            showLabel: false,
          },
        },
      },
      hoverPopup: {
        enabled: true,
        actions: {
          edit: { visible: true, icon: "", label: "Edit", tooltip: "Edit Link", showLabel: false },
          copy: { visible: true, icon: "", label: "Copy", tooltip: "Copy Link", showLabel: false },
          preview: { visible: true, icon: "", label: "Preview", tooltip: "Open Link", showLabel: false },
          unlink: { visible: true, icon: "", label: "Unlink", tooltip: "Remove Link", showLabel: false },
        },
      },
    },
  }}
/>

Defaults are applied automatically when any of these properties are omitted.

To disable link hover popup globally:

toolbarConfig={{
  linkConfig: {
    hoverPopup: {
      enabled: false,
    },
  },
}}

Toolbar Buttons Reference

These are the keys you can use inside toolbarConfig.options:

Key Default Tooltip Description
bold "Bold" Bold button
italic "Italic" Italic button
underline "Underline" Underline button
strikethrough "Strikethrough" Strikethrough button
superscript "Superscript" Superscript button
subscript "Subscript" Subscript button
link "Insert Link" Link button
fontColor "Text Color" Font color picker button
bgColor "Background Color" Background color picker button
heading "Headings" Heading dropdown
font "Font Family" Font-family dropdown
size "Font Size" Font-size dropdown
lineHeight "Line Height" Line-height dropdown
bulletList "Unordered List" Unordered list style selector (disc/circle/square)
numberedList "Ordered List" Ordered list style selector (decimal, upper-roman, lower-roman, lower-latin, upper-latin, lower-greek)
alignLeft "Align Left" Align text left
alignCenter "Align Center" Align text center
alignRight "Align Right" Align text right
alignJustify "Align Justify" Justify text
decreaseIndent "Decrease Indent" Outdent button
increaseIndent "Increase Indent" Indent button
direction "Switch to Right-to-Left" Text direction toggle
insertLine "Insert Line" Insert horizontal rule (<hr>)
undo "Undo" Undo button
redo "Redo" Redo button
clearFormatting "Clear Formatting" Remove formatting button

Each key accepts:

{
  visible?: boolean;   // default: true — set false to hide
  tooltip?: string;    // override default tooltip text
  icon?: ReactNode;    // override default icon/label
  width?: number;      // icon-mode trigger width in px (for heading/font/size/lineHeight)
}
Table Hover Action Keys

Configure table hover popup actions using toolbarConfig.options.table.tableActions.

Key Default Tooltip Description
cellActions "Cell Actions" Table cell actions dropdown trigger (alias: tableCellActions)
cellStyleActions "Cell Style Actions" Table cell style dropdown trigger (aliases: tableCellStyleActions, cellStyles)
cellProperties "Cell Properties" Table cell properties popup trigger (alias: tableCellProperties)
verticalAlign "Vertical Align" Table vertical align dropdown trigger (aliases: tableVerticalAlignActions, tableVerticalAlign)
horizontalAlign "Horizontal Align" Table horizontal align dropdown trigger (aliases: tableHorizontalAlignActions, tableHorizontalAlign)
rowActions "Row Actions" Table row actions dropdown trigger (alias: tableRowActions)
columnActions "Column Actions" Table column actions dropdown trigger (alias: tableColumnActions)
deleteTable "Delete Table" Delete table action button (alias: tableDelete)

cellStyleActions dropdown options:

  • Highlighted: applies 1px solid red border to active/selected cells; selecting again resets the border
  • Thick: applies 1px double red border to active/selected cells; selecting again resets the border

Each key accepts:

{
  visible?: boolean;   // default: true
  tooltip?: string;    // override default tooltip text
  icon?: ReactNode;    // override default icon/label
  width?: number;      // icon-mode trigger width in px (dropdown triggers)
  panel?: {
    fields?: {
      borderStyle?: { visible?: boolean; options?: string[] }; // alias: borderType
      borderColor?: { visible?: boolean };
      borderWidth?: { visible?: boolean; min?: number; max?: number };
      backgroundColor?: { visible?: boolean };
      fontColor?: { visible?: boolean };
      bold?: { visible?: boolean };
      italic?: { visible?: boolean };
      underline?: { visible?: boolean };
      strikeThrough?: { visible?: boolean }; // alias: strikethrough
    };
  };
}

CSP / Nonce Support

The editor injects a <style> tag at runtime for dynamic color/font classes. If your app uses a Content Security Policy, pass a nonce:

<RichTextEditor
  value={html}
  onChange={setHtml}
  isNonceEnabled={true}
  nonceValue="abc123"
  nonceHeaders="style-src 'self' 'nonce-abc123';"
/>

This adds nonce="abc123" to the injected style element. Use nonceHeaders when you also want to pass the CSP policy string used by your app.


Styling & CSS Customization

Import the required stylesheet:

import "rich-text-lite/dist/style.css";
Key CSS Classes
Class Element
.rte-container Outermost wrapper
.rte-toolbar Toolbar row
.rte-editor The contentEditable area
.rte-btn All toolbar buttons
.rte-btn-active Active/toggled toolbar button
.rte-select Dropdown selects (heading, font, size)
.rte-color-picker Color picker popup
.rte-link-popup Link insertion popup
Overriding Styles

You can override any class in your own CSS:

/* Change editor min-height */
.rte-editor {
  min-height: 300px;
}

/* Change toolbar background */
.rte-toolbar {
  background: #f5f5f5;
}

/* Change active button color */
.rte-btn-active {
  background: #dbeafe;
  color: #1d4ed8;
}

/* Style the dropdowns */
.rte-select {
  border-radius: 4px;
  font-size: 13px;
}

Output Format

The onChange callback receives raw HTML from the contentEditable area. Example output:

<p><b>Hello</b> <span style="font-size: 20px">world</span></p>
<h3>A heading</h3>
<ul><li>Item one</li><li>Item two</li></ul>

You can render this HTML anywhere using dangerouslySetInnerHTML or a sanitizer like DOMPurify.


Development

git clone <repo-url>
cd newgen-rich-text-editor
npm install
npm run dev

Starts a dev server at http://localhost:5173 with a demo page.

Build
npm run build

Outputs to dist/:

  • rich-text-lite.es.js — ES module
  • rich-text-lite.umd.js — UMD bundle
  • style.css — Required styles

License

MIT

Keywords