NPM Package
A lightweight Vue 3 component that renders Editor.js blocks as native Vue components. No v-html, full customization, XSS protection included.
import EditorjsRenderer from 'editorjs-vue-renderer'
<EditorjsRenderer :editorjs-blocks="blocks" />Editor.js is an excellent block-based editor that I use in all my projects - both for admin panels and user-generated content (like on Gameverse). It's clean, extensible, and easy to maintain.
However, Editor.js stores content as JSON blocks, not HTML. The official renderer converts these blocks to HTML strings, which means you have to use v-html to display them. This creates several problems:
v-html is vulnerable to XSS attacks if content isn't properly sanitizedI created a Vue component that renders Editor.js blocks as native Vue components. Each block type (paragraph, header, list, etc.) is rendered as a proper Vue component, giving you full control over styling and behavior.
Each block renders as a real Vue component, not injected HTML
Built-in DOMPurify sanitization prevents injection attacks
Pass your own Vue components for custom or unsupported block types
Works seamlessly with Nuxt and other SSR setups
Simply pass your Editor.js blocks array to the component
<template>
<EditorjsRenderer :editorjs-blocks="blocks" />
</template>
<script setup lang="ts">
import EditorjsRenderer from 'editorjs-vue-renderer';
const blocks = [
{
id: 'header-1',
type: 'header',
data: { text: 'Hello World', level: 2 }
},
{
id: 'paragraph-1',
type: 'paragraph',
data: { text: 'Welcome to my blog!' }
},
{
id: 'list-1',
type: 'list',
data: {
style: 'unordered',
items: ['First item', 'Second item', 'Third item']
}
},
];
</script>Text paragraphs with inline formatting support
Headings from H1 to H6 with customizable levels
Ordered and unordered lists with nested items
Images with captions and optional styling
Block quotes with optional caption/author
Tables with optional header row support
Override default blocks or add support for custom Editor.js plugins
<template>
<EditorjsRenderer
:editorjs-blocks="blocks"
:custom-blocks="customBlocks"
/>
</template>
<script setup>
import EditorjsRenderer from 'editorjs-vue-renderer';
import CustomQuote from './blocks/CustomQuote.vue';
import CustomEmbed from './blocks/CustomEmbed.vue';
// Override default blocks or add new ones
const customBlocks = {
quote: CustomQuote, // Override default quote
youtube: CustomEmbed, // Add custom youtube block
gallery: defineAsyncComponent(
() => import('./blocks/Gallery.vue')
)
};
</script>