How to Customize the Astryx Rich Text Editor Component: A Complete Guide

The Astryx Rich Text Editor is customized through props like xstyle, nodes, and plugins, plus toolbar configuration via RichTextEditorToolbar props and the core icon registry.

The Astryx Rich Text Editor from @astryxdesign/lab provides a composable, Lexical-based editing experience designed for extensibility without forking. This guide covers every customization layer—from visual styling to custom nodes and toolbar injections—based on the actual source implementation in the facebook/astryx repository.


Editor Architecture and Extension Points

Understanding the component structure helps you customize effectively. The editor is split into focused modules in packages/lab/src/RichTextEditor/:

Component File Path Responsibility
Core Editor RichTextEditor.tsx LexicalComposer wrapper, default node registration, focus handling, value changes, markdown shortcuts
Read-Only View RichTextView.tsx Serialized state renderer without editing affordances
Toolbar RichTextEditorToolbar.tsx Composable formatting bar with configurable buttons
Auto-Link Plugin RichTextEditorAutoLinkPlugin.tsx Optional URL detection

The editor uses uncontrolled state: defaultValue is read once at mount, and you persist changes via onChange with editorState.toJSON().


Visual Customization: Size, Layout, and Status

Control the editor's appearance through dedicated props without touching internal styles.

Size and Dimensions

  • size: 'sm' | 'md' | 'lg' — controls internal padding and wrapper spacing
  • width: number (px) or CSS string — width={300} or width="100%"

Custom Layout with xstyle

The xstyle prop accepts StyleX styles for layout overrides like margins or shadows:

import {RichTextEditor} from '@astryxdesign/lab';
import * as stylex from '@stylexjs/stylex';

const customStyles = stylex.create({
  wrapper: {
    borderRadius: '8px',
    boxShadow: '0 0 0 1px var(--color-border)',
    backgroundColor: 'var(--color-bg-primary)',
  },
});

export function StyledEditor() {
  return (
    <RichTextEditor
      label="Styled"
      xstyle={customStyles.wrapper}
    />
  );
}

Status and Validation

The status prop combines visual borders with optional messages:

<RichTextEditor
  label="Feedback"
  status={{ type: 'error', message: 'Too many characters' }}
  maxLength={500}
/>

maxLength shows a live counter that automatically styles to error when exceeded.


Behavioral Customization: Nodes, Plugins, and Shortcuts

Registering Custom Lexical Nodes

Pass custom node classes via the nodes prop. Critical: use the same array in RichTextView for proper serialization:

import {
  RichTextEditor,
  RichTextView,
} from '@astryxdesign/lab';
import {MyMentionNode} from './MyMentionNode';

const commonNodes = [MyMentionNode];

export function MentionEditor({initialJSON}) {
  return (
    <>
      <RichTextEditor
        label="Message"
        defaultValue={initialJSON}
        nodes={commonNodes}
        plugins={<MyMentionPlugin />}
      />
      <RichTextView value={initialJSON} nodes={commonNodes} />
    </>
  );
}

Custom Plugins

Any Lexical plugin can be injected via the plugins prop. The toolbar itself is a plugin:

import {
  RichTextEditor,
  RichTextEditorToolbar,
} from '@astryxdesign/lab';

export function ArticleEditor() {
  return (
    <RichTextEditor
      label="Article"
      plugins={<RichTextEditorToolbar />}
    />
  );
}

Markdown Shortcuts

Control markdown typing behavior:

// Disable entirely
<RichTextEditor hasMarkdownShortcuts={false} />

// Or supply custom transformers
<RichTextEditor transformers={myCustomTransformers} />

Toolbar Customization

RichTextEditorToolbar in RichTextEditorToolbar.tsx exposes granular control through props.

Toolbar Props Reference

Prop Type Purpose
headingLevels Array<1 | 2 | 3 | 4 | 5 | 6> Which heading buttons appear
hasLink boolean Enable/disable link button
promptForUrl (selectedText?: string) => string | Promise<string> Replace window.prompt for URL input
linkOpensInNewTab boolean Control target="_blank" and rel attributes
endContent ReactNode Inject custom controls after divider

Injecting Custom Toolbar Controls

Add an AI button or any custom UI:

import {
  RichTextEditor,
  RichTextEditorToolbar,
} from '@astryxdesign/lab';
import {Toolbar, Button} from '@astryxdesign/core';

function AIInsertButton() {
  return (
    <Button
      size="sm"
      onPress={() => alert('AI generated snippet!')}
    >
      AI
    </Button>
  );
}

export function AIEnhancedEditor() {
  return (
    <RichTextEditor
      label="AI-enabled"
      plugins={
        <RichTextEditorToolbar
          endContent={<AIInsertButton />}
        />
      }
    />
  );
}
<RichTextEditor
  label="Plain"
  plugins={<RichTextEditorToolbar hasLink={false} />}
/>
import {Dialog} from '@astryxdesign/core';

function UrlPrompt(selectedText) {
  // Replace with your Dialog component
  return window.prompt('Enter link URL', selectedText);
}

export function CustomLinkEditor() {
  return (
    <RichTextEditor
      label="Links"
      plugins={
        <RichTextEditorToolbar promptForUrl={UrlPrompt} />
      }
    />
  );
}

Icon Theming Without Forking

Register custom glyphs through the core icon registry. The toolbar uses richtext:* keys:

import {registerIcons} from '@astryxdesign/core/Icon';
import {BoldIcon} from './MyIcons';

registerIcons({
  richtext: {bold: <BoldIcon />},
});

// All subsequent toolbars use the custom bold icon

See RichTextEditorToolbar.tsx for the full icon key list (richtext:bold, richtext:italic, richtext:link, etc.).


Accessibility Customization

Fine-tune screen reader behavior:

  • tabEscapeHint: Override or suppress the "Escape then Tab" hint
  • hasAutoFocus: Auto-focus on mount
<RichTextEditor
  label="Notes"
  hasAutoFocus
  tabEscapeHint="Press Escape then Tab to leave editor"
/>

Label, description, and status announcements are wired automatically.


Key Source Files

File Purpose
packages/lab/src/RichTextEditor/RichTextEditor.tsx Core component, props, imperative API
packages/lab/src/RichTextEditor/RichTextEditorToolbar.tsx Toolbar implementation, icon registry integration
packages/lab/src/RichTextEditor/RichTextView.tsx Read-only state renderer
packages/lab/src/RichTextEditor/editorNodes.ts Default node list (headings, lists, link, code)
packages/lab/src/RichTextEditor/editorTheme.ts Shared StyleX theme tokens
packages/lab/src/RichTextEditor/RichTextEditor.doc.mjs Public API documentation

Summary

  • Visual styling: Use size, width, xstyle, and status props
  • Custom nodes: Pass nodes array to both RichTextEditor and RichTextView
  • Toolbar control: Configure via RichTextEditorToolbar props or inject plugins
  • Custom controls: Use endContent prop for additional buttons
  • Icon theming: Override via registerIcons({ richtext: {...} })
  • Accessibility: Adjust tabEscapeHint and hasAutoFocus as needed

The Astryx Rich Text Editor's composable architecture lets you adapt it to product requirements while maintaining design system consistency and full accessibility support.


Frequently Asked Questions

How do I add custom formatting buttons to the Astryx Rich Text Editor?

Use the endContent prop on RichTextEditorToolbar to inject any React element after the standard buttons. This renders after an automatic divider, keeping your custom controls visually separated from built-in formatting options.

Can I use custom Lexical nodes with the Astryx editor?

Yes. Pass your node classes to the nodes prop on both RichTextEditor and RichTextView to ensure proper serialization and round-tripping. The default nodes are defined in editorNodes.ts and merged with your custom array.

Provide a promptForUrl function to RichTextEditorToolbar. This receives the selected text and must return a URL string or Promise. You can integrate any modal system—the example above uses a placeholder window.prompt that you'd replace with your Dialog component.

Where are the toolbar icons defined and how do I override them?

Icons are keyed under richtext:* in Astryx's core icon registry. Import registerIcons from @astryxdesign/core/Icon and pass an object mapping keys like bold, italic, or link to your custom components. Changes apply globally to all subsequent toolbar renders.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →