Features
You can enable, disable, or customize each feature through the features option.
Feature Overview
Vizel enables most features by default. Set any feature to false to disable it.
| Feature | Description |
|---|---|
slashCommand | Slash command menu (type / to open) |
table | Table editing support |
tableOfContents | Auto-collected heading navigation block |
image | Image upload and resize |
codeBlock | Code blocks with syntax highlighting |
dragHandle | Drag handle for block reordering |
characterCount | Character and word counting |
textColor | Text color and highlight |
taskList | Checkbox task lists |
link | Link editing |
markdown | Markdown import/export |
mathematics | LaTeX math equations |
embed | URL embeds (YouTube, etc.) |
details | Collapsible content blocks |
callout | Info, warning, danger, tip, and note admonition blocks |
diagram | Mermaid/GraphViz diagrams |
superscript | Superscript text formatting |
subscript | Subscript text formatting |
typography | Smart quotes, em dashes, and other typographic transformations |
wikiLink | Wiki-style internal links ([[page-name]]) — opt-in |
mention | @user autocomplete — opt-in |
comment | Text annotations and comments — opt-in |
collaboration | Real-time collaboration mode (disables History) — opt-in |
Markdown Flavor Selection
Vizel supports multiple Markdown output flavors to match the platform you are targeting. The flavor option controls how content is serialized when exporting Markdown; input parsing is always tolerant and accepts all formats regardless of the selected flavor.
Available Flavors
| Flavor | Callout Output | WikiLink Output | Target Platforms |
|---|---|---|---|
"commonmark" | Blockquote fallback | Standard link [text](wiki://page) | Stack Overflow, Reddit, email |
"gfm" (default) | GitHub Alerts > [!NOTE] | Standard link [text](wiki://page) | GitHub, GitLab, DEV.to |
"obsidian" | Obsidian Callouts > [!note] | [[page]] / [[page|text]] | Obsidian, Logseq, Foam |
"docusaurus" | Directives :::note | Standard link [text](wiki://page) | Docusaurus, VitePress, Zenn, Qiita |
Usage
const editor = useVizelEditor({
flavor: 'obsidian', // Default: 'gfm'
});Extension-Specific Behavior
- Callout: Serializes admonition blocks in the format matching the selected flavor. All four callout formats are parsed regardless of flavor.
- WikiLink: When
flavoris"obsidian", wiki links serialize as[[page]]syntax. For all other flavors, they serialize as standard Markdown links[text](wiki://page). - Mention: Always serializes as
@usernameregardless of flavor.
TIP
You can override the flavor-driven defaults for individual extensions. For example, set wikiLink.serializeAsWikiLink or callout.markdownFormat explicitly to override the flavor configuration.
Disabling Features
Set a feature to false to disable it:
const editor = useVizelEditor({
features: {
slashCommand: false, // Disable slash commands
dragHandle: false, // Disable drag handle
table: false, // Disable tables
},
});Slash Commands
Type / to open the command menu for block insertion.
Options
| Property | Type | Description |
|---|---|---|
items | VizelSlashCommandItem[] | Custom command items |
suggestion | object | Suggestion configuration |
Custom Commands
import type { VizelSlashCommandItem } from '@vizel/core';
const customItems: VizelSlashCommandItem[] = [
{
title: 'Custom Block',
description: 'Insert a custom block',
icon: 'lucide:box',
keywords: ['custom', 'block'],
group: 'custom',
command: ({ editor, range }) => {
editor.chain().focus().deleteRange(range).insertContent({
type: 'paragraph',
content: [{ type: 'text', text: 'Custom content' }],
}).run();
},
},
];
const editor = useVizelEditor({
features: {
slashCommand: {
items: customItems, // Add custom items
},
},
});Default Commands
| Group | Commands |
|---|---|
| Text | Heading 1, Heading 2, Heading 3 |
| Lists | Bullet List, Numbered List, Task List |
| Blocks | Quote, Divider, Details, Code Block, Table |
| Media | Image, Upload Image, Embed |
| Advanced | Math Equation, Inline Math, Mermaid Diagram, GraphViz Diagram |
Images
The image feature supports drag and drop, paste, and resize.
Options
| Property | Type | Default | Description |
|---|---|---|---|
resize | boolean | true | Enable image resizing |
onUpload | (file: File) => Promise<string> | Base64 | Upload handler |
maxFileSize | number | - | Max file size in bytes |
allowedTypes | string[] | See below | Allowed MIME types |
onValidationError | (error) => void | - | Validation error callback |
onUploadError | (error, file) => void | - | Upload error callback |
Default Allowed Types
['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']Example: Custom Upload
const editor = useVizelEditor({
features: {
image: {
onUpload: async (file) => {
const formData = new FormData();
formData.append('image', file);
const res = await fetch('/api/upload', {
method: 'POST',
body: formData,
});
const { url } = await res.json();
return url;
},
maxFileSize: 5 * 1024 * 1024, // 5MB
allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
onValidationError: (error) => {
if (error.type === 'file_too_large') {
alert('File is too large. Maximum size is 5MB.');
} else if (error.type === 'invalid_type') {
alert('Invalid file type. Only JPEG, PNG, and WebP are allowed.');
}
},
onUploadError: (error, file) => {
console.error(`Failed to upload ${file.name}:`, error);
alert('Upload failed. Please try again.');
},
},
},
});Code Blocks
This feature provides code blocks with syntax highlighting and language selection.
Options
| Property | Type | Default | Description |
|---|---|---|---|
defaultLanguage | string | "plaintext" | Default language |
lineNumbers | boolean | false | Show line numbers |
lowlight | Lowlight | All languages | Custom Lowlight instance |
Example: Limited Languages
import { createLowlight, common } from 'lowlight';
const lowlight = createLowlight(common);
const editor = useVizelEditor({
features: {
codeBlock: {
defaultLanguage: 'typescript',
lineNumbers: true,
lowlight, // Only common languages
},
},
});Character Count
This feature tracks character and word counts.
Options
| Property | Type | Default | Description |
|---|---|---|---|
limit | number | null | null | Max characters (null = unlimited) |
mode | "textSize" | "nodeSize" | "textSize" | Counting mode |
wordCounter | (text: string) => number | - | Custom word counter |
Example: Character Limit
const editor = useVizelEditor({
features: {
characterCount: {
limit: 10000, // Max 10,000 characters
mode: 'textSize',
},
},
});
// Access counts
const chars = editor.storage.characterCount.characters();
const words = editor.storage.characterCount.words();
const limit = editor.storage.characterCount.limit;
const percentage = (chars / limit) * 100;Text Color & Highlight
This feature adds text color and background highlight support.
Options
| Property | Type | Description |
|---|---|---|
textColors | VizelColorDefinition[] | Custom text color palette |
highlightColors | VizelColorDefinition[] | Custom highlight color palette |
multicolor | boolean | Enable multicolor highlights |
Custom Color Palette
import type { VizelColorDefinition } from '@vizel/core';
const customColors: VizelColorDefinition[] = [
{ name: 'Brand', color: '#6366f1' },
{ name: 'Success', color: '#22c55e' },
{ name: 'Warning', color: '#f59e0b' },
{ name: 'Error', color: '#ef4444' },
];
const editor = useVizelEditor({
features: {
textColor: {
textColors: customColors,
highlightColors: customColors.map(c => ({
...c,
color: `${c.color}40`, // Add transparency
})),
},
},
});Markdown
This feature enables Markdown import/export.
Options
| Property | Type | Default | Description |
|---|---|---|---|
indentation | { style, size } | { style: 'space', size: 2 } | Indentation config |
gfm | boolean | true | GitHub Flavored Markdown |
breaks | boolean | false | Convert newlines to <br> |
Example
const editor = useVizelEditor({
features: {
markdown: {
gfm: true,
breaks: false,
indentation: {
style: 'space',
size: 2,
},
},
},
});
// Export to Markdown
const md = editor.storage.markdown.getMarkdown();
// Import from Markdown
editor.commands.setContent(
editor.storage.markdown.parseMarkdown('# Hello\n\nWorld')
);Recommended API
Use editor.getMarkdown() for Markdown export:
const md = editor.getMarkdown();The editor.storage.markdown.getMarkdown() method is an internal storage accessor. Prefer editor.getMarkdown() for application code.
Mathematics
This feature renders LaTeX math equations with KaTeX.
Options
| Property | Type | Default | Description |
|---|---|---|---|
katexOptions | KatexOptions | {} | KaTeX rendering options |
inlineInputRules | boolean | true | Enable $...$ input rules |
blockInputRules | boolean | true | Enable $$...$$ input rules |
Example
const editor = useVizelEditor({
features: {
mathematics: {
katexOptions: {
throwOnError: false,
strict: false,
},
inlineInputRules: true,
blockInputRules: true,
},
},
});Usage
- Inline math: Type
$E=mc^2$and press space - Block math: Type
$$on a new line, enter equation, type$$
Embeds
This feature embeds content from URLs (YouTube, Vimeo, Twitter).
Options
| Property | Type | Default | Description |
|---|---|---|---|
fetchEmbedData | Function | Built-in | Custom fetch function |
providers | VizelEmbedProvider[] | Default providers | Custom/additional providers |
pasteHandler | boolean | true | Auto-embed pasted URLs |
inline | boolean | false | Inline vs block embeds |
Default Providers
- YouTube
- Vimeo
- Twitter/X
- CodePen
- CodeSandbox
- Figma
- Loom
- Spotify
Example: Custom Provider
import type { VizelEmbedProvider } from '@vizel/core';
const customProvider: VizelEmbedProvider = {
name: 'MyService',
patterns: [/^https?:\/\/myservice\.com\/embed\/(\w+)/],
transform: (url) => `https://myservice.com/embed/${url.split('/').pop()}`,
};
const editor = useVizelEditor({
features: {
embed: {
providers: [customProvider],
pasteHandler: true,
},
},
});Details
This feature provides collapsible content blocks (accordion/disclosure).
Options
| Property | Type | Description |
|---|---|---|
details | object | Details container options |
detailsContent | object | Content area options |
detailsSummary | object | Summary/header options |
Example
const editor = useVizelEditor({
features: {
details: true, // Enable with defaults
},
});Usage
Use the slash command /details or /toggle to insert a collapsible block.
Diagrams
This feature supports Mermaid and GraphViz diagrams.
Options
| Property | Type | Default | Description |
|---|---|---|---|
mermaidConfig | MermaidConfig | {} | Mermaid configuration |
graphvizEngine | string | "dot" | GraphViz layout engine |
defaultType | "mermaid" | "graphviz" | "mermaid" | Default diagram type |
defaultCode | string | - | Default Mermaid code |
defaultGraphvizCode | string | - | Default GraphViz code |
GraphViz Engines
dot- Hierarchical (default)neato- Spring modelfdp- Force-directedsfdp- Scalable force-directedtwopi- Radialcirco- Circular
Example
const editor = useVizelEditor({
features: {
diagram: {
mermaidConfig: {
theme: 'neutral',
securityLevel: 'loose',
},
defaultType: 'mermaid',
defaultCode: `graph TD
A[Start] --> B[End]`,
},
},
});Links
This feature provides link editing and auto-linking.
Options
| Property | Type | Default | Description |
|---|---|---|---|
openOnClick | boolean | true | Open links on click |
autolink | boolean | true | Auto-link URLs while typing |
linkOnPaste | boolean | true | Link pasted URLs |
defaultProtocol | string | "https" | Default protocol |
HTMLAttributes | object | - | HTML attributes for links |
Example
const editor = useVizelEditor({
features: {
link: {
openOnClick: true,
autolink: true,
linkOnPaste: true,
defaultProtocol: 'https',
HTMLAttributes: {
target: '_blank',
rel: 'noopener noreferrer',
},
},
},
});Task Lists
This feature adds checkbox task lists with nested indentation support.
Options
| Property | Type | Description |
|---|---|---|
taskList | TaskListOptions | Task list container options |
taskItem | TaskItemOptions | Task item options |
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Tab | Indent (nest) the current task item |
Shift+Tab | Outdent (un-nest) the current task item |
Alt+↑ | Move the current task item up |
Alt+↓ | Move the current task item down |
These shortcuts also work for bullet lists and ordered lists.
Example
const editor = useVizelEditor({
features: {
taskList: {
taskItem: {
nested: true, // Allow nested task lists
},
},
},
});Drag Handle
This feature provides a handle for drag-and-drop block reordering. It also adds keyboard shortcuts for moving blocks and list items.
Options
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Show drag handle |
Keyboard Shortcuts
| Shortcut | Action |
|---|---|
Alt+↑ | Move the current block or list item up |
Alt+↓ | Move the current block or list item down |
Tab | Indent (nest) a list item |
Shift+Tab | Outdent (un-nest) a list item |
The drag handle is automatically positioned next to the hovered block. For list items, it adjusts its size to be less intrusive.
Example
const editor = useVizelEditor({
features: {
dragHandle: {
enabled: true,
},
},
});Block Menu
Clicking the drag handle opens a context menu for the block. The menu provides:
- Delete — Remove the block
- Duplicate — Copy the block below
- Copy / Cut — Clipboard operations
- Turn into — Convert the block to a different type (heading, list, blockquote, etc.)
The block menu is automatically included in the Vizel all-in-one component. When using the composition pattern (VizelEditor + VizelBubbleMenu), add VizelBlockMenu explicitly:
// React
import { VizelBlockMenu } from '@vizel/react';
<VizelBlockMenu />
// Vue
import { VizelBlockMenu } from '@vizel/vue';
<VizelBlockMenu />
// Svelte
import { VizelBlockMenu } from '@vizel/svelte';
<VizelBlockMenu />Wiki Links
This feature adds wiki-style [[page-name]] links for linking between pages. It supports display text aliases with [[page-name|display text]] syntax.
Options
| Property | Type | Default | Description |
|---|---|---|---|
resolveLink | (pageName: string) => string | (p) => '#' + p | Resolves a page name to a URL |
pageExists | (pageName: string) => boolean | () => true | Checks if a page exists |
getPageSuggestions | (query: string) => VizelWikiLinkSuggestion[] | - | Autocomplete suggestions |
onLinkClick | (pageName: string, event: MouseEvent) => void | - | Click callback |
Example
const editor = useVizelEditor({
features: {
wikiLink: {
resolveLink: (page) => `/wiki/${encodeURIComponent(page)}`,
pageExists: (page) => knownPages.has(page),
},
},
});See the Wiki Links Guide for detailed usage.
Comments
This feature adds text annotation marks for reviewing and discussion.
Options
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable comment marks |
Example
const editor = useVizelEditor({
features: {
comment: true,
},
});Comment management (storage, replies, resolution) uses the framework-specific hooks/composables/runes. See the Comments Guide for details.
Collaboration
This feature enables real-time multi-user editing built on Yjs. When you enable it, Vizel disables the built-in History extension (Yjs handles undo/redo).
WARNING
This opt-in feature requires additional dependencies: yjs, a Yjs provider, @tiptap/extension-collaboration, and @tiptap/extension-collaboration-cursor.
Example
const editor = useVizelEditor({
features: {
collaboration: true, // Disables History
},
extensions: [
Collaboration.configure({ document: ydoc }),
CollaborationCursor.configure({ provider, user }),
],
});See the Collaboration Guide for setup instructions.
Internationalization (i18n)
Vizel provides full internationalization support. All UI strings (toolbar labels, slash menu items, block menu actions, find & replace, etc.) can be translated by passing a VizelLocale object.
Quick Start
Pass a locale prop to the Vizel component or the editor hook/composable/rune:
import { Vizel } from '@vizel/react';
import type { VizelLocale } from '@vizel/core';
import { createVizelLocale } from '@vizel/core';
// Create a partial locale (only override what you need)
const jaLocale = createVizelLocale({
toolbar: {
undo: '元に戻す',
redo: 'やり直す',
bold: '太字',
italic: '斜体',
},
slashMenu: {
noResults: '結果なし',
groups: { text: 'テキスト', lists: 'リスト', blocks: 'ブロック' },
},
});
<Vizel locale={jaLocale} /><script setup lang="ts">
import { Vizel } from '@vizel/vue';
import { createVizelLocale } from '@vizel/core';
const jaLocale = createVizelLocale({
toolbar: { undo: '元に戻す', redo: 'やり直す' },
});
</script>
<template>
<Vizel :locale="jaLocale" />
</template><script lang="ts">
import { Vizel } from '@vizel/svelte';
import { createVizelLocale } from '@vizel/core';
const jaLocale = createVizelLocale({
toolbar: { undo: '元に戻す', redo: 'やり直す' },
});
</script>
<Vizel locale={jaLocale} />Full Locale Override
For a complete translation, provide a full VizelLocale object:
import type { VizelLocale } from '@vizel/core';
const myLocale: VizelLocale = {
toolbar: { ariaLabel: 'Formatting', undo: 'Undo', redo: 'Redo', /* ... all fields required */ },
nodeTypes: { text: 'Text', heading1: 'Heading 1', /* ... */ },
blockMenu: { label: 'Block menu', delete: 'Delete', /* ... */ },
slashMenu: { noResults: 'No results', groups: { /* ... */ }, items: { /* ... */ } },
findReplace: { label: 'Find and replace', findPlaceholder: 'Find...', /* ... */ },
codeBlock: { languagePlaceholder: 'Language', copyCode: 'Copy', copied: 'Copied!' },
dragHandle: { ariaLabel: 'Drag to reorder block' },
saveIndicator: { saved: 'Saved', saving: 'Saving...', unsaved: 'Unsaved', error: 'Error saving' },
nodeSelector: { changeBlockType: 'Change block type', blockTypes: 'Block types', currentBlockType: 'Current block type: {type}' },
relativeTime: { justNow: 'just now', secondsAgo: '{n}s ago', minutesAgo: '{n}m ago', hoursAgo: '{n}h ago', daysAgo: '{n}d ago' },
bubbleMenu: { ariaLabel: 'Text formatting', superscript: 'Superscript', subscript: 'Subscript' },
colorPicker: { textColor: 'Text Color', highlight: 'Highlight', textColorPalette: 'Text color palette', highlightPalette: 'Highlight color palette', recent: 'Recent', hexPlaceholder: '#000000', apply: 'Apply', applyAriaLabel: 'Apply custom color' },
linkEditor: { urlPlaceholder: 'Enter URL...', apply: 'Apply', applyAriaLabel: 'Apply link', removeLink: 'Remove link', removeLinkAriaLabel: 'Remove link', openInNewTab: 'Open in new tab', visit: 'Visit', visitTitle: 'Open URL in new tab', embedAsRichContent: 'Embed as rich content' },
};Partial Locale with createVizelLocale()
Use createVizelLocale() to merge partial translations with the English defaults:
import { createVizelLocale } from '@vizel/core';
// Only override the fields you need — English defaults fill the rest
const locale = createVizelLocale({
toolbar: {
bold: 'Fett',
italic: 'Kursiv',
},
slashMenu: {
noResults: 'Keine Ergebnisse',
},
});Composition Pattern
When using the decomposed component pattern, pass locale to the editor hook and to components that display UI strings:
import { VizelEditor, VizelBubbleMenu, VizelToolbar, VizelBlockMenu, useVizelEditor } from '@vizel/react';
const editor = useVizelEditor({ locale: myLocale });
<VizelToolbar editor={editor} locale={myLocale} />
<VizelEditor editor={editor} />
<VizelBubbleMenu editor={editor} locale={myLocale} />
<VizelBlockMenu locale={myLocale} /><VizelToolbar :editor="editor" :locale="myLocale" />
<VizelEditor :editor="editor" />
<VizelBubbleMenu :editor="editor" :locale="myLocale" />
<VizelBlockMenu :locale="myLocale" /><VizelToolbar {editor} locale={myLocale} />
<VizelEditor {editor} />
<VizelBubbleMenu {editor} locale={myLocale} />
<VizelBlockMenu locale={myLocale} />Default Locale
The English locale is available as vizelEnLocale:
import { vizelEnLocale } from '@vizel/core';
console.log(vizelEnLocale.toolbar.bold); // "Bold"Toolbar Extensibility
The toolbar supports custom actions, dropdown menus, and responsive overflow for narrow viewports.
Custom Toolbar Actions
Pass an actions array to VizelToolbarDefault to customize the toolbar:
import { VizelToolbar, VizelToolbarDefault } from '@vizel/react';
import type { VizelToolbarActionItem } from '@vizel/core';
import { vizelDefaultToolbarActions } from '@vizel/core';
const myActions: VizelToolbarActionItem[] = [
...vizelDefaultToolbarActions,
{
id: 'myAction',
label: 'My Action',
icon: 'bold',
group: 'custom',
isActive: () => false,
isEnabled: () => true,
run: (editor) => { /* custom logic */ },
},
];
<VizelToolbar>
<VizelToolbarDefault editor={editor} actions={myActions} />
</VizelToolbar><script setup lang="ts">
import { VizelToolbar, VizelToolbarDefault } from '@vizel/vue';
import type { VizelToolbarActionItem } from '@vizel/core';
import { vizelDefaultToolbarActions } from '@vizel/core';
const myActions: VizelToolbarActionItem[] = [
...vizelDefaultToolbarActions,
{
id: 'myAction',
label: 'My Action',
icon: 'bold',
group: 'custom',
isActive: () => false,
isEnabled: () => true,
run: (editor) => { /* custom logic */ },
},
];
</script><script lang="ts">
import { VizelToolbar, VizelToolbarDefault } from '@vizel/svelte';
import type { VizelToolbarActionItem } from '@vizel/core';
import { vizelDefaultToolbarActions } from '@vizel/core';
const myActions: VizelToolbarActionItem[] = [
...vizelDefaultToolbarActions,
{
id: 'myAction',
label: 'My Action',
icon: 'bold',
group: 'custom',
isActive: () => false,
isEnabled: () => true,
run: (editor) => { /* custom logic */ },
},
];
</script>Dropdown Menus
Group related actions into dropdown menus using VizelToolbarDropdownAction:
import type { VizelToolbarDropdownAction } from '@vizel/core';
const headingDropdown: VizelToolbarDropdownAction = {
id: 'headings',
label: 'Headings',
icon: 'heading1',
group: 'heading',
type: 'dropdown',
options: [
{
id: 'heading1',
label: 'Heading 1',
icon: 'heading1',
group: 'heading',
isActive: (editor) => editor.isActive('heading', { level: 1 }),
isEnabled: (editor) => editor.can().toggleHeading({ level: 1 }),
run: (editor) => editor.chain().focus().toggleHeading({ level: 1 }).run(),
shortcut: 'Mod+Alt+1',
},
// ... more heading options
],
getActiveOption: (editor) =>
// Return the currently active heading option to show in the trigger
options.find((opt) => opt.isActive(editor)),
};Overflow Menu
Use VizelToolbarOverflow to show hidden actions in a popover when the toolbar is narrow:
import { VizelToolbar, VizelToolbarOverflow } from '@vizel/react';
<VizelToolbar>
{/* Main toolbar buttons */}
<VizelToolbarOverflow editor={editor} actions={overflowActions} />
</VizelToolbar><VizelToolbar>
<!-- Main toolbar buttons -->
<VizelToolbarOverflow :editor="editor" :actions="overflowActions" />
</VizelToolbar><VizelToolbar>
<!-- Main toolbar buttons -->
<VizelToolbarOverflow {editor} actions={overflowActions} />
</VizelToolbar>Next Steps
- Wiki Links - Wiki-style internal links
- Comments - Text annotations and comments
- Version History - Document snapshots
- Collaboration - Real-time multi-user editing
- Plugins - Extend Vizel with plugins
- Theming - Customize the appearance
- Auto-Save - Persist content automatically
- API Reference