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

rich-text-lite

v7.0.32

Published

A lightweight, customizable rich text editor React component

Readme

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 | | onCopy | (event: React.ClipboardEvent<HTMLDivElement>) => void | — | Called when content is copied from inside the editor | | 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 | | disabled | boolean | false | Disables toolbar actions and makes the visual/code editor read-only | | cleanPaste | boolean | true | Sanitizes pasted HTML while keeping common rich text tags | | tabIndex | number | 0 | Tab index applied to the editor area and every toolbar / table-action button, ensuring a consistent focus order. Pass -1 to remove the entire editor UI from the tab order | | 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, onCopy, 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.colorPickerComponent | React.ComponentType | built-in swatch + hex/RGB grid | Fully replaces the built-in color picker UI in all four pickers. The component must call onChange(hex) with a valid hex string — this is the consumer's responsibility. toolbarConfig.colors is not required when this is used. See Custom Color Picker UI | | toolbarConfig.lineHeight.values | Array<string\|number\|{ value, label }> | ['1', '1.15', '1.5', '1.75', '2'] | Configurable values for the line-height dropdown | | toolbarConfig.font.options | Array<string\|{ value, label }> | Sans Serif, Serif, Monospace | Configurable font-family options for the font dropdown | | toolbarConfig.size.options | Array<string\|number\|{ value, label }> | 10px–30px in 2px steps | Configurable font-size values for the size dropdown | | toolbarConfig.colors | Array<string> | built-in 35-color palette | Custom color palette for all color pickers. Each entry must be a valid 3- or 6-digit hex string (e.g. "#fff" or "#ffffff"). Falls back to the built-in palette when omitted, empty, or all entries are invalid | | toolbarConfig.defaultFontColor | string | "#000000" | Initial color shown in the font-color picker (6-digit hex, e.g. "#ff0000"). Falls back to #000000 when omitted or invalid | | toolbarConfig.defaultBgColor | string | "#ffff00" | Initial color shown in the background-color picker (6-digit hex). Falls back to #ffff00 when omitted or invalid | | toolbarConfig.defaultCellBorderColor | string | "#d6dde5" | Default border color in the cell-properties popup when the cell has no explicit border color | | toolbarConfig.defaultCellBgColor | string | "#ffffff" | Default background color in the cell-properties picker when the cell has no background set | | toolbarConfig.defaultTableBorderColor | string | "#d6dde5" | Default border color in the table-properties popup | | toolbarConfig.defaultTableBgColor | string | "#ffffff" | Default background color in the table-properties picker | | toolbarConfig.defaultTableFontColor | string | "#000000" | Default font color in the table-properties popup | | toolbarConfig.linkConfig | object | {} | Configures link dialog fields and link-hover quick action popup | | toolbarConfig.charCount | object | {} | Configures the character count footer (see Character Count Footer) | | toolbarConfig.charCount.visible | boolean | true | Show or hide the character count footer | | toolbarConfig.charCount.label | string | "Characters" | Custom label prefix displayed before the count | | 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")}
  onCopy={(e) => console.log("copied", e.clipboardData)}
  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"),
        onCopy: (e) => console.log("copied from config", e.type),
      },
    },
  }}
/>
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
  • onCopy receives the React clipboard event for copy actions triggered inside the editor
  • 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 by default; fully configurable via toolbarConfig.font.options | | Font Size | 10px–30px (2px increments) by default; fully configurable via toolbarConfig.size.options | | 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 (configurable via toolbarConfig.colors) + custom hex + custom RGB input | | Background Color | Color palette (configurable via toolbarConfig.colors) + 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 | | Character Count | Footer bar showing total plain-text character count, updated live. Configurable via toolbarConfig.charCount | | Table Tab Navigation | Inside a table, Tab moves to the next cell; Shift+Tab moves to the previous cell. Pressing Tab on the last cell of the last row automatically inserts a new row | | WCAG 2.1 AA / NVDA | Full role, aria-label, aria-pressed, aria-expanded, aria-live coverage; consistent tabIndex across all interactive elements |


Toolbar Configuration

All toolbar customization is done through the toolbarConfig prop.

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    font: {
      options: [
        { value: "", label: "Sans Serif" },
        { value: "serif", label: "Serif" },
        { value: "monospace", label: "Monospace" },
        { value: "Arial, sans-serif", label: "Arial" },
        { value: "Georgia, serif", label: "Georgia" },
      ],
    },
    size: {
      options: [
        { value: "12", label: "12px" },
        { value: "14", label: "14px" },
        { value: "16", label: "16px" },
        { value: "18", label: "18px" },
        { value: "24", label: "24px" },
        { value: "32", label: "32px" },
      ],
    },
    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
    },
  }}
/>

Font Family Options

Configure the font-family dropdown options via toolbarConfig.font.options. Each entry can be a plain string (used as both value and label) or an object with value and label. The value must be a valid CSS font-family string. Falls back to the built-in list if omitted.

toolbarConfig={{
  font: {
    options: [
      { value: "", label: "Sans Serif" },          // empty value = browser default
      { value: "serif", label: "Serif" },
      { value: "monospace", label: "Monospace" },
      { value: "Arial, sans-serif", label: "Arial" },
      { value: "Georgia, serif", label: "Georgia" },
      "Courier New",                                // plain string shorthand
    ],
  },
}}

Custom font families are injected into the CSS sheet on first use — no extra stylesheet setup needed.

Font Size Options

Configure the font-size dropdown options via toolbarConfig.size.options. Each entry can be a plain number/string (pixel value) or an object with value and label. Falls back to the built-in 10–30px range if omitted.

toolbarConfig={{
  size: {
    options: [
      { value: "12", label: "Small" },
      { value: "14", label: "Normal" },
      { value: "18", label: "Large" },
      { value: "24", label: "X-Large" },
      32,                                           // plain number shorthand
    ],
  },
}}

A "Default" option (empty value) that resets font size is always prepended automatically.


Line Height Options

Configure the line-height dropdown options via toolbarConfig.lineHeight.values. See the quick-start example above for the same pattern.


Custom Color Palette

Replace the built-in 35-color palette with your own set of hex colors via toolbarConfig.colors. The same palette is used in the font color picker, background color picker, cell properties panel, and table properties panel.

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    colors: [
      "#000000", "#ffffff", "#e60000", "#ff9900",
      "#ffff00", "#008a00", "#0066cc", "#9933ff",
      "#f06666", "#ffc266", "#ffff66", "#66b966",
      "#66a3e0", "#c285ff", "#444444", "#888888",
    ],
  }}
/>

Rules:

  • Each entry must be a valid 3- or 6-digit hex string (e.g. "#fff" or "#ffffff"). Invalid entries are silently ignored.
  • Falls back to the built-in 35-color palette when the array is omitted, empty, or contains no valid entries.
  • The hex/RGB custom color inputs remain available below the palette regardless of this setting.
  • Custom palette colors are injected into the editor's dynamic CSS stylesheet automatically — no extra stylesheet setup required.
  • The picker grid column count adapts automatically: up to 7 columns for larger palettes, fewer for smaller ones.

Custom Color Picker UI

Replace the entire built-in swatch + hex/RGB picker UI with your own component via toolbarConfig.colorPickerComponent. When provided it replaces the picker in all four locations: font color, background color, cell properties, and table properties panels.

function MyColorPicker({ color, onChange, onClose, colors }) {
  return (
    <div style={{ padding: 8, background: "#fff", border: "1px solid #ccc", borderRadius: 8 }}>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 8 }}>
        {/* use the passed palette, or your own hardcoded set */}
        {colors.map((hex) => (
          <button
            key={hex}
            style={{ width: 24, height: 24, background: hex, border: "1px solid #999", borderRadius: 3, cursor: "pointer" }}
            onClick={() => onChange(hex)}
          />
        ))}
      </div>
      <button onClick={onClose}>Cancel</button>
    </div>
  );
}

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    colorPickerComponent: MyColorPicker,
  }}
/>

Props received by the component:

| Prop | Type | Description | |------|------|-------------| | color | string | Currently active hex color for this picker context | | onChange | (hex: string) => void | Call with a valid hex string (e.g. "#ff0000") to apply the color and close the picker | | onClose | () => void | Call to close the picker without applying any color | | colors | string[] | Resolved palette from toolbarConfig.colors or the built-in default — provided as a convenience; the component may use or ignore it |

Notes:

  • toolbarConfig.colors is not required when supplying colorPickerComponent. The two props are independent — you can use either, both, or neither.
  • Calling onChange(hex) correctly is the consumer's responsibility. Pass a well-formed hex string such as "#rrggbb" or "#rgb". Passing an invalid value will produce no visible color in the editor output.
  • The component is rendered inside a positioned .rte-color-picker-custom wrapper that anchors it below the toolbar color button. The component controls its own dimensions and inner layout.

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

Link Config (Dialog + Hover Popup)

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:


Character Count Footer

A character count footer is shown below the editor by default. It counts plain-text characters (HTML markup is excluded). The counter updates live as the user types, pastes, or undoes/redoes content.

// Shown by default — no config needed
<RichTextEditor value={html} onChange={setHtml} />

Hide it via toolbarConfig.charCount.visible:

<RichTextEditor
  value={html}
  onChange={setHtml}
  toolbarConfig={{
    charCount: {
      visible: false,
    },
  }}
/>

Customize the label prefix:

toolbarConfig={{
  charCount: {
    visible: true,
    label: "Chars",   // renders: "Chars: 42"
  },
}}

| Option | Type | Default | Description | |--------|------|---------|-------------| | visible | boolean | true | Show or hide the character count footer | | label | string | "Characters" | Prefix text rendered before the count number |

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 | | .rte-footer | Character count footer bar | | .rte-char-count | Character count text span inside the footer | | .rte-sr-only | Visually-hidden utility class (screen-reader only content) |

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