vue-modern-monaco-mdc
vue-modern-monaco-mdc
Vue 3 composables and components for modern-monaco editor, with optional MDC (Markdown Components) support.
Features
- Vue 3 Components — Ready-to-use
<MonacoEditor>and<MonacoDiffEditor>components - Composables — Flexible
useMonaco(),useMonacoDiff(), anduseMonacoOptions()composables - Themes — Built-in dark and light themes (Vitesse)
- Syntax Highlighting — Powered by Shiki via modern-monaco
- MDC Support — Optional Markdown Components support with autocomplete (tree-shakable)
- Reactive — Full Vue reactivity support with
v-model - TypeScript — Full TypeScript support
Installation
npm install vue-modern-monaco-mdc
# or
pnpm add vue-modern-monaco-mdc
# or
yarn add vue-modern-monaco-mdc
Monaco itself is loaded from the CDN at runtime. For its TypeScript types (editor instance, events…), also install modern-monaco as a dev dependency:
pnpm add -D modern-monaco
Quick Start
Using Components
<template>
<MonacoEditor v-model="code" language="typescript" theme="vitesse-dark" style="height: 400px" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import { MonacoEditor } from "vue-modern-monaco-mdc";
const code = ref(`function hello() {
console.log('Hello, World!')
}`);
</script>
Using Composables
For more control over the editor lifecycle:
<template>
<div ref="container" style="height: 400px" />
</template>
<script setup lang="ts">
import { ref, shallowRef } from "vue";
import { useMonaco } from "vue-modern-monaco-mdc";
const container = shallowRef<HTMLElement | null>(null);
const content = ref('const hello = "world"');
const { editor, isReady } = useMonaco({
container,
content,
language: "typescript",
theme: "vitesse-dark",
onChange: (value) => console.log("Content changed:", value),
});
</script>
Examples
Diff Editor
Compare two versions of code side-by-side:
<template>
<MonacoDiffEditor
:original="originalCode"
v-model:modified="modifiedCode"
language="typescript"
theme="vitesse-dark"
style="height: 400px"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { MonacoDiffEditor } from "vue-modern-monaco-mdc";
const originalCode = ref(`function add(a: number, b: number): number {
return a + b
}`);
const modifiedCode = ref(`function add(a: number, b: number): number {
// Add two numbers together
const result = a + b
return result
}`);
</script>
Accessing the Editor Instance
<template>
<div>
<button @click="formatCode">Format Code</button>
<MonacoEditor ref="editorRef" v-model="code" language="typescript" style="height: 400px" />
</div>
</template>
<script setup lang="ts">
import { ref, shallowRef } from "vue";
import { MonacoEditor } from "vue-modern-monaco-mdc";
const code = ref(`function example() {
return "unformatted code"
}`);
const editorRef = shallowRef<InstanceType<typeof MonacoEditor> | null>(null);
async function formatCode() {
const editor = editorRef.value?.editor;
await editor?.getAction("editor.action.formatDocument")?.run();
}
</script>
MDC (Markdown Components) Support
MDC support is optional and tree-shakable. Import from the /mdc subpath to avoid bundling it when not needed.
<template>
<MonacoEditor
v-model="code"
language="mdc"
theme="vitesse-dark"
:on-setup="setupMDC"
style="height: 400px"
/>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { MonacoEditor } from "vue-modern-monaco-mdc";
import { registerMdcLanguage, setupSuggestion } from "vue-modern-monaco-mdc/mdc";
import type { Monaco } from "vue-modern-monaco-mdc";
import type { ComponentMeta, TreeItem } from "vue-modern-monaco-mdc/mdc";
const code = ref(`# My Document\n\n::my-component{prop="value"}\nContent here\n::`);
const componentsMeta: ComponentMeta[] = [
{
name: "my-component",
path: "/components/MyComponent.vue",
meta: {
props: [{ name: "prop", type: "string", required: false }],
slots: [{ name: "default", description: "Default slot content" }],
},
},
];
const mediaTree: TreeItem[] = [{ name: "image.png", fsPath: "/images/image.png", type: "file" }];
function setupMDC(monaco: Monaco) {
registerMdcLanguage(monaco);
setupSuggestion(monaco, componentsMeta, mediaTree, (key, defaultVal) => defaultVal || key);
}
</script>
Markdown Toolbar
The /mdc subpath exports formatting utilities for building toolbar buttons:
<template>
<div class="toolbar">
<button @click="toggleBold(editor)">Bold</button>
<button @click="toggleItalic(editor)">Italic</button>
<button @click="onHeading(editor)">Heading</button>
<button @click="onUnorderedList(editor)">List</button>
<button @click="onLink(editor)">Link</button>
</div>
</template>
<script setup lang="ts">
import type { MonacoEditorInstance } from "vue-modern-monaco-mdc";
import {
toggleBold,
toggleItalic,
onHeading,
onUnorderedList,
onLink,
} from "vue-modern-monaco-mdc/mdc";
const props = defineProps<{ editor: MonacoEditorInstance }>();
</script>
API Reference
Components
<MonacoEditor>
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue |
string |
— | Editor content (v-model) |
language |
string |
'plaintext' |
Language mode |
theme |
string |
'vitesse-dark' |
Theme name |
options |
MonacoEditorOptions |
— | Additional editor options |
onSetup |
(monaco) => void |
— | Callback when Monaco instance is ready |
Events: update:modelValue, ready
Slots: loading — custom loading state
Exposed: editor, monaco, isReady
<MonacoDiffEditor>
| Prop | Type | Default | Description |
|---|---|---|---|
original |
string |
— | Original content (left side) |
modified |
string |
— | Modified content (v-model:modified) |
language |
string |
'plaintext' |
Language mode |
theme |
string |
'vitesse-dark' |
Theme name |
options |
MonacoEditorOptions |
— | Additional editor options |
Events: update:modified, ready
Slots: loading
Composables
useMonaco(options)
const { editor, monaco, isReady, getValue, setValue, focus } = useMonaco({
container: Ref<HTMLElement | null>,
content: Ref<string>,
language?: MaybeRef<string>,
theme?: MaybeRef<string>,
options?: MonacoEditorOptions,
onChange?: (value: string) => void,
onSetup?: (monaco: Monaco) => void,
})
useMonacoDiff(options)
const { editor, monaco, isReady, getModifiedValue, setModifiedValue } = useMonacoDiff({
container: Ref<HTMLElement | null>,
original: Ref<string>,
modified: Ref<string>,
language?: MaybeRef<string>,
theme?: MaybeRef<string>,
options?: MonacoEditorOptions,
onChange?: (value: string) => void,
})
useMonacoOptions(editor, options?)
Reactive bindings for common editor settings. Changes to the returned refs are automatically applied to the editor.
const { lineNumbers, folding, wordWrap, minimap, scrollBeyondLastLine } = useMonacoOptions(
editor: MonacoEditorInstance | MonacoDiffEditorInstance | null,
options?: {
defaults?: Partial<MonacoOptions> | Ref<Partial<MonacoOptions>>
}
)
// Toggle options directly
lineNumbers.value = true
wordWrap.value = false
Themes
Built-in themes: vitesse-dark (default), vitesse-light
Register a custom theme:
import { getMonaco, registerCustomTheme } from "vue-modern-monaco-mdc";
const monaco = await getMonaco();
registerCustomTheme(monaco, "my-theme", {
base: "vs-dark",
inherit: true,
rules: [],
colors: { "editor.background": "#1a1a1a" },
});
Utilities
import { loadMonaco, getMonaco } from "vue-modern-monaco-mdc";
// Load Monaco with specific languages
const monaco = await loadMonaco({ theme: "vitesse-dark", langs: ["typescript", "javascript"] });
// Get the already-loaded Monaco instance
const monaco = await getMonaco();
MDC Functions (vue-modern-monaco-mdc/mdc)
Language Registration
| Function | Description |
|---|---|
registerMdcLanguage(monaco) |
Register MDC syntax highlighting and code folding |
mapLanguageToMdc(lang) |
Map 'markdown' → 'mdc', pass through others |
Autocomplete
| Function | Description |
|---|---|
setupSuggestion(monaco, meta, tree, t) |
Register MDC component/media completions |
getMediasCompletionItems(monaco, range, trigger, tree) |
Get completion items for media files |
Formatting Utilities
| Function | Description |
|---|---|
onUndo(editor) |
Undo last action |
onRedo(editor) |
Redo last action |
onHeading(editor) |
Toggle heading on current line |
toggleBold(editor) |
Toggle bold on selection |
toggleItalic(editor) |
Toggle italic on selection |
toggleStrikethrough(editor) |
Toggle |
toggleCode(editor) |
Toggle inline code on selection |
onLink(editor) |
Insert link (uses clipboard URL if available) |
onImage(editor) |
Insert image (uses clipboard URL if image) |
onBlockquote(editor) |
Toggle blockquote on selected lines |
onCodeBlock(editor) |
Insert code block |
onUnorderedList(editor) |
Toggle bullet list on selected lines |
onOrderedList(editor) |
Toggle numbered list on selected lines |
onTable(editor) |
Insert table template |
onPrettify(editor) |
Clean up markdown formatting |
TypeScript
import type {
Monaco,
MonacoEditor as MonacoEditorInstance,
MonacoDiffEditor as MonacoDiffEditorInstance,
MonacoEditorOptions,
UseMonacoOptions,
UseMonacoReturn,
UseMonacoOptionsOptions,
} from "vue-modern-monaco-mdc";
import type { ComponentMeta, TreeItem } from "vue-modern-monaco-mdc/mdc";
Browser Support
Modern browsers with ES module support. Monaco Editor uses Web Workers — ensure your build tool handles them correctly.
Development
The project uses Vite+ (vp) and pnpm:
vp install # dependencies
vp check # format, lint and type check
vp test # tests
vp pack # build to dist/
pnpm playground
Release
pnpm release bumps the version, then commits, tags and pushes it. The v* tag runs the release workflow: CI, then the npm publish (trusted publishing, next dist-tag for a prerelease) and the GitHub release with its notes from the conventional commits.
License
MIT
Related Projects
- modern-monaco — The underlying Monaco Editor wrapper
- Monaco Editor — Microsoft's code editor
Related Packages
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.2.1 | ^3.3.0 | MIT | |
| 0.2.0 | ^3.3.0 | MIT | |
| 0.1.0 | ^3.3.0 | MIT |