---
title: "MazMarkdownEditor"
description: "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."
package: "maz-ui"
url: "https://maz-ui.com/components/maz-markdown-editor"
---

# 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.

- Package: `maz-ui`
- Import: `import MazMarkdownEditor from 'maz-ui/components/MazMarkdownEditor'`
- Nuxt: auto-imported by `@maz-ui/nuxt`, no import needed in a Nuxt app
- Web: https://maz-ui.com/components/maz-markdown-editor

> Before use it, you should install the MazUi plugin in your project, follow instructions in [Getting Started](../guide/getting-started.md)

## Installation requirement

The preview relies on [`marked`](https://marked.js.org) for parsing and [`dompurify`](https://github.com/cure53/DOMPurify) for sanitization. Both are loaded lazily (only when the preview is rendered), so install them in your project:

```bash [pnpm]
pnpm add marked dompurify
```

```bash [npm]
npm install marked dompurify
```

```bash [yarn]
yarn add marked dompurify
```

If 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).

```vue
<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`).

```vue
<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` | `![alt](url)` |
| `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):

```vue
<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"`:

```vue
<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.

```vue
<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.

```vue
<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.

```vue
<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.

```vue
<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.

```vue
<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.

```vue
<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'`).

```vue
<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:

```vue
<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](https://maz-ui.com/ecosystem/translations.md) and the component will pick up your locale automatically.

## Accessibility

The write / preview / split switcher follows the [WAI-ARIA tabs pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/): it is a `tablist` named by the `name` prop, the editor and the preview are its `tabpanel`s. Only the selected mode is in the tab sequence, ← / → (reversed in RTL), Home and End switch the mode.

## MazMarkdownEditor API

`import MazMarkdownEditor from 'maz-ui/components/MazMarkdownEditor'`

### Props

- `model-value` (`string`, `v-model`): The markdown value of the editor.
- `id` (`string`): The id of the textarea.
- `name` (`string`, default: `"MazMarkdownEditor"`): The name of the textarea.
- `label` (`string`): Static label displayed above the editor.
- `placeholder` (`string`): The placeholder of the textarea.
- `required` (`boolean`, default: `false`): If the editor is required.
- `disabled` (`boolean`, default: `false`): If the editor is disabled.
- `readonly` (`boolean`, default: `false`): If the editor is readonly.
- `error` (`boolean`, default: `false`): If the editor has an error.
- `success` (`boolean`, default: `false`): If the editor has a success.
- `warning` (`boolean`, default: `false`): If the editor has a warning.
- `color` (`"primary" | "secondary" | "accent" | "info" | "success" | "warning" | "destructive" | "contrast" | "transparent"`, default: `"primary"`): The color of the editor.
- `rounded-size` (`MazRoundedSize`, default: `'md'`): Size radius of the component's border.
- `top-label` (`string`): Static label displayed above the editor. Unlike a floating label, this remains fixed.
- `assistive-text` (`string`): Helper text displayed below the editor to provide additional context or validation feedback.
- `toolbar` (`boolean | MazMarkdownEditorToolbarAction[]`, default: `false`): 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).
- `line-numbers` (`boolean`, default: `false`): Display line numbers in a gutter on the left of the editor.
- `shortcuts` (`boolean`, default: `true`): Enable keyboard shortcuts for the toolbar actions (e.g. Cmd/Ctrl+B for bold).
- `sanitize` (`boolean`, default: `true`): Sanitize the rendered preview HTML with DOMPurify.
- `render-function` (`((markdown: string) => string | Promise<string>)`): 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.
- `marked-options` (`Record<string, unknown>`): Options forwarded to `marked` when using the default renderer.
- `rows` (`number`, default: `6`): Minimum number of visible rows of the editor textarea.
- `translations` (`{ write?: string | undefined; preview?: string | undefined; split?: string | undefined; emptyPreview?: string | undefined; toolbar?: { heading?: string | undefined; bold?: string | undefined; italic?: string | undefined; strikethrough?: ...`): Custom translations for the component. Override any key independently.
- `mode` (`MazMarkdownEditorMode`, default: `"write"`, `v-model:mode`)

### Events

- `@update:model-value` (payload: `value?: string | undefined`): Emitted when the markdown value changes
- `@blur` (payload: `value: FocusEvent`): Emitted when the textarea is blurred
- `@change` (payload: `value: Event`): Emitted when the textarea value changes
- `@focus` (payload: `value: FocusEvent`): Emitted when the textarea is focused
- `@input` (payload: `value?: string | undefined`): Emitted when the markdown value changes
- `@update:mode` (payload: `value: MazMarkdownEditorMode`)

### Slots

- `#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) Bindings: `mode: MazMarkdownEditorMode` (The current editor mode), `wrapSelection: (before: string, after?: string) => void` (Wrap the current selection with the given markers), `prefixLines: (prefix: string) => void` (Prefix every selected line with the given string), `applyHeading: (level: number) => void` (Apply a heading of the given level to the selected lines), `insertLink: () => void` (Insert a markdown link at the selection), `insertImage: () => void` (Insert a markdown image at the selection), `insertCodeBlock: () => void` (Insert a fenced code block at the selection), `insertTable: () => void` (Insert a markdown table at the selection).
- `#empty-preview`: Content displayed in the preview pane when there is nothing to render

### Exposed (template ref)

- `textarea`: The underlying textarea element
- `renderPreview`: Programmatically re-render the markdown preview
- `focus`: Focus the editor textarea
