MazMarkdownEditor
MazMarkdownEditor is a GitHub-style markdown editor with Write/Preview/Split tabs, a configurable formatting toolbar, optional line numbers, and a sanitized preview. It shares the same states, labels and theming as MazTextarea, is SSR-friendly, and supports custom renderers.
Before use it, you should install the MazUi plugin in your project, follow instructions in Getting Started
Installation requirement
The preview relies on marked for parsing and dompurify for sanitization. Both are loaded lazily (only when the preview is rendered), so install them in your project:
pnpm add marked dompurifynpm install marked dompurifyyarn add marked dompurifyIf they are not installed, the preview falls back to a safe, escaped plain-text block. You can also provide your own renderer with the renderFunction prop to avoid these dependencies entirely.
Basic usage
The component exposes a v-model bound to the raw markdown string and two tabs: Write (textarea) and Preview (rendered, sanitized HTML).
Nothing to preview
<template>
<MazMarkdownEditor v-model="content" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MazMarkdownEditor from 'maz-ui/components/MazMarkdownEditor'
const content = ref('# Hello maz-ui\n\nThis is **markdown** with a [link](https://maz-ui.com).')
</script>Toolbar
The formatting toolbar is opt-in. Set toolbar to true to display the full toolbar. Buttons act on the current textarea selection and preserve the native undo/redo history (Cmd/Ctrl + Z).
Nothing to preview
<template>
<MazMarkdownEditor v-model="content" toolbar />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MazMarkdownEditor from 'maz-ui/components/MazMarkdownEditor'
const content = ref('Select some text and click **Bold**.')
</script>Available actions
The full toolbar exposes the following action keys:
| Key | Result |
|---|---|
heading | Heading dropdown (#, ##, ###) |
bold | **bold** |
italic | _italic_ |
strikethrough | ~~strikethrough~~ |
quote | > quote |
code | `inline code` |
codeBlock | fenced ``` code block |
link | [text](url) |
image |  |
bulletList | - item |
orderedList | 1. item |
checkList | - [ ] task |
table | markdown table skeleton |
Configure the toolbar
Pass an ordered array of action keys to display only a subset (and control their order):
Nothing to preview
<template>
<MazMarkdownEditor
v-model="content"
:toolbar="['heading', 'bold', 'italic', 'link', 'codeBlock']"
/>
</template>Keyboard shortcuts
Classic markdown shortcuts are enabled by default and work whenever the textarea is focused. They preserve the native undo/redo history. The toolbar buttons also display their shortcut in the tooltip. Use mod for Cmd on macOS and Ctrl elsewhere.
| Shortcut | Action |
|---|---|
mod + B | Bold |
mod + I | Italic |
mod + Shift + X | Strikethrough |
mod + K | Link |
mod + E | Inline code |
mod + Shift + E | Code block |
mod + Shift + . | Quote |
mod + Shift + 8 | Bulleted list |
mod + Shift + 7 | Numbered list |
mod + Shift + L | Task list |
mod + Alt + 1/2/3 | Heading 1/2/3 |
Disable them with :shortcuts="false":
<template>
<MazMarkdownEditor v-model="content" toolbar :shortcuts="false" />
</template>TIP
Image and table insertions have no default shortcut (to avoid clashing with browser shortcuts). Use the toolbar buttons or the toolbar slot helpers for those.
List continuation
Pressing Enter inside a list automatically inserts the next marker, just like a desktop markdown editor:
- Bulleted lists (
-,*,+) repeat the marker. - Numbered lists increment the index (
1.→2.). - Task lists insert a fresh unchecked box (
- [ ]). - Indentation is preserved.
Pressing Enter again on an empty item removes the marker and exits the list. This works out of the box, no configuration required.
Label & assistive text
Like MazTextarea, the editor supports a static top-label (or label), the required asterisk and an assistive-text displayed below the editor.
Nothing to preview
<template>
<MazMarkdownEditor
v-model="content"
top-label="Description"
assistive-text="Markdown is supported"
required
/>
</template>States
Use error, success, warning, disabled and readonly to reflect validation states, consistent with the other form components.
Nothing to preview
Nothing to preview
Nothing to preview
<template>
<MazMarkdownEditor v-model="content" error assistive-text="This field has an error" />
<MazMarkdownEditor v-model="content" success assistive-text="Looks good!" />
<MazMarkdownEditor v-model="content" disabled />
</template>Split mode
A third tab displays the editor and the preview side by side. Switch to it from the toolbar tabs, or control it with v-model:mode ('write' | 'preview' | 'split'). On small screens the two panes stack vertically.
Nothing to preview
<template>
<MazMarkdownEditor v-model="content" mode="split" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MazMarkdownEditor from 'maz-ui/components/MazMarkdownEditor'
const content = ref('# Split view\n\nType on the left, see the **preview** on the right.')
</script>Line numbers
Enable the line-numbers prop to display a gutter on the left of the editor. Numbers reflect logical lines and stay in sync with the textarea scroll.
Nothing to preview
<template>
<MazMarkdownEditor v-model="content" line-numbers />
</template>Custom renderer
Provide a render-function to use your own markdown engine (e.g. markdown-it, a Nuxt Content renderer, or a server-side pipeline). It receives the raw markdown and returns an HTML string (sync or async). The result is still sanitized when sanitize is enabled.
Nothing to preview
<template>
<MazMarkdownEditor v-model="content" :render-function="renderer" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MazMarkdownEditor from 'maz-ui/components/MazMarkdownEditor'
const content = ref('Rendered by a custom function')
function renderer(markdown: string) {
return `<p>${markdown.toUpperCase()}</p>`
}
</script>Sanitization
By default, the rendered HTML is sanitized with DOMPurify to prevent XSS. Disabling it (:sanitize="false") renders the raw output of the renderer; only do this when the markdown source is fully trusted.
<template>
<!-- Trusted content only -->
<MazMarkdownEditor v-model="content" :sanitize="false" />
</template>Controlled mode
The active tab is available through v-model:mode ('write' | 'preview' | 'split').
<template>
<MazMarkdownEditor v-model="content" v-model:mode="mode" />
<p>Current tab: {{ mode }}</p>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import MazMarkdownEditor from 'maz-ui/components/MazMarkdownEditor'
const content = ref('')
const mode = ref<'write' | 'preview' | 'split'>('write')
</script>Custom translations
The tab labels, the empty-preview text and the toolbar button labels come from the @maz-ui/translations package (markdownEditor.*). You can override any key per-instance with the translations prop without touching the global locale:
Rien à afficher
<template>
<MazMarkdownEditor
v-model="content"
toolbar
:translations="{ write: 'Édition', preview: 'Aperçu', emptyPreview: 'Rien à afficher' }"
/>
</template>For app-wide translations, set up the maz-ui translations plugin and the component will pick up your locale automatically.
Accessibility
The write / preview / split switcher follows the WAI-ARIA tabs pattern: it is a tablist named by the name prop, the editor and the preview are its tabpanels. Only the selected mode is in the tab sequence, ← / → (reversed in RTL), Home and End switch the mode.
Props
| Name | Description | Type | Required | Default | Possible values |
|---|---|---|---|---|---|
| model-value | v-model The markdown value of the editor | string | No | - | - |
| id | The id of the textarea | string | No | - | - |
| name | The name of the textarea | string | No | "MazMarkdownEditor" | - |
| label | Static label displayed above the editor | string | No | - | - |
| placeholder | The placeholder of the textarea | string | No | - | - |
| required | If the editor is required | boolean | No | false | - |
| disabled | If the editor is disabled | boolean | No | false | - |
| readonly | If the editor is readonly | boolean | No | false | - |
| error | If the editor has an error | boolean | No | false | - |
| success | If the editor has a success | boolean | No | false | - |
| warning | If the editor has a warning | boolean | No | false | - |
| color | The color of the editor | "primary" | "secondary" | "accent" | "info" | "success" | "warning" | "destructive" | "contrast" | "transparent" | No | "primary" | 'primary' | 'secondary' | 'accent' | 'info' | 'success' | 'warning' | 'destructive' | 'contrast' | 'transparent' |
| rounded-size | Size radius of the component's border | MazRoundedSize | No | 'md' | - |
| top-label | Static label displayed above the editor. Unlike a floating label, this remains fixed | string | No | - | - |
| assistive-text | Helper text displayed below the editor to provide additional context or validation feedback | string | No | - | - |
| toolbar | Show the formatting toolbar. Pass true for the full toolbar, or an ordered array of action keys to display only a subset (and control their order). | boolean | MazMarkdownEditorToolbarAction[] | No | false | - |
| line-numbers | Display line numbers in a gutter on the left of the editor | boolean | No | false | - |
| shortcuts | Enable keyboard shortcuts for the toolbar actions (e.g. Cmd/Ctrl+B for bold). | boolean | No | true | - |
| sanitize | Sanitize the rendered preview HTML with DOMPurify | boolean | No | true | - |
| render-function | Custom markdown renderer. Receives the raw markdown and returns an HTML string (sync or async). Falls back to marked when omitted. The result is still sanitized when sanitize is enabled. | ((markdown: string) => string | Promise<string>) | No | - | - |
| marked-options | Options forwarded to marked when using the default renderer | Record<string, unknown> | No | - | - |
| rows | Minimum number of visible rows of the editor textarea | number | No | 6 | - |
| translations | Custom translations for the component. Override any key independently. | { write?: string | undefined; preview?: string | undefined; split?: string | undefined; emptyPreview?: string | undefined; toolbar?: { heading?: string | undefined; bold?: string | undefined; italic?: string | undefined; strikethrough?: ... | No | - | - |
| mode | v-model:mode | MazMarkdownEditorMode | No | "write" | - |
Events
| Event name | Payload | Description |
|---|---|---|
| update:model-value | value?: string | undefined | Emitted when the markdown value changes |
| blur | value: FocusEvent | Emitted when the textarea is blurred |
| change | value: Event | Emitted when the textarea value changes |
| focus | value: FocusEvent | Emitted when the textarea is focused |
| input | value?: string | undefined | Emitted when the markdown value changes |
| update:mode | value: MazMarkdownEditorMode |
Slots
| Name | Description | Bindings |
|---|---|---|
| label | Replace the static label above the editor | - |
| toolbar | Replace the toolbar content (only rendered when the toolbar is enabled and not in preview mode) | mode MazMarkdownEditorMode - The current editor modewrapSelection (before: string, after?: string) => void - Wrap the current selection with the given markersprefixLines (prefix: string) => void - Prefix every selected line with the given stringapplyHeading (level: number) => void - Apply a heading of the given level to the selected linesinsertLink () => void - Insert a markdown link at the selectioninsertImage () => void - Insert a markdown image at the selectioninsertCodeBlock () => void - Insert a fenced code block at the selectioninsertTable () => void - Insert a markdown table at the selection |
| empty-preview | Content displayed in the preview pane when there is nothing to render | - |
Expose
textarea
The underlying textarea element
renderPreview
Programmatically re-render the markdown preview
focus
Focus the editor textarea