Web browsers exhibit some inconsistencies when it comes to handling scrolled content. This is an attempt to categorize and explain them, and provide work-arounds and layout tests.
- Scrolling block (flow) elements and grid elements
- Scrolling flexboxes
- Scrolling in a shrinkwrapped (
minmax(0, 1fr)
withmax-height
) grid - Shifting down visible content in a scroll area
- Scrolling grid flex item
The following situation is where the layout differences below happen:
- Scrollbars have size (Windows-style or when traditional scrollbars are enabled on macOS)
overflow: auto
- Vertical overflow that creates a vertical scrollbar
- No constraint (
max-width
etc) in the horizontal direction
Note that browsers are consistent in the following:
overflow: scroll
always adds scrollbars to the size of the element- Horizontal scrollbars are always added to the size of the element
Firefox, Edge, and Safari (for the most part) shrink the element's inner width to fit the scrollbar and avoid forcing a relayout on the parent element. Chrome adds the scrollbar to the element's total width and does not shrink content, unless its outer size constraints are reached.
Chrome adds the vertical scrollbar to the total size of the block element, other browsers don't.
- Scrolling block as an inline-block
- Scrolling block as a grid item
- Scrolling block as a flex item
- Scrolling grid
- Set a fixed width on the scrolling element or some parent element to make everything layout with definite size
- Detect the shrink-width behavior and if it exists, add the scrollbar size
Safari and Chrome add the scrollbar to a flexbox's size. Safari initially subtracts the scrollbar until its inner content is laid out again (for example, by toggling flex
).
The best way is to just avoid this layout - if you need to scroll a flexbox, try wrapping it with something else that scrolls, like another flexbox and scroll the flex item.
These bugs happen while the user is scrolling.
In a "shrinkwrap" grid layout, Safari and Chrome discard the scroll position if content triggers a relayout while the user is dragging the scrollbar (bug)
Using minmax(0, 1fr)
along with max-height
and max-width
gives you a magical layout. You can make a container that grows as its content does and gets no larger, and also shrinks/scrolls when it hits a specified limit. Safari and Chrome have just one issue with this layout when using the mouse to drag the scrollbar.
- Dragging a shrinkwraped grid's scrollbar Chrome stops scrolling, Safari stops and resets scroll to top
- Focusing an input inside the shrinkwrapped grid Safari resets scroll to top
Detect if the width/height of the content has hit the constraint and if so, change max-width/max-height
to width/height
Chrome attempts to keep visible content in a scrolling viewport if it would be shifted down by relative positioning changes (div added before content, or height of a div before the content is changed)
Chrome probably added this feature to help with badly coded webpages that have ads which change size or load while the user is reading. Unfortunately this behavior can be a nightmare if you're implementing virtual scrolling or something similar.
- Use
overflow-anchor: none
. This is the best/fastest solution. - Replace the visible elements in the viewport with new ones when you update the content (e.g. use
:key
in Vue)
A grid as a flex item that is also a scroll root can sometimes change size in Chrome
If appended with JS, the layout will be wrong on the first pass, but when the user interacts in a way that causes layout inside the flex item (like appending a grid item) it will self-correct.
Chrome bug: 1042399