Appearance
Rich Text Editor โ
A WYSIWYG field built on SunEditor v3. <mono-rich-text-editor> is a mono form field โ label, helper and validation chrome, size / color / variant, monoForm binding โ whose input is a SunEditor instance. The value is HTML.
ts
import 'mono-helper/ui/rich-text-editor'vue
<mono-rich-text-editor
label="Description"
:model-value="html"
@change="html = $event.detail.modelValue"
/>Install
SunEditor is an optional peer โ it isn't bundled, so nothing pays for it unless a page renders the editor.
sh
pnpm add suneditor@^3.3.3If it's missing, the element renders an inline message naming the package and the command (and emits error) rather than failing silently. The editor's own stylesheet (suneditor/css/editor) is loaded by the element the first time one renders; set load-css="false" if your app ships it itself.
Plain JS, not a wrapper
There is no published web-component or Vue build of SunEditor โ the "Web Components" example on suneditor.com is a fifteen-line custom element around the core package, and suneditor-vue is an empty repository. This element wraps the core suneditor package the same way that example does (a light-DOM mount, created on connect and destroyed on disconnect) and adds what it lacks: lazy loading, v-model and monoForm, the mono chrome and theming.
Basic โ
model-value is the HTML. change fires when SunEditor commits an edit, input on every keystroke โ both carry the textarea's detail shape (modelValue, oldValue, name). An emptied editor reports '', never SunEditor's <p><br></p>, so required means what it says.
Toolbar โ
toolbar takes a preset name, a SunEditor buttonList (bind with .prop), its JSON, or a string of button names where | separates groups.
| preset | buttons |
|---|---|
basic | undo/redo ยท bold, underline, italic, strike ยท bulleted, numbered, link ยท remove format |
standard (default) | + block style, font, size ยท font & background colour ยท align, indent ยท table, link, image, video ยท blockquote, code block, hr ยท code view, full screen |
full | every built-in that needs no configuration |
default | SunEditor's own list โ no plugin buttons |
Buttons that name a plugin which is not loaded are dropped from the list rather than rejected, so trimming plugins never breaks a preset.
Plugins โ
Most toolbar buttons are SunEditor plugins. plugins defaults to auto: every built-in that works without configuration, loaded on demand from suneditor/plugins as one chunk your bundler owns. Trim it and the download shrinks:
vue
<script setup>
import { ref, onMounted } from 'vue'
const plugins = ref()
onMounted(async () => {
const { font, image, link } = await import('suneditor/plugins')
plugins.value = [font, image, link]
})
</script>
<mono-rich-text-editor v-if="plugins" :plugins.prop="plugins" />
<mono-rich-text-editor plugins="font, link, table" />
<mono-rich-text-editor plugins="none" />Import plugins on the client
SunEditor's modules read window when they are imported, so a static import { font } from 'suneditor/plugins' in a component that is server-rendered (Nuxt, VitePress) throws before the page exists. Import them from onMounted (or a client-only module) as above โ or use the name form, which mono loads on demand and is server-safe as written.
Left out of auto because each needs configuration SunEditor warns about otherwise: imageGallery / videoGallery / audioGallery / fileGallery / fileBrowser (a server url), fileUpload (an uploadUrl), template and layout (a list), math (KaTeX / MathJax), exportPDF (an API). Ask for them by name and pass their options through options.
Form โ
Bind it like any other field โ :control-form and key-form โ and the controller owns the value, runs the rules and paints the state. required fails on an emptied editor; min / max measure the HTML string.
Modes โ
mode places the toolbar: classic (default) above the content, classic:bottom below, inline / inline:bottom shown only while the editor has focus, balloon / balloon-always floating over the selection.
Sizes โ
size scales the toolbar, its icons and the editing font off the --theme-control-font-* ladder. The height is the editing area's โ height (default auto), min-height (default 10rem) and max-height size it; width / min-width / max-width size the field.
Colors โ
Pick a colour and every variant takes it. The frame is the textarea's: outlined, filled, underlined, and color retargets the focus ring, the active toolbar state and links inside the content.
States โ
disabled turns SunEditor's toolbar and editing off; readonly shows the content and refuses edits. error-message / success-message / validation-state paint the frame like the other fields. max-length caps the character count and shows SunEditor's counter (char-counter shows it without a cap).
Language and direction โ
language takes a SunEditor language code (ko, ja, zh-CN, pt-BR, โฆ) and loads that pack on demand, or a language object bound with .prop. text-direction="rtl" flips the content (the host's own dir attribute is left to the page).
Options and the instance โ
options (bind with .prop) is SunEditor's InitOptions, merged last over everything the props produce โ upload URLs, counters, whitelists, events. A handler under options.events runs before mono's own for the same event and can still return false to cancel SunEditor's default.
The element exposes the instance and the operations a form usually needs: editor (the SunEditor instance, null until ready), getHtml(), setHtml(html), insertHtml(html), getText(), isEmpty(), focus(), blur(), codeView(), fullScreen(). Everything else is on editor.$ โ editor.$.html, editor.$.viewer, editor.$.ui.
Displaying saved HTML โ
The editor's stylesheet is for the editor. Content shown elsewhere โ a detail page, a print view โ needs SunEditor's content sheet and the sun-editor-editable class so tables, alignment and code blocks look as they did when written:
ts
import 'suneditor/css/contents'vue
<div class="sun-editor-editable" v-html="html"></div>Theming โ
The frame is the textarea's box: every knob falls back to the matching --mono-textarea-* one, so a flavour that retunes the textarea retunes this field (lyra's square, luma's pill, sera's underline) with nothing set. SunEditor's UI is built on --se-* custom properties, and the element remaps the ones a theme is made of onto the Basecoat tokens โ the focus colour for its active state and links, --foreground / --muted-foreground for ink, --border for hairlines, the mode's hover washes, and the select panel's popover (--mono-select-dropdown-*) for its dropdowns and the modal layer it parks on <body>. Dark mode is the same tokens flipped. Nothing reaches into SunEditor's selectors except to flatten the frame it paints around itself, which the mono frame replaces, and to give the toolbar its muted band.
| variable | default | purpose |
|---|---|---|
--mono-rich-text-editor-primary โฆ -info | --mono-textarea-* โ --ring, --muted-foreground, --success, --destructive, --warning, --info | the accent per color โ the focus ring, SunEditor's active state and links |
--mono-rich-text-editor-text / -placeholder / -muted | --foreground / --muted-foreground / --muted-foreground | inks |
--mono-rich-text-editor-border (or -border-color / -rest-border) | --mono-textarea-* โ --input | the frame edge |
--mono-rich-text-editor-bg (or -surface) | --mono-textarea-* โ --mono-mode-surface | the frame and editing surface |
--mono-rich-text-editor-shadow / -radius / -border-width | --mono-shadow-xs / --mono-radius-md / --mono-border-width | the frame |
--mono-rich-text-editor-ring-color / -ring-width / -ring-alpha | the color hue / --mono-ring-width / --mono-ring-alpha | the focus ring |
--mono-rich-text-editor-outline-* / -filled-bg / -readonly-bg / -disabled-bg | the textarea's | per-variant and per-state presets |
--mono-rich-text-editor-font-size / -font-<size> / -font-family / -icon-size | --mono-textarea-font-<size> โ --mono-text-sm / inherit / per size | toolbar and content typography |
--mono-rich-text-editor-toolbar-bg | --muted 60% on the surface | the toolbar band |
--mono-rich-text-editor-fullscreen-bg | --background | the surface SunEditor paints when it goes full screen โ its root lifts out of the frame (position: fixed, inline), so the editor takes the page surface itself, opaque, in the mode's colours; the mount carries mono-fullscreen while it lasts |
--mono-rich-text-editor-popover-bg / -color / -radius / -shadow | --mono-select-dropdown-* โ --popover / --popover-foreground / --mono-radius-md / --mono-shadow-md | SunEditor's dropdowns and dialogs |
--mono-rich-text-editor-tooltip-bg / -tooltip-color / -tooltip-radius | --foreground / --background / --mono-tooltip-radius โ --mono-radius-md | every tooltip โ the toolbar's, and the dropdown items' (SunEditor gives those native titles, which no theme can paint; the element rewrites them into data-tooltip + aria-label) โ as Basecoat's [data-tooltip]: bg-foreground text-background text-xs in the page font, at the style's corner (luma / rhea xl, maia 2xl, lyra / sera square) |
--mono-rich-text-editor-min-height | 10rem | default editing-area height |
--mono-rich-text-editor-label-* / -message-* / -gap | the textarea's | label and message type, label / field / footer spacing |
Deprecated and honoured as no-ops until 2.0: --mono-rich-text-editor-focus-rgb, --mono-rich-text-editor-background, --mono-rich-text-editor-underline-glow (the underline is sera's line now), and the --theme-* bridge โ every default reads a Basecoat token.
The editor DOM itself is light DOM in both builds (SunEditor's sheet is global and its modals live on <body>, neither of which a shadow root can adopt), so any --se-* override you set on an ancestor reaches it directly.
Server rendering โ
The chrome (label, frame, messages) renders on the server; the editor is client state and is built on the first client update, exactly like mono-chart and mono-date. Under mono-helper/ui/shadow/rich-text-editor the frame is Declarative Shadow DOM and the editor mount is projected through <slot name="editor">.
Optional dependency โ
suneditor is declared as an optional peerDependency (^3.3.3) and loaded with dynamic imports โ suneditor, suneditor/plugins, suneditor/css/editor, suneditor/langs/* โ the first time an element renders. It is externalized from the build, so dist/ui/rich-text-editor.js stays small and every one of those specifiers resolves against your app's copy, split however your bundler splits them.
Props โ
Import
import { RichTextEditorProps } from 'mono-helper'| Prop | Value | Default | Description |
|---|---|---|---|
modelValue | string | โ | Two-way bound HTML. `''` for an empty editor. |
value | string | โ | HTML (kept in sync with modelValue). |
name | string | โ | Form field name, carried on the events. |
placeholder | string | โ | Placeholder shown while the editor is empty. |
label | string | โ | Text label shown above the editor. |
helperText | string | โ | Helper text shown below the editor. |
validationState | 'warning' | 'default' | 'valid' | 'invalid' | โ | Validation state controlling the frame styling. |
validationMessage | string | โ | Message shown for the current validation state. |
errorMessage | string | โ | Error message that forces the invalid state. |
successMessage | string | โ | Success message that forces the valid state. |
disabled | string | boolean | โ | Disables editing and the toolbar. |
readonly | string | boolean | โ | Read-only: content shows, cannot be edited. |
required | string | boolean | โ | Marks the label and lets `monoForm`'s `required` rule apply. |
size | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | โ | Field size โ scales the toolbar, icons and editing font. |
color | 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'teal' | 'purple' | 'neutral' | 'dark' | โ | Accent colour for focus and active toolbar state. |
variant | 'underlined' | 'outlined' | 'filled' | โ | Frame variant. |
mode | 'inline' | 'classic' | 'balloon' | 'balloon-always' | 'classic:bottom' | 'inline:bottom' | โ | Toolbar placement: `classic` (default), `inline`, `balloon`, `balloon-always`, `classic:bottom`, `inline:bottom`. |
toolbar | string | RichTextEditorButtonList | โ | Toolbar preset (`basic` | `standard` | `full` | `default`) or a SunEditor `buttonList` (bind with `.prop`, or JSON). |
plugins | string | any[] | Record<string, any> | โ | Plugins: `auto` (all built-ins, default), `none`, an array of plugin classes / names, or a name โ class object. |
language | string | Record<string, unknown> | โ | Language: a SunEditor language code (`ko`, `de`, `zh-CN`, โฆ) loaded on demand, or a language object (`.prop`). |
textDirection | 'ltr' | 'rtl' | โ | Text direction of the CONTENT (the host's own `dir` is left to the page). |
charCounter | string | boolean | โ | Show SunEditor's character counter in the status bar. |
maxLength | string | number | โ | Maximum number of characters (`charCounter_max`). |
statusbar | string | boolean | โ | Show the status bar (resize handle, path). Default `true`. |
sticky | string | number | โ | Sticky toolbar offset in px while the page scrolls (`toolbar_sticky`); unset = not sticky. |
loadCss | string | boolean | โ | Whether the element loads SunEditor's own stylesheet (`suneditor/css/editor`) the first time it renders. Default `true`. Set `false` when the app ships it. |
options | RichTextEditorOptions | โ | Raw SunEditor `InitOptions`, merged LAST over everything the props produce. Bind with `.prop`. |
cssClass | string | RichTextEditorCssClass | โ | Class hooks per part. |
width | string | number | โ | Explicit sizing of the whole field. |
height | string | number | โ | Editing area height (SunEditor `height`, default `auto`). |
minWidth | string | number | โ | โ |
maxWidth | string | number | โ | โ |
minHeight | string | number | โ | Editing area minimum height (SunEditor `minHeight`, default `10rem`). |
maxHeight | string | number | โ | โ |
ariaLabel | string | โ | `aria-label` for the editing area (defaults to `label` / `placeholder`). |
visible | boolean | โ | Whether the control is shown. Defaults to `true`. |
visibleType | 'none' | 'invisible' | โ | How it hides when `visible` is `false`. Defaults to `'none'`. |