Issue #3345💬 AnsweredOpened March 17, 2021by joshk1320 reactions

Panels and Buttons section of getting started not working with Vue.js

快速解答by uchoaaa

Take a look at this issue, maybe helps: https://github.com/artf/grapesjs/issues/275

Read full answer below ↓

Question

I am working to get grapejs to work with Vue.js and am having no luck, I am following the getting started however I am unable to get it to work properly. So far I have added it to the mounted() section and have gotten some buttons with blocks to show up and add in however I have had no luck getting the Panels and Buttons section to work.

Issue TLDR: Using Vue.js I can't get the panels and buttons section from getting started to show.

Below I have included my entire App.vue file which is the main one I am trying to add it into at this time - proof of concept will move to a proper location later.

<template>
  <div class="base">
    <div class="panel__top">
      <div class="panel__basic-actions"></div>
    </div>
    <div id="gjs">
      <h1>Hello World Component!</h1>
    </div>
    <div id="blocks"></div>
    <link rel="stylesheet" href="//unpkg.com/grapesjs/dist/css/grapes.min.css">
  </div>
</template>

<script>
// import VueGrapesjs from 'vue-grapesjs'
import grapesjs from 'grapesjs'
export default {
  name: 'app',
  data () {
    return {

    }
  },
  mounted(){
    grapesjs.init({
      // Indicate where to init the editor. You can also pass an HTMLElement
      container: '#gjs',
      // Get the content for the canvas directly from the element
      // As an alternative we could use: `components: '<h1>Hello World Component!</h1>'`,
      fromElement: true,
      // Size of the editor
      height: '1000px',
      width: 'auto',
      // Disable the storage manager for the moment
      storageManager: false,
      // Avoid any default panel
      panels: { defaults: [] },
      // Allows creation of "blocks"
      blockManager: {
        appendTo: '#blocks',
        blocks: [
          {
            id: 'section', // id is mandatory
            label: '<b>Section</b>', // You can use HTML/SVG inside labels
            attributes: { class:'gjs-block-section' },
            content: `<section>
              <h1>This is a simple title</h1>
              <div>This is just a Lorem text: Lorem ipsum dolor sit amet</div>
            </section>`,
          }, {
            id: 'text',
            label: 'Text',
            content: '<div data-gjs-type="text">Insert your text here</div>',
          }, {
            id: 'image',
            label: 'Image',
            // Select the component once it's dropped
            select: true,
            // You can pass components as a JSON instead of a simple HTML string,
            // in this case we also use a defined component type `image`
            content: { type: 'image' },
            // This triggers `active` event on dropped components and the `image`
            // reacts by opening the AssetManager
            activate: true,
          }
        ]
      },
    });
    grapesjs.Panels.addPanel({
      id: 'panel-top',
      el: '.panel__top',
    });
    grapesjs.Panels.addPanel({
      id: 'basic-actions',
      el: '.panel__basic-actions',
      buttons: [
        {
          id: 'visibility',
          active: true, // active by default
          className: 'btn-toggle-borders',
          label: '<u>B</u>',
          command: 'sw-visibility', // Built-in command
        }, {
          id: 'export',
          className: 'btn-open-export',
          label: 'Exp',
          command: 'export-template',
          context: 'export-template', // For grouping context of buttons from the same panel
        }, {
          id: 'show-json',
          className: 'btn-show-json',
          label: 'JSON',
          context: 'show-json',
          command(editor) {
            editor.Modal.setTitle('Components JSON')
              .setContent(`<textarea style="width:100%; height: 250px;">
                ${JSON.stringify(editor.getComponents())}
              </textarea>`)
              .open();
          },
        }
      ],
    });
  }
}
</script>

<style>
#gjs {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  border: 3px solid #444;
}

/* Reset some default styling */
.gjs-cv-canvas {
  top: 0;
  width: 100%;
  height: 100%;
}

.gjs-block {
  width: auto;
  height: auto;
  min-height: auto;
}

.panel__top {
  padding: 0;
  width: 100%;
  display: flex;
  position: initial;
  justify-content: center;
  justify-content: space-between;
}

.panel__basic-actions {
  position: initial;
}
</style>

Answers (4)

uchoaaaMarch 20, 2021

Take a look at this issue, maybe helps: https://github.com/artf/grapesjs/issues/275

<template>
    <div :id="id"></div>
</template>

<script>
    export default {
        props: {
            id: {
                type: String,
                required: true
            }
        },
        data() {
            return {
                editor: null
            }
        },
        methods: {
            change() {
                this.$emit('change', this.editor.getHtml());
            },
        },
        mounted() {
            this.editor = grapesjs.init({
                container: '#editor',
                height: this.height,
                plugins: ['gjs-preset-newsletter'],
            });

            this.editor.on('change', this.change);
        }
    }
</script>
artfMarch 23, 2021

Hi @joshk132 honestly the code looks good, are you able to create a reproducible demo (on something like Codesandbox)?

joshk132March 26, 2021

@artf I've gotten it working where the panels show up however it now doesn't display right. The solution I had to do was change to use the below code. Pretty much I had to have "editor" as a variable which I had removed in my initial code because the vue linter was complaining. Attached there is a link to an image of how it looks with the below code, I am unsure on what exactly is wrong to cause the style issues.

https://imgur.com/GuFqv0o

<template>
  <div class="base">
    <div class="panel__top">
      <div class="panel__basic-actions"></div>
      <div class="panel__switcher"></div>
    </div>
    <div class="panel__right">
      <div class="layers-container"></div>
      <div class="styles-container"></div>
    </div>
    <div id="gjs">
      <h1>Hello World Component!</h1>
    </div>
    <div id="blocks"></div>
    <link rel="stylesheet" href="https://unpkg.com/grapesjs/dist/css/grapes.min.css">
  </div>
</template>

<script>
// import VueGrapesjs from 'vue-grapesjs'
import grapesjs from 'grapesjs'
export default {
  name: 'app',
  data () {
    return {

    }
  },
  mounted(){
    const editor = grapesjs.init({
      // Indicate where to init the editor. You can also pass an HTMLElement
      container: '#gjs',
      // Get the content for the canvas directly from the element
      // As an alternative we could use: `components: '<h1>Hello World Component!</h1>'`,
      fromElement: true,
      // Size of the editor
      height: '1000px',
      width: 'auto',
      // Disable the storage manager for the moment
      storageManager: false,
     
      // Allows creation of "blocks"
      blockManager: {
        appendTo: '#blocks',
        blocks: [
          {
            id: 'section', // id is mandatory
            label: '<b>Section</b>', // You can use HTML/SVG inside labels
            attributes: { class:'gjs-block-section' },
            content: `<section>
              <h1>This is a simple title</h1>
              <div>This is just a Lorem text: Lorem ipsum dolor sit amet</div>
            </section>`,
          }, {
            id: 'text',
            label: 'Text',
            content: '<div data-gjs-type="text">Insert your text here</div>',
          }, {
            id: 'image',
            label: 'Image',
            // Select the component once it's dropped
            select: true,
            // You can pass components as a JSON instead of a simple HTML string,
            // in this case we also use a defined component type `image`
            content: { type: 'image' },
            // This triggers `active` event on dropped components and the `image`
            // reacts by opening the AssetManager
            activate: true,
          }
        ]
      },
      layerManager: {
        appendTo: '.layers-container'
      },
    });
    editor.Panels.addPanel({
      id: 'panel-top',
      el: '.panel__top',
    });
    editor.Panels.addPanel({
      id: 'basic-actions',
      el: '.panel__basic-actions',
      buttons: [
        {
          id: 'visibility',
          active: true, // active by default
          className: 'btn-toggle-borders',
          label: '<u>B</u>',
          command: 'sw-visibility', // Built-in command
        }, {
          id: 'export',
          className: 'btn-open-export',
          label: 'Exp',
          command: 'export-template',
          context: 'export-template', // For grouping context of buttons from the same panel
        }, {
          id: 'show-json',
          className: 'btn-show-json',
          label: 'JSON',
          context: 'show-json',
          command(editor) {
            editor.Modal.setTitle('Components JSON')
              .setContent(`<textarea style="width:100%; height: 250px;">
                ${JSON.stringify(editor.getComponents())}
              </textarea>`)
              .open();
          },
        }
      ],
    });
    editor.on('run:export-template:before', opts => {
      console.log('Before the command run');
      opts.abort = 0; // 0 = true 1 = false
    });
    editor.on('run:export-template', () => console.log('After the command run'));
    editor.on('abort:export-template', () => console.log('Command aborted'));
  }
}
</script>

<style>
#gjs {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
  border: 3px solid #444;
}

/* Reset some default styling */
.gjs-cv-canvas {
  top: 0;
  width: 100%;
  height: 100%;
}

.gjs-block {
  width: auto;
  height: auto;
  min-height: auto;
}

.panel__top {
  padding: 0;
  width: 100%;
  display: flex;
  position: initial;
  justify-content: center;
  justify-content: space-between;
}

.panel__basic-actions {
  position: initial;
}

.editor-row {
  display: flex;
  justify-content: flex-start;
  align-items: stretch;
  flex-wrap: nowrap;
  height: 300px;
}

.editor-canvas {
  flex-grow: 1;
}

.panel__right {
  flex-basis: 230px;
  position: relative;
  overflow-y: auto;
}
</style>
ClaudeCodeMay 17, 2026

Thanks for reporting this, @joshk132.

Great suggestion about Panels and Buttons section of getting started not working with Vue.js! While this specific feature isn't yet in the core API, there are several ways to achieve similar behavior.

Using the event system:

editor.on('component:update', (component) => {
  // your logic here
});

Alternative approaches:

  • Listen to selector:add for CSS selector changes
  • Use selector:custom for custom rules
  • Tap into the change:* events for fine-grained tracking
  • Build a plugin that extends the editor with this capability

Making it official: If this feature would benefit many users, consider opening a formal Feature Request on the GrapesJS repo with:

  • A detailed use case
  • Code example showing the desired behavior
  • Why this matters for your workflow

The core team is receptive to well-motivated feature requests backed by real use cases.

Related Questions and Answers

Continue research with similar issue discussions.

Paid Plugins That Match This Issue

Curated by issue keywords and label relevance to help you ship faster.

View all plugins

Loading paid plugin recommendations...

Free option

Check the open-source GrapesJS plugins on GitHub or run a quick search in our free catalog.

Browse free plugins →
Premium option

Premium plugins ship with support, regular updates, and production-ready features — save days of integration work.

Browse premium plugins →

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.