🚧 Glyx is pre-release software. APIs may change before v1.0. Get started →
Documentation
Packages
@glyx-dev/rich-text

@glyx-dev/rich-text Stable

A fully-featured rich text editor with bold, italic, underline, cursor, selection, and a formatting toolbar.

npm install @glyx-dev/rich-text

Basic usage

import { RichTextEditor, RichTextToolbar } from '@glyx-dev/rich-text'
 
export default function App() {
  return (
    <RichTextEditor width={700} height={400} placeholder="Start writing…">
      <RichTextToolbar />
    </RichTextEditor>
  )
}

The toolbar must be rendered inside RichTextEditor — it reads editor state from context.

Controlled value

import { useState } from 'react'
import { RichTextEditor, RichTextToolbar, emptyDoc, docToPlainText } from '@glyx-dev/rich-text'
 
function NoteEditor() {
  const [doc, setDoc] = useState(emptyDoc())
 
  return (
    <>
      <RichTextEditor value={doc} onChange={setDoc} width={700} height={400}>
        <RichTextToolbar />
      </RichTextEditor>
      <Text>{docToPlainText(doc)}</Text>
    </>
  )
}

Props

RichTextEditor

PropTypeDefaultDescription
valueDocControlled document value
onChange(doc: Doc) => voidCalled on every edit
widthnumber600Editor width in px
heightnumber400Editor height in px
fontSizenumber14Default font size for new text
colorstring'#e8e8f0'Default text color
placeholderstring''Shown when document is empty
styleViewStyleAdditional styles on the outer container

RichTextToolbar

PropTypeDefaultDescription
styleViewStyleAdditional styles on the toolbar row

Renders B / I / U buttons. Buttons are active (highlighted) when the current selection already has that format. Clicking toggles the format on the selection.

Keyboard shortcuts

KeyAction
TypeInsert at cursor
EnterNew paragraph
Backspace / DeleteDelete character or selection
Move cursor
Move cursor to previous/next line
Home / EndStart / end of paragraph
Shift + any move keyExtend selection
Ctrl+ASelect all
Ctrl+C / X / VCopy / cut / paste (plain text)
Ctrl+BToggle bold on selection
Ctrl+IToggle italic on selection
Ctrl+UToggle underline on selection

Document model

A document is a plain JSON object — serialize and store it however you like:

type Span = {
  text:       string
  bold?:      boolean
  italic?:    boolean
  underline?: boolean
  color?:     string   // CSS color
  fontSize?:  number   // overrides editor default
}
 
type Paragraph = { spans: Span[] }
type Doc       = { paragraphs: Paragraph[] }

Utilities

import { emptyDoc, docToPlainText, docFromPlainText } from '@glyx-dev/rich-text'
 
emptyDoc()                       // → empty Doc
docToPlainText(doc)              // → plain string (paragraphs joined by \n)
docFromPlainText('Hello\nWorld') // → Doc with two paragraphs

Reading editor state from a sibling component

Use useEditorContext() inside any component rendered as a child of RichTextEditor:

import { useEditorContext } from '@glyx-dev/rich-text'
 
function WordCount() {
  const { doc } = useEditorContext()
  const words = docToPlainText(doc).trim().split(/\s+/).filter(Boolean).length
  return <Text>{words} words</Text>
}
 
function App() {
  return (
    <RichTextEditor width={700} height={400}>
      <RichTextToolbar />
      <WordCount />
    </RichTextEditor>
  )
}

Persisting documents

import { db } from '@glyx-dev/react'
import { emptyDoc } from '@glyx-dev/rich-text'
 
// Save
await db.run('UPDATE notes SET body = ? WHERE id = ?', [JSON.stringify(doc), id])
 
// Load
const [row] = await db.query('SELECT body FROM notes WHERE id = ?', [id])
const doc = row ? JSON.parse(row.body) : emptyDoc()