@uxf/wysiwyg
v11.122.5
Published
UXF Wysiwyg editor
Downloads
1,208
Readme
@uxf/wysiwyg
Rich-text / WYSIWYG editor for React, built on Plate / Slate. It ships a ready-made WysiwygEditor component, a toolbar, and a set of UXF-styled plugins (headings, marks, lists, links, images, videos, buttons, blockquote, highlight).
When to use
Reach for @uxf/wysiwyg when you need a structured rich-text editor whose value is a JSON document (a Slate/Plate node tree), not an HTML string. The editor is uncontrolled: it is seeded once from initialValue and reports every edit through onChange as a WysiwygContent array.
It is a client component only and depends on @uxf/ui for its visual primitives. For a higher-level CMS content-builder wrapper, see @uxf/cms; use this package directly when you want the standalone editor.
Installation
yarn add @uxf/wysiwygAll Plate/Slate packages and the UXF packages are peer dependencies with pinned versions — install them at the versions declared in package.json:
- UXF:
@uxf/core,@uxf/core-react,@uxf/ui - Plate (
20.7.2):@udecode/plate-basic-marks,@udecode/plate-block-quote,@udecode/plate-break,@udecode/plate-code-block,@udecode/plate-common,@udecode/plate-core,@udecode/plate-floating,@udecode/plate-link,@udecode/plate-list,@udecode/plate-media,@udecode/plate-node-id,@udecode/plate-paragraph,@udecode/plate-reset-node,@udecode/plate-select,@udecode/plate-trailing-block - Slate wrappers:
@udecode/slate(19.8.0),@udecode/slate-react(19.7.1),@udecode/slate-utils(19.7.1) - Slate core:
slate(0.90.0),slate-history(0.86.0),slate-hyperscript(0.77.0),slate-react(0.91.11) react/react-dom(>=18.2.0)
Required CSS
Import the published stylesheet once (e.g. in your global CSS):
@import url("@uxf/wysiwyg/styles/styles.css");This stylesheet uses Tailwind's theme(), @apply (including uxf-typo-* utilities from @uxf/ui) and CSS nesting, so it must be processed by your app's Tailwind/PostCSS pipeline alongside the @uxf/ui Tailwind config.
Required translations
The editor renders its toolbar/labels through useUxfTranslation (@uxf/core-react/translations). Register this package's translations, or you will see raw translation keys instead of text. Merge the default export of @uxf/wysiwyg/translations/translations (locales: cs, en, sk, de) into the translation function you pass to your app's provider:
import wysiwygTranslations from "@uxf/wysiwyg/translations/translations";
import uiTranslations from "@uxf/ui/translations/translations";
import { createDevT } from "@uxf/core-react/translations/create-dev-t";
export const tFunction = createDevT("cs", {
...uiTranslations,
...wysiwygTranslations,
});Wire tFunction through the standard UXF setup (translationFn on UiContextProvider from @uxf/ui/context, or TranslationsProvider from @uxf/core-react/translations).
Quick start
Build the plugin set once, then render WysiwygEditor. createAllPluginsWithUi enables every plugin; its only required option is image (image insertion needs uploadImage + getImageUrl).
"use client";
import { createAllPluginsWithUi, WysiwygContent, WysiwygEditor } from "@uxf/wysiwyg";
import { useState } from "react";
const plugins = createAllPluginsWithUi({
image: {
uploadImage: async (file) => uploadToS3(file), // returns FileResponse
getImageUrl: (file) => resolveUrl(file),
},
});
const INITIAL_VALUE: WysiwygContent = [{ type: "paragraph", id: "p1", children: [{ text: "Hello" }] }];
export function MyEditor() {
const [value, setValue] = useState<WysiwygContent>(INITIAL_VALUE);
return <WysiwygEditor id="my-editor" initialValue={value} onChange={setValue} plugins={plugins} />;
}To enable only some plugins, compose them yourself with createPluginsWithUi:
import { createBoldPluginWithUi, createHeadingsPluginWithUi, createPluginsWithUi } from "@uxf/wysiwyg";
const plugins = createPluginsWithUi([
createHeadingsPluginWithUi({ disabledLevels: [1] }),
createBoldPluginWithUi(),
]);Content model
WysiwygContent is a WysiwygRootBlock[] — a Plate/Slate document, not HTML. Each block carries a type, an optional id, and children:
type WysiwygRootBlock =
| UxfParagraphElement // type: "paragraph"
| UxfHeadingElement // type: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
| UxfBlockQuoteElement // type: "blockquote"
| UxfUnorderedListElement // type: "ul" (li > lic)
| UxfOrderedListElement // type: "ol" (li > lic)
| UxfLinkElement // type: "link" (inline)
| UxfImageElement // type: "image" (void)
| UxfVideoElement // type: "video" (void)
| UxfButtonElement; // type: "button" (void)Inline text (RichText) carries the mark flags bold, italic, underline, code, highlight. onChange always receives the full, current document.
API
Everything below is exported from the package root (@uxf/wysiwyg) unless a deep path is shown. There is no exports map, so deep imports resolve by filesystem.
WysiwygEditor
import { WysiwygEditor } from "@uxf/wysiwyg";
| Prop | Type | Required | Description |
| --- | --- | --- | --- |
| id | string | yes | Unique Plate instance id. Must be unique per editor on the page. |
| initialValue | WysiwygContent \| undefined | yes | Document the editor is seeded with on mount. |
| onChange | (value: WysiwygContent) => void | yes | Called on every edit with the full document. |
| plugins | UxfPlatePlugin[] | yes | Build with createAllPluginsWithUi / createPluginsWithUi. |
| className | string | no | Class on the editor root wrapper. |
| editableProps | TEditableProps<WysiwygContent> | no | Passed to Plate's editable area (placeholder, readOnly, autoFocus, spellCheck, className, …). Defaults: autoFocus: false, readOnly: false, spellCheck: false, localized placeholder. |
| customPluginsToolbarButtons | ReactNode | no | Extra toolbar buttons appended to the built-in ones. |
| toolbarLeftElement | ReactNode | no | Node rendered at the toolbar's left edge. |
| toolbarRightElement | ReactNode | no | Node rendered at the toolbar's right edge. |
Plugin builders
createPluginsWithUi / createAllPluginsWithUi return the UxfPlatePlugin[] you pass to WysiwygEditor. Both inject the base plugins automatically (paragraph, exit-break, reset-node, soft-break, node-id, select-on-backspace, trailing-block), so pass only the feature builders below — not raw Plate plugins.
| Export | Signature | Notes |
| --- | --- | --- |
| createPluginsWithUi | (plugins: Array<WysiwygPlugin \| WysiwygRecursivePlugin<string>>, options?: { overrideByKey?: MyOverrideByKey }) => UxfPlatePlugin[] | Compose a custom subset. |
| createAllPluginsWithUi | (options: CreateAllPluginsOptions) => UxfPlatePlugin[] | Enable every plugin. |
| createHeadingsPluginWithUi | (options?: HeadingsPluginOptions) => WysiwygRecursivePlugin | disabledLevels?: (1..6)[]. |
| createBoldPluginWithUi | () => WysiwygPlugin | |
| createItalicPluginWithUi | () => WysiwygPlugin | |
| createUnderlinePluginWithUi | () => WysiwygPlugin | |
| createCodePluginWithUi | () => WysiwygPlugin | |
| createHighlightPluginWithUi | (color?: CSSProperties["color"]) => WysiwygPlugin | Defaults to twColors.yellow[300]. |
| createBlockquotePluginWithUi | () => WysiwygPlugin | |
| createListPluginWithUi | () => WysiwygRecursivePlugin | Renders ul / ol / li. |
| createLinkPluginWithUi | () => WysiwygPlugin | |
| createImagePluginWithUi | (options: UxfImagePluginOptions) => WysiwygPlugin | See options below. |
| createVideoPluginWithUi | () => WysiwygPlugin | |
| createButtonPluginWithUi | () => WysiwygPlugin | |
CreateAllPluginsOptions:
| Key | Type | Required | Description |
| --- | --- | --- | --- |
| image | UxfImagePluginOptions | yes | Image plugin options (pass {} to enable without upload handlers). |
| headings | HeadingsPluginOptions | no | e.g. { disabledLevels: [1] }. |
| highlightColor | CSSProperties["color"] | no | Highlight mark color. |
UxfImagePluginOptions (extends Plate's MediaPlugin):
| Key | Type | Description |
| --- | --- | --- |
| uploadImage | (file: File) => Promise<FileResponse> | Upload handler; returns a FileResponse (@uxf/core/types). |
| getImageUrl | (file: FileResponse) => string | Resolves the display URL for an uploaded file. |
| disableUploadOnPasteImageUrl | boolean | Disable auto-upload when an image URL is pasted. |
| disableUploadOnPasteImage | boolean | Disable auto-upload when an image blob is pasted. |
Hooks
import { ... } from "@uxf/wysiwyg"; — thin, typed wrappers over Plate's editor hooks, bound to WysiwygContent / UxfEditor:
useUxfPlateEditorRef, useUxfEditorRef, useUxfEditorState, useUxfPlateEditorState, useUxfPlateSelectors, useUxfPlateActions, useUxfPlateStates, and a re-export of useSelected (from slate-react).
Utilities
Editor helpers for building custom plugins/toolbar buttons: getUxfEditor, getPluginOptions, getPluginType, someNode, toggleNodeType, focusEditor, isRangeInSingleText, isMarkActive, toggleMark, isPluginEnabled, isSomeOfPluginsEnabled, getSelectedNode, getActiveElement, removeElement, insertVoid, removeSelectedNode.
Serialize a document to plain text (deep import, not in the root barrel):
import { serializeToPlaintext } from "@uxf/wysiwyg/serializers/serialize-to-plaintext";
serializeToPlaintext(value); // keepIndentation defaults to true (joins blocks with "\n")Types
The full node model and editor types are re-exported, including: WysiwygContent, WysiwygRootBlock, RichText, the element interfaces (UxfParagraphElement, UxfHeadingElement, UxfBlockQuoteElement, UxfLinkElement, UxfImageElement, UxfVideoElement, UxfButtonElement, UxfUnorderedListElement, UxfOrderedListElement, LiElement, LicElement), plus UxfEditor, UxfPlatePlugin, WysiwygPlugin, WysiwygRecursivePlugin, and the render prop/component types (RenderElementProps, ElementUiComponent, RenderLeafProps, LeafUiComponent, RenderAfterEditable, MyOverrideByKey, UiComponents).
Gotchas
- Client component only.
WysiwygEditoris marked"use client"and renders aLoaderuntil it has mounted on the client; it cannot render on the server. - Uncontrolled value.
initialValueseeds the document once on mount. ChanginginitialValuelater does not reset the editor — remount it (e.g. via a changed Reactkey) to load a different document. Read the live value fromonChange. - Build plugins with the provided factories. Pass only the results of
createPluginsWithUi/createAllPluginsWithUitoplugins; they inject the required base plugins. Do not hand-assemble raw Plate plugin arrays. - CSS is required and Tailwind-dependent. Without
@uxf/wysiwyg/styles/styles.cssthe editor is unstyled; the file relies on your Tailwind/PostCSS setup and@uxf/ui'suxf-typo-*utilities. - Translations are required. Register
@uxf/wysiwyg/translations/translations, otherwise labels/tooltips render as raw keys. - Pinned peers. Plate/Slate and
@uxf/*peer versions are pinned exactly (seepackage.json); mismatched versions will not work.
Links
- Plate documentation — the underlying editor framework.
- Slate documentation — the underlying document model.
- Package homepage: gitlab.com/uxf-npm/wysiwyg
