Fixing Cmd+Backspace in Firefox for Quill Lists
2025.11.20
As a Quill maintainer, I spend a lot of time looking at browser editing behavior.
Recently, some users reported a strange issue in Firefox on macOS: pressing Cmd+Backspace at the end of a list item deleted only the last character instead of everything back to the start of the line. The same shortcut worked fine in other browsers.
The Setup
Here is a minimal example that reproduces the bug:
<div contenteditable="true">
<ul>
<li><span contenteditable="false"></span>abc</li>
</ul>
</div>
Steps:
- Place the caret after
abc. - Press Cmd+Backspace.
Expected result:
abcdisappears.
Actual result in Firefox:
- Only
cdisappears.
For editors like Quill, this feels very wrong. Users expect Cmd+Backspace to clear the whole list item, not behave like a single Backspace.
What Firefox Is Doing
After digging through Gecko, I think the problem comes from the interaction between native macOS editing commands, line-edge selection, and Gecko's editable-range checks.
On macOS, Cmd+Backspace is not handled as an ordinary Backspace key. Cocoa reports it as the selector deleteToBeginningOfLine:, and Firefox maps that selector to Gecko's DeleteToBeginningOfLine command. Inside the editor, that command becomes eToBeginningOfLine / eDeleteToBeginningOfSoftLine.
For collapsed selections, Gecko asks the layout engine to extend the deletion range back to the beginning of the current line. That path goes through nsFrameSelection::CreateRangeExtendedToPreviousHardLineBreak, which uses PeekOffset with eSelectBeginLine.
With the markup above and the default layout, Gecko's line-edge calculation can land on the non-editable span before abc. The candidate deletion range is therefore not just the text abc; it also touches the contenteditable="false" span.
Before using an extended range, the HTML editor checks whether the range is editable. In current Gecko, this check is done by AutoClonedRangeArray::IsEditableRange. It requires the range boundaries to be in editable content and rejects non-editable replaced content. When the extended range starts at or crosses the non-editable span, Firefox refuses to use that extended range.
The result is that the line-delete command never gets a clean editable range from the caret back to the start of abc. In this case, Firefox behaves like a plain backward delete and removes only the last character.
Nothing is wrong with your editor code. Firefox is trying to protect the non-editable span, but that protection interacts badly with this shortcut and this layout.
Why display: flex Fixes It
One practical workaround is to make each list item a flex container:
li {
display: flex;
}
This small change restructures the layout in an important way:
- In a flex container, the text
abcsits inside an anonymous flex item. - The span becomes a separate flex item next to that anonymous box.
- When Firefox computes the line edge, it stays within the flex item that holds
abc. - The start of that flex item becomes the relevant line start for the text.
- The candidate delete range only covers
abc, which is fully editable. IsEditableRangeaccepts the range, so Firefox deletes the text back to the start of the item as expected.
From the user's point of view, Cmd+Backspace now behaves like it does in other browsers.
Why position: absolute and float Do Not Help
A natural idea is to pull the span away from the text with CSS. For example:
li > span {
position: absolute;
}
Another attempt is to float it:
li > span {
float: left;
}
In practice, these approaches do not reliably fix the Firefox behavior, because they do not remove the non-editable content from Gecko's editing and layout calculations.
Absolutely positioned content still has a placeholder frame at its original position, and Gecko's frame code has explicit logic for walking between out-of-flow frames and their placeholders. Floats are different internally, but they can still leave the non-editable frame participating at the start of the line from the editor's point of view.
So even if the span no longer appears inline in the way you expect, the line-edge computation can still land on layout data associated with that non-editable span. The candidate deletion range is then rejected by the editable-range check, and Cmd+Backspace still degenerates to deleting a single character.
Visually, the span may look like it has left the text, but the editor path has not necessarily escaped it.
Using display: contents as a Cleaner Workaround
If your non-editable span is just a wrapper or is empty, you can also use display: contents:
li > span {
display: contents;
}
In this case, the span does not create a box in the layout tree at all. Only its children remain, and there is no placeholder frame for Firefox to step on.
- The span itself no longer contributes a box for
PeekOffsetto land on. - The line edge for the editable text becomes the start of
abc. - The candidate delete range covers only editable text.
- The editor deletes everything from the caret to that text start.
The tradeoff is that you can no longer style the span itself. You cannot give it a background, border, width, or height because it no longer has a box.
What I Use in Practice
For Quill and similar editors, I prefer the flex-based fix. It keeps the markup simple and works even when the non-editable element needs visual styling.
- Use
display: flexon list items that contain non-editable decorations, such as checkboxes or icons. - Avoid relying on
position: absoluteorfloatto pull those decorations away from the text. - Consider
display: contentsonly when the wrapper is purely structural and does not need styles.
If your users are seeing Cmd+Backspace behave strangely in Firefox, this is likely the reason. Adjusting the layout is often enough to bring Firefox back in line with other browsers.