RTE misplaced when opened right after dropping `activeOnRender` component
Yes, I guess you're right. It doesn't actually make sense activating RTE without selecting the component (it probably makes sense with all other "activatable" blocks).
Read full answer below βQuestion
Version: 0.17.4
Are you able to reproduce the bug from the demo?
- Yes
- No
Steps to reproduce:
- Go to the demo
- Drop a "Text" block inside the canvas
- Click on the "Text" component that was just dropped
- The opened RTE is misplaced (cf attached screenshot)
What is the expected behavior?
The opened RTE should be positioned correctly
What is the current behavior?
The RTE is misplaced, it hides the "Text" component's content & can prevent editing properly (especially when using bigger custom RTE like CKEditor).
This only happens when the RTE is first opened & only if the dropped block is a "Text" component with activeOnRender option set to true.
This is caused by a miscalculation of the toolbar's position when its offsetHeight equals 0. Itself due to its parent (CanvasView.toolsEl) being set to display: none at this time (because rte.enable is called on drop when activeOnRender is true, which happens while the component is unselected as opposed to normal, hence its toolsEl still being set to display: none)
Proposed solution:
Selecting the component before triggering activeOnRender's active event seems to fix the issue.
Concretely, adding em.setSelected(result); before result.trigger('active'); here should do it, unless I'm missing something.
I could take care of the PR if this looks good to you :)
Are you able to attach screenshots, screencasts or a live demo?
- Yes (attach)
- No
Answers (4)
Yes, I guess you're right. It doesn't actually make sense activating RTE without selecting the component (it probably makes sense with all other "activatable" blocks).
Hi. Me and @ronaldohoch have found a workaround for this problem.
We trigger the scroll event when the RTE is enabled. Here's the code:
// Trigger scroll event from canvas so that grapesjs
// fix CKEditor position correctly
editor.on("rte:enable", () => {
editor.trigger('canvasScroll');
});
Hello, just updated the file from commit bb4a661, and i think it's so close, i'm using the grapesjs-ckeditor code and the first open of RTE, i'ts placed right:

But at the second time the rte is open, it's misplaced again... :/

I'll keep checking for it :/
<details> <summary>The code i'm get from grapesjs-ckeditor</summary> let c = {
options:{
sharedSpaces:''
}
};
let defaults = {
// CKEditor options
options: {},
// On which side of the element to position the toolbar
// Available options: 'left|center|right'
position: 'left',
};
// Load defaults
for (let name in defaults) {
if (!(name in c))
c[name] = defaults[name];
}
if (!CKEDITOR) {
throw new Error('CKEDITOR instance not found');
}
editor.setCustomRte({
enable(el, rte) {
// If already exists I'll just focus on it
if(rte && rte.status != 'destroyed') {
this.focus(el, rte);
return rte;
}
el.contentEditable = true;
// Seems like 'sharedspace' plugin doesn't work exactly as expected
// so will help hiding other toolbars already created
let rteToolbar = editor.RichTextEditor.getToolbarEl();
[].forEach.call(rteToolbar.children, (child) => {
child.style.display = 'none';
});
// Check for the mandatory options
var opt = c.options;
var plgName = 'sharedspace';
if (opt.extraPlugins) {
if (typeof opt.extraPlugins === 'string')
opt.extraPlugins += ',' + plgName;
else
opt.extraPlugins.push(plgName);
} else {
opt.extraPlugins = plgName;
}
if(!c.options.sharedSpaces) {
c.options.sharedSpaces = {top: rteToolbar};
}
// Init CkEditors
rte = CKEDITOR.inline(el, c.options);
/**
* Implement the `rte.getContent` method so that GrapesJS is able to retrieve CKE's generated content (`rte.getData`) properly
*
* See:
* - {@link https://github.com/artf/grapesjs/issues/2916}
* - {@link https://github.com/artf/grapesjs/blob/dev/src/dom_components/view/ComponentTextView.js#L80}
* - {@link https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getData}
*/
rte.getContent = rte.getData;
// Make click event propogate
rte.on('contentDom', () => {
var editable = rte.editable();
editable.attachListener(editable, 'click', () => {
el.click();
});
});
// The toolbar is not immediatly loaded so will be wrong positioned.
// With this trick we trigger an event which updates the toolbar position
rte.on('instanceReady', e => {
var toolbar = rteToolbar.querySelector('#cke_' + rte.name);
if (toolbar) {
toolbar.style.display = 'block';
}
editor.trigger('canvasScroll')
});
// Prevent blur when some of CKEditor's element is clicked
rte.on('dialogShow', e => {
const editorEls = grapesjs.$('.cke_dialog_background_cover, .cke_dialog');
['off', 'on'].forEach(m => editorEls[m]('mousedown', stopPropagation));
});
this.focus(el, rte);
return rte;
},
disable(el, rte) {
el.contentEditable = false;
if(rte && rte.focusManager)
rte.focusManager.blur(true);
},
focus(el, rte) {
// Do nothing if already focused
if (rte && rte.focusManager.hasFocus) {
return;
}
el.contentEditable = true;
rte && rte.focus();
},
});
// Update RTE toolbar position
editor.on('rteToolbarPosUpdate', (pos) => {
// Update by position
switch (c.position) {
case 'center':
let diff = (pos.elementWidth / 2) - (pos.targetWidth / 2);
pos.left = pos.elementLeft + diff;
break;
case 'right':
let width = pos.targetWidth;
pos.left = pos.elementLeft + pos.elementWidth - width;
break;
}
if (pos.top <= pos.canvasTop) {
pos.top = pos.elementTop + pos.elementHeight;
}
// Check if not outside of the canvas
if (pos.left < pos.canvasLeft) {
pos.left = pos.canvasLeft;
}
});
<details>Thanks for reporting this, @mcottret.
The issue with RTE misplaced when opened right after dropping activeOnRender component appears to be a race condition or state management timing problem. This typically happens when component lifecycle events and DOM modifications overlap, creating an inconsistent state.
What to try:
- Add a setTimeout wrapper to ensure the DOM has settled:
setTimeout(() => {
// your operation here
}, 0);
-
Check initialization order β make sure components are fully loaded before you interact with them
-
Use the editor's event system β listen to completion events:
editor.on('component:mount', (component) => {
// safe to interact with component here
});
Recommended next steps:
- Test with the latest GrapesJS version if you haven't
- Provide a minimal reproducible example (CodeSandbox) β this helps the team identify the root cause faster
- Include GrapesJS version, browser, and console errors in your report
Related Questions and Answers
Continue research with similar issue discussions.
Issue #3175
Selection & active RTE not cleared when component is removed programatically
Version: 0.16.30 Are you able to reproduce the bug from the demo? [x] Yes [ ] No Steps to reproduce:Select a text component in the demo's c...
Issue #3794
RTE Overlapping text In Basic Text Component when Width & Height are 100% of Canvas
GrapesJS version [X] I confirm to use the latest version of GrapesJS What browser are you using? Version 93.0.4577.82 (Official Build) (64-...
Issue #5378
Uncaught TypeError: Cannot read properties of undefined (reading 'Canvas') while dragging a component in Layer Manager Panel
GrapesJS version [X] I confirm to use the latest version of GrapesJS What browser are you using? Chrome v116.0.5845.97 Reproducible demo li...
Issue #4848
Component content is set to empty string after inline editing
GrapesJS version [X] I confirm to use the latest version of GrapesJS What browser are you using? Firefox v91.4.1 Reproducible demo link htt...
Paid Plugins That Match This Issue
Curated by issue keywords and label relevance to help you ship faster.
Loading paid plugin recommendations...
Check the open-source GrapesJS plugins on GitHub or run a quick search in our free catalog.
Browse free plugins βPremium plugins ship with support, regular updates, and production-ready features β save days of integration work.
Browse premium plugins βRelated tutorials
In-depth guides on the same topic.
Tutorial
Find the Right GrapesJS Plugin in Seconds: Smarter Discovery Is Live
We're shipping a set of discovery upgrades. New label filters, a proper compatibility switch for GrapesJS vs Studio, one-click and a smarter sort bar.
Tutorial
GrapesJS vs Webflow vs Tilda: What to Choose for Your Business in 2026
Choosing the right website platform in 2026 is no longer just about building a site
Tutorial
GJS Market 2.0 - Donations, Tracking, Labels and Better Product Discovery
Weβve rolled out a new set of GrapesJS marketplace updates across GJS Market, focused on improving how creators distribute products
Browse Plugin Categories
Jump directly to plugin category pages on the marketplace.