Vue setup

Redactor is a plain JavaScript editor. In Vue 3 you wrap it in a component: create the editor when the component mounts, and call destroy() when the component is removed.

This page shows two complete examples — one editor and several editors on the same page.

This guide uses the ES module build (redactor.esm.min.js). Adjust paths to where you keep the package files.

Host element and Vue#

Redactor may move a div host into its own UI tree. If that div is also Vue’s component root, Vue loses the node and multiple editors (or v-if swaps) throw DOM errors like insertBefore.

Use a stable Vue root as an anchor, and create the real host div yourself in mounted. Vue only owns the anchor; Redactor owns the host:

template: '<div ref="anchor"></div>',
mounted() {
    const host = document.createElement('div');
    this.$refs.anchor.appendChild(host);
    this.redactor = Redactor(host, config);
}

A textarea host also works (Redactor leaves it in place), but a div is fine with the anchor pattern above.

Single editor#

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Vue Redactor</title>
    <link rel="stylesheet" href="/assets/redactor/redactor.css">
</head>
<body>
<div id="app">
    <Redactor :config="configOptions"></Redactor>
    {{ content }}
</div>

<script type="module">
import { createApp } from 'vue';
import Redactor from '/assets/redactor/redactor.esm.min.js';
import '/assets/redactor/plugins/counter.js';

const app = createApp({
    data() {
        return {
            content: '',
            configOptions: {
                plugins: ['counter'],
                content: '<p>Hello from Vue</p>'
            }
        };
    }
});

app.component('Redactor', {
    template: '<div ref="anchor"></div>',
    props: {
        config: {
            type: Object,
            default: () => ({
                content: '<p></p>'
            })
        }
    },
    data() {
        return {
            redactor: null
        };
    },
    mounted() {
        this.init();
    },
    beforeUnmount() {
        this.destroy();
    },
    methods: {
        init() {
            const config = {
                ...this.config,
                events: {
                    ...(this.config.events || {}),
                    'editor:change': ({ html }) => {
                        this.handleInput(html);
                    }
                }
            };

            const host = document.createElement('div');
            this.$refs.anchor.appendChild(host);

            this.redactor = Redactor(host, config);
            this.$parent.redactor = this.redactor;
        },
        destroy() {
            if (this.redactor) {
                this.redactor.destroy();
            }
            this.redactor = null;
            this.$parent.redactor = null;
        },
        handleInput(html) {
            this.$emit('input', html);
            if (this.$parent) {
                this.$parent.content = html;
            }
        }
    }
});

app.mount('#app');
</script>
</body>
</html>

Notes:

  • Always call destroy() in beforeUnmount (Vue 3) so Redactor can restore/remove its DOM before Vue tears down the anchor.
  • Call destroy() on the Redactor instance (this.redactor), not on the DOM ref.
  • Document changes use the editor:change event (via events / legacy subscribe in the config). See Events.

Multiple editors#

The same component can run several times. Pass different content and config per instance:

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Vue Redactor</title>
    <link rel="stylesheet" href="/assets/redactor/redactor.css">
</head>
<body>
<div id="app">
    <Redactor :config="configOptions" :content="content1"></Redactor>
    <br><br>
    <Redactor :config="{ plugins: ['counter'] }" :content="content2"></Redactor>
</div>

<script type="module">
import { createApp } from 'vue';
import Redactor from '/assets/redactor/redactor.esm.min.js';
import '/assets/redactor/plugins/counter.js';

const app = createApp({
    data() {
        return {
            configOptions: {
                plugins: ['counter']
            },
            content1: '<p>Hello world 1!</p>',
            content2: '<p>Hello world 2!</p>'
        };
    }
});

app.component('Redactor', {
    template: '<div ref="anchor"></div>',
    props: {
        config: {
            type: Object,
            default: () => ({})
        },
        content: {
            type: String,
            default: ''
        }
    },
    data() {
        return {
            redactor: null
        };
    },
    mounted() {
        this.init();
    },
    beforeUnmount() {
        this.destroy();
    },
    methods: {
        init() {
            const config = {
                ...this.config,
                content: this.content || this.config.content || '<p></p>',
                events: {
                    ...(this.config.events || {}),
                    'editor:change': ({ html }) => {
                        this.handleInput(html);
                    }
                }
            };

            const host = document.createElement('div');
            this.$refs.anchor.appendChild(host);

            this.redactor = Redactor(host, config);
        },
        destroy() {
            if (this.redactor) {
                this.redactor.destroy();
            }
            this.redactor = null;
        },
        handleInput(html) {
            this.$emit('input', html);
        }
    }
});

app.mount('#app');
</script>
</body>
</html>

Each instance owns its own anchor and Redactor app and cleans up in beforeUnmount.