HTML Editor & Preview
A WYSIWYG rich text editor with full formatting — inline marks, colors, fonts, alignment, lists, tables, images, and a safe HTML preview renderer. Theme-aware and accessible by default.
Basic Usage
Rich Text Editor
Full WYSIWYG editing with inline, block, color, font, alignment, list, table, and link/image support.
HTML Rich Text Editor
A WYSIWYG editor with the full range of rich formatting: underline, strikethrough, inline code, highlighted text, superscript, subscript.
Text colors & fonts
You can change text color, background color, font size, and font family on any selection.
Lists
- Nested unordered lists
- Items with inline formatting
- Even deeper
- Multiple levels supported
- Ordered
- Numbered
- Sequences
- Task lists with checkboxes
- Click the box to toggle
Alignment
Left aligned paragraph.
Center aligned paragraph.
Right aligned paragraph.
Justified paragraph that stretches across the full width when there is enough content to demonstrate the justification behavior of text blocks.
Blockquote
"WYSIWYG means what you see is what you get." — every rich text editor ever.
Code block
const editor = new HtmlEditor();
editor.mount('#root');
Table
| Feature | Inline | Block |
|---|---|---|
| Headings | — | yes |
| Colors | yes | yes |
| Tables | — | yes |
Try any of the toolbar actions or keyboard shortcuts — Ctrl+B, Ctrl+I, Ctrl+U, Ctrl+K, Ctrl+Alt+1..6.
HTML Rich Text Editor
A WYSIWYG editor with the full range of rich formatting: underline, strikethrough, inline code, highlighted text, superscript, subscript.
Text colors & fonts
You can change text color, background color, font size, and font family on any selection.
Lists
- Nested unordered lists
- Items with inline formatting
- Even deeper
- Multiple levels supported
- Ordered
- Numbered
- Sequences
- Task lists with checkboxes
- Click the box to toggle
Alignment
Left aligned paragraph.
Center aligned paragraph.
Right aligned paragraph.
Justified paragraph that stretches across the full width when there is enough content to demonstrate the justification behavior of text blocks.
Blockquote
"WYSIWYG means what you see is what you get." — every rich text editor ever.
Code block
const editor = new HtmlEditor();
editor.mount('#root');
Table
| Feature | Inline | Block |
|---|---|---|
| Headings | — | yes |
| Colors | yes | yes |
| Tables | — | yes |
Try any of the toolbar actions or keyboard shortcuts — Ctrl+B, Ctrl+I, Ctrl+U, Ctrl+K, Ctrl+Alt+1..6.
import { HtmlEditor } from 'fluxo-ui';
const [value, setValue] = useState('<h1>Hello</h1>');
<HtmlEditor
value={value}
onChange={setValue}
defaultView="split"
maxHeight={520}
/>View Modes
Edit / Split / Preview
Fully controlled view mode — wire it to your own UI if you want external toggles.
View Modes
Switch between Edit, Split, and Preview from the top-right toolbar.
Sample content
Inline: bold, italic, strike, inline code, and a link.
- Edit-only hides the preview pane
- Split shows both side-by-side (tabs on mobile)
- Preview hides the editor entirely
Each view can be controlled externally via the
viewprop.
View Modes
Switch between Edit, Split, and Preview from the top-right toolbar.
Sample content
Inline: bold, italic, strike, inline code, and a link.
- Edit-only hides the preview pane
- Split shows both side-by-side (tabs on mobile)
- Preview hides the editor entirely
Each view can be controlled externally via the
viewprop.
const [view, setView] = useState<EditorViewMode>('split');
<HtmlEditor
value={value}
onChange={setValue}
view={view}
onViewChange={setView}
/>Preview Only
Preview Only
Render sanitized HTML without the editor — ideal for displaying user-generated content.
Preview-only Renderer
Use HtmlPreview when you only need to render sanitized HTML — comments, blog posts, or read-only docs.
What this renderer supports
Inline: bold, italic, underline, strike, code, highlight, sup, sub, and links.
Colors: blue, red, highlighted.
Lists
- First top-level item
- Second with nested list
- Nested bullet
- Another nested bullet
Blockquote
Blockquotes can span multiple lines and contain inline formatting.
Code block
import { HtmlPreview } from 'fluxo-ui';
<HtmlPreview value={html} />
Table
| Left | Center | Right |
|---|---|---|
| one | two | three |
| short | long | text |
Dangerous scripts and javascript: URLs are automatically stripped.
<p>Safe paragraph</p><script>alert('xss')</script><p><a href="javascript:alert(1)">click</a></p><p onclick="alert(1)">event handler attempt</p>import { HtmlPreview } from 'fluxo-ui';
<HtmlPreview value={html} openLinksInNewTab />Custom Toolbar
Minimal Toolbar
Use the built-in minimal preset for simple comment boxes.
Custom Toolbars
Configure exactly which formatting buttons appear. Pass toolbar with any subset of actions, or use the built-in MINIMAL_HTML_TOOLBAR.
- bold, italic, underline,
strike - Link to docs
Quotes still work even when the toolbar is hidden — all keyboard shortcuts are always available.
Custom Selection
Pass an explicit list of toolbar actions in your preferred order.
Custom Toolbars
Configure exactly which formatting buttons appear. Pass toolbar with any subset of actions, or use the built-in MINIMAL_HTML_TOOLBAR.
- bold, italic, underline,
strike - Link to docs
Quotes still work even when the toolbar is hidden — all keyboard shortcuts are always available.
No Toolbar
Disable the toolbar entirely — keyboard shortcuts still work.
Custom Toolbars
Configure exactly which formatting buttons appear. Pass toolbar with any subset of actions, or use the built-in MINIMAL_HTML_TOOLBAR.
- bold, italic, underline,
strike - Link to docs
Quotes still work even when the toolbar is hidden — all keyboard shortcuts are always available.
import { HtmlEditor, MINIMAL_HTML_TOOLBAR } from 'fluxo-ui';
<HtmlEditor toolbar={MINIMAL_HTML_TOOLBAR} />
<HtmlEditor
toolbar={['bold', 'italic', 'underline', 'divider', 'h2', 'h3', 'divider', 'textColor', 'link', 'quote']}
/>
<HtmlEditor toolbar={false} />Image Upload — Immediate
Immediate Upload Strategy
Every selected image uploads right away and the final URL is inserted into the HTML.
Immediate Upload
Click the image button in the toolbar, or paste / drop an image directly into the editor.
The editor calls your uploadImage callback immediately and inserts the final URL once it resolves.
Things to try
- Click the image toolbar button and choose Upload
- Paste an image from your clipboard
- Drag an image file from your desktop onto the editor
Already-hosted images work too — paste the URL via the From URL tab in the image dialog.
<HtmlEditor
value={value}
onChange={setValue}
uploadStrategy="immediate"
uploadImage={async (file) => {
const url = await myUploader(file);
return url;
}}
maxImageSize={5 * 1024 * 1024}
acceptedImageTypes={['image/png', 'image/jpeg', 'image/webp']}
/>Image Upload — Deferred
Deferred Upload Strategy
Images are inserted as blob URLs immediately for instant preview, then uploaded only when you flush.
Deferred Upload (Flush on Submit)
Drop or paste images — they appear instantly via local blob: URLs. When you click Submit, the editor flushes all pending uploads via flushUploads() and replaces the blob URLs with the real ones.
Workflow
- Drop or paste any image into the editor
- Keep editing — images show up immediately
- Click Submit to run all uploads and get the final HTML
Use this strategy when you don't want to upload until the user actually commits their content.
const editorRef = useRef<HtmlEditorHandle>(null);
<HtmlEditor
ref={editorRef}
value={value}
onChange={setValue}
uploadStrategy="deferred"
uploadImage={async (file) => await myUploader(file)}
/>
<Button
onClick={async () => {
const finalHtml = await editorRef.current?.flushUploads();
submitForm(finalHtml);
}}
>
Submit
</Button>Read-only
Read-only Editor
All toolbar actions and keyboard shortcuts are disabled.
Read-only Mode
The editor can be rendered in read-only mode — content remains selectable, but the toolbar is disabled.
What you can see
Inline formatting still renders: bold, italic, underline, strike, inline code, and links.
- Useful for displaying the editor with its chrome intact
- Prevents any formatting changes
- Still allows copy to clipboard
Read-only doesn't mean "hidden". It means "visible but immutable."
Read-only Mode
The editor can be rendered in read-only mode — content remains selectable, but the toolbar is disabled.
What you can see
Inline formatting still renders: bold, italic, underline, strike, inline code, and links.
- Useful for displaying the editor with its chrome intact
- Prevents any formatting changes
- Still allows copy to clipboard
Read-only doesn't mean "hidden". It means "visible but immutable."
<HtmlEditor value={value} readOnly defaultView="split" />Import
import { HtmlEditor, HtmlPreview } from 'fluxo-ui';
import type { HtmlEditorProps, HtmlEditorHandle, HtmlPreviewProps } from 'fluxo-ui';HtmlEditor Props
valuestringControlled HTML value.
valuestringControlled HTML value.
defaultValuestringInitial value when uncontrolled.
defaultValuestringInitial value when uncontrolled.
onChange(value: string) => voidCalled whenever the HTML changes.
onChange(value: string) => voidCalled whenever the HTML changes.
placeholderstring"'Start writing...'"Placeholder shown when empty.
placeholderstring"'Start writing...'"Placeholder shown when empty.
readOnlyboolean"false"Disable all editing while keeping the chrome.
readOnlyboolean"false"Disable all editing while keeping the chrome.
disabledboolean"false"Fully disable the editor.
disabledboolean"false"Fully disable the editor.
minHeightstring | number"'320px'"Minimum height of the editor body.
minHeightstring | number"'320px'"Minimum height of the editor body.
maxHeightstring | numberOptional max height (scrolls beyond).
maxHeightstring | numberOptional max height (scrolls beyond).
view'edit' | 'split' | 'preview'Controlled view mode.
view'edit' | 'split' | 'preview'Controlled view mode.
defaultView'edit' | 'split' | 'preview'"'edit'"Initial view when uncontrolled.
defaultView'edit' | 'split' | 'preview'"'edit'"Initial view when uncontrolled.
onViewChange(view: EditorViewMode) => voidCalled when the user toggles views.
onViewChange(view: EditorViewMode) => voidCalled when the user toggles views.
allowedViewsEditorViewMode[]Restrict which view modes appear in the switcher.
allowedViewsEditorViewMode[]Restrict which view modes appear in the switcher.
toolbarHtmlToolbarItem[] | false"DEFAULT_HTML_TOOLBAR"Toolbar configuration or false to hide.
toolbarHtmlToolbarItem[] | false"DEFAULT_HTML_TOOLBAR"Toolbar configuration or false to hide.
showToolbarboolean"true"Hide the toolbar entirely.
showToolbarboolean"true"Hide the toolbar entirely.
showStatusBarboolean"true"Show word/char count footer.
showStatusBarboolean"true"Show word/char count footer.
showWordCountboolean"true"Toggle word count in the status bar.
showWordCountboolean"true"Toggle word count in the status bar.
uploadImage(file: File) => Promise<string>Async upload callback — must resolve with the final URL.
uploadImage(file: File) => Promise<string>Async upload callback — must resolve with the final URL.
uploadStrategy'immediate' | 'deferred'"'immediate'"Upload immediately or defer to flushUploads().
uploadStrategy'immediate' | 'deferred'"'immediate'"Upload immediately or defer to flushUploads().
maxImageSizenumberMaximum file size in bytes.
maxImageSizenumberMaximum file size in bytes.
acceptedImageTypesstring[]Array of MIME types accepted for upload.
acceptedImageTypesstring[]Array of MIME types accepted for upload.
onUploadError(message: string, file?: File) => voidCalled when validation or upload fails.
onUploadError(message: string, file?: File) => voidCalled when validation or upload fails.
sanitize(html: string) => stringCustom sanitizer for paste and preview (defaults to built-in allow-list).
sanitize(html: string) => stringCustom sanitizer for paste and preview (defaults to built-in allow-list).
sanitizerConfigHtmlSanitizerConfigOverride the allow-list used by the built-in sanitizer.
sanitizerConfigHtmlSanitizerConfigOverride the allow-list used by the built-in sanitizer.
openLinksInNewTabboolean"true"Open preview links with target="_blank".
openLinksInNewTabboolean"true"Open preview links with target="_blank".
spellCheckboolean"true"Enable browser spellcheck on the editable surface.
spellCheckboolean"true"Enable browser spellcheck on the editable surface.
autoFocusboolean"false"Focus the editor on mount.
autoFocusboolean"false"Focus the editor on mount.
ariaLabelstring"'Rich text editor'"Accessible label for the editor.
ariaLabelstring"'Rich text editor'"Accessible label for the editor.
HtmlPreview Props
valuestringHTML source to render.
valuestringHTML source to render.
sanitize(html: string) => stringCustom sanitizer — defaults to the built-in allow-list.
sanitize(html: string) => stringCustom sanitizer — defaults to the built-in allow-list.
sanitizerConfigHtmlSanitizerConfigOverride the allow-list used by the built-in sanitizer.
sanitizerConfigHtmlSanitizerConfigOverride the allow-list used by the built-in sanitizer.
openLinksInNewTabboolean"true"Add target="_blank" rel="noopener" to links.
openLinksInNewTabboolean"true"Add target="_blank" rel="noopener" to links.
emptyFallbackReact.ReactNodeShown when value is empty.
emptyFallbackReact.ReactNodeShown when value is empty.
Features
True WYSIWYG
contentEditable-based rich text surface — you see formatting as you type.
Complete formatting
Bold, italic, underline, strike, code, sup/sub, highlight, colors, fonts, alignment, tables, lists.
Safe by default
Built-in HTML sanitizer strips scripts, event handlers, and dangerous URL schemes.
Image uploads
Immediate or deferred uploads with paste, drop, and URL input flows.
Configurable toolbar
Pick any subset of 30+ toolbar actions, or hide the toolbar entirely.
Theme-aware
All colors flow from --eui-* variables — auto-supports light, dark, and brand themes.