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 spacingwidth: number (px) or CSS string —width={300}orwidth="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 />}
/>
}
/>
);
}
Disabling Link Button
<RichTextEditor
label="Plain"
plugins={<RichTextEditorToolbar hasLink={false} />}
/>
Custom Link Prompt Dialog
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" hinthasAutoFocus: 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, andstatusprops - Custom nodes: Pass
nodesarray to bothRichTextEditorandRichTextView - Toolbar control: Configure via
RichTextEditorToolbarprops or injectplugins - Custom controls: Use
endContentprop for additional buttons - Icon theming: Override via
registerIcons({ richtext: {...} }) - Accessibility: Adjust
tabEscapeHintandhasAutoFocusas 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.
How do I replace the default link prompt with my own dialog?
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →