HyMarkdownEditor
Usage examples on this page are written for Lit / plain HTML (
<hy-markdown-editor>). The same component ships asHyMarkdownEditorin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
Markdown editor with a CodeMirror 6 source pane and a unified-pipeline preview, presented either as a single pane with a Source ⇄ Preview toggle or as a side-by-side split.
The view prop picks the layout strategy. The default view="single" shows one pane with a Source ⇄ Preview eye toggle. view="auto" makes it responsive — a container query renders source + preview side-by-side when the editor is wide and collapses back to the single-pane toggle when it is narrow. view="split" pins the side-by-side layout with a draggable hy-splitter handle so the panes can be resized. In any split layout the preview refreshes live (debounced) as you type.
Fenced code in the preview is syntax-highlighted for css, diff, html, javascript (js), json, jsonc, jsx, markdown (md), scss, shellscript (bash, sh), svelte, tsx, typescript (ts), vue and yaml (yml), in a light / dark theme pair. The highlighter and each language's grammar load the first time a document uses them, so prose costs nothing extra; a fence in any other language, or none, renders as plain code.
When to use
- Long text that needs headings, lists, links or emphasis: a description, notes, documentation.
- Writing that people want to check as it will look, with the preview.
Do
- Set
maxlengthwhen the text has a limit, and say the limit beside the field.
When not to use
- A short plain value such as a name:
hy-text-input. Several lines of plain text:hy-text-area. - Showing stored markdown without editing it: render it as HTML on the page. Use
readonlyonly when the source itself must stay visible. - Files that are not part of the text:
hy-file-upload.
Examples
Basic (single pane with Source ⇄ Preview toggle)
<hy-markdown-editor label="Description" placeholder="Type Markdown..."></hy-markdown-editor>Responsive (auto split when wide, eye toggle when narrow)
<hy-markdown-editor view="auto" label="Notes" value="# Live preview"></hy-markdown-editor>Pinned side-by-side split
<hy-markdown-editor view="split" label="Notes" value="# Live preview"></hy-markdown-editor>Mode-locked preview
<hy-markdown-editor mode="preview" no-mode-toggle value="# Hello"></hy-markdown-editor>Autofocus inside a dialog (overlay-discovery contract)
<hy-dialog>
<hy-markdown-editor autofocus autoselect value="# Existing"></hy-markdown-editor>
</hy-dialog>API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
editorView | — | EditorView | null | — | Access the live CM6 view. Public for tests / advanced consumers (e.g. wiring custom keymap extensions). Returns null before the widget has mounted (SSR, pre-firstUpdated). |
uniqueId | unique-id | string | '' | — |
value | value | string | '' | — |
mode | mode | 'source' | 'preview' | 'source' | — |
view | view | 'auto' | 'single' | 'split' | 'single' | Pane layout strategy — single (default) | auto | split. Reflected so the container-query split rules in the stylesheet can branch on it. See MarkdownEditorView. The mode toggle still governs which pane shows in single-pane layouts; in a split layout both panes are visible and the toggle is hidden. |
noModeToggle | no-mode-toggle | boolean | false | — |
placeholder | placeholder | string | '' | — |
label | label | string | '' | — |
disabled | disabled | boolean | false | — |
autofocus | autofocus | boolean | false | Focus the source pane on first connect. This is primarily the marker overlay containers (dialog, drawer, popover) discover on show; secondarily a page-load focus shortcut with the same UX caveats as native HTML autofocus. |
autoselect | autoselect | boolean | false | Pre-select the source-pane contents after autofocus (or after .focus() followed by .select()). Mirrors text-area / text-input. |
name | name | string | '' | Form-field name. Set when the editor participates in a <form>. |
required | required | boolean | false | Marks the editor as required for form validation. |
readonly | readonly | boolean | false | Read-only: source pane is non-editable but still focusable. The format tools stay in place — inert, out of the accessibility tree and painted quiet — so the editor keeps its shape; the mode toggle keeps working. |
spellcheck | spellcheck | boolean | true | Override the default spellcheck="true" on the source pane. Note this shadows the inherited HTMLElement.spellcheck boolean — the inner textarea reads from this property only. |
maxlength | maxlength | number | null | null | Maximum character length of the markdown source. |
minlength | minlength | number | null | null | Minimum character length of the markdown source. |
uploadImage | — | ImageUploadHandler | undefined | — | Host-provided async upload hook. When set, the image dialog gains a drop/pick upload affordance (an embedded hy-file-upload); the host wires this to its asset store (e.g. Supabase Storage behind the Clerk session). When absent, the image dialog is URL-only. Library-side property, not an attribute — the consuming app injects the function. |
acceptedImageTypes | accepted-image-types | string | 'image/*' | accept filter forwarded to the embedded hy-file-upload. Defaults to image/*. |
maxImageBytes | max-image-bytes | number | null | null | Max upload size in bytes, forwarded to hy-file-upload's max-size. |
strings | — | Partial<MarkdownEditorStrings> | {} | The editor's built-in strings (toolbar labels, heading menu, mode toggle and announcements, pane names, insert-image dialog, upload error). Set the keys you translate; the rest keep their English defaults (DEFAULT_MARKDOWN_EDITOR_STRINGS). A JS property, not an attribute. Use for internationalization. |
imageUploadStrings | — | Partial<FileUploadStrings> | {} | The built-in strings of the image dialog's upload zone (call to action, drop line, accepted types, progress, remove buttons, rejection messages), forwarded to the embedded `hy-file-upload`'s `strings`. Set the keys you translate; the rest keep their English defaults (`DEFAULT_FILE_UPLOAD_STRINGS`). A JS property, not an attribute. Use for internationalization. |
Events
| Event | Detail | Description |
|---|---|---|
input | — | Detail { value: string }. Fired on every keystroke. |
change | — | Detail { value: string }. Fired on blur after the value changed. |
mode-change | — | Detail { mode, previous }. Fired when the mode-toggle button is clicked. Not fired for programmatic el.mode = ... assignment. |
image-upload-start | — | Detail { file: File }. Fired when a picked/dropped image starts uploading via the uploadImage callback. |
image-upload-error | — | Detail { file: File, message: string }. Fired when the uploadImage callback rejects. |
Slots
| Slot | Description |
|---|---|
extra-tools | Extra toolbar items, rendered in the toolbar's trailing cluster next to the mode toggle. Use for primary actions anchored to the editing surface (e.g. Save / Cancel on an editable document). Only visible while the toolbar is — source / split mode; hidden in single-pane preview. React mirror: the extraTools prop. |
CSS Parts
| Part | Description |
|---|---|
base | Outer wrapper. |
toolbar | Top bar holding the formatting controls and mode toggle. |
format-tools | Leading group of formatting controls (source mode only). |
mode-toggle | The Source ⇄ Preview toggle button. |
body | The editing / preview surface. |
source | The source pane (CodeMirror 6 mount). |
preview | The preview pane. |
preview-skeleton | Loading placeholder shown while the cold preview pipeline loads. |
live-region | Screen-reader announcement region for mode pivots. |
image-dialog | The insert-image dialog (hy-dialog). |
CSS Custom Properties
The documented hooks. Every override hook the component reads — one per token it paints — is listed in @whitespaceux/harmony/theme-api.json, and @whitespaceux/harmony/component-bindings.json (the Harmony MCP get-bindings tool) says which part and state each one paints.
| Property | Default | Description |
|---|---|---|
--hy-markdown-editor-min-height | — | Minimum height of the body. Defaults to 12rem. |
--hy-markdown-editor-padding-form-md | var(--hy-padding-form-md) | Vertical padding inside source / preview. |
--hy-markdown-editor-padding-layout-lg | var(--hy-padding-layout-lg) | Horizontal padding inside source / preview. |
--hy-markdown-editor-padding-form-sm | var(--hy-padding-form-sm) | Vertical padding inside the toolbar. |
--hy-markdown-editor-icon-size-lg | var(--hy-icon-size-lg) | Height of the vertical toolbar separator. |
--hy-markdown-editor-gap-form-sm | var(--hy-gap-form-sm) | Gap between toolbar items. |
--hy-markdown-editor-radius-form-md | var(--hy-radius-form-md) | Border radius of the outer wrapper. |
--hy-markdown-editor-stroke-sm | var(--hy-stroke-sm) | Border width. |
--hy-markdown-editor-border-default-rest | var(--hy-border-default-rest) | Border color. |
--hy-markdown-editor-background-surface-base | var(--hy-background-surface-base) | Background color. |
--hy-markdown-editor-foreground-default-rest | var(--hy-foreground-default-rest) | Foreground / text color. |
--hy-markdown-editor-tools-foreground-faint-rest | var(--hy-foreground-faint-rest) | Format tool glyph color while readonly |
--hy-markdown-editor-placeholder-foreground-subtle-rest | var(--hy-foreground-subtle-rest) | Color of the source field's placeholder. |
--hy-markdown-editor-code-sm-font-family | var(--hy-code-sm-font-family) | Source-pane font family. |
Methods
focus()
Focus the CM6 editor. No-op when the source pane can't be shown (single-pane preview) or before mount.
select()
Select the entire source-pane content. No-op when the source pane can't be shown.