Web Development
Preventing layout shift when adding a border to an element's hover state
Adding a border to an element on hover? Try these options to prevent the page's appearance from jumping around.
2023-01-28
View the demo and explore the code from the gif above in my codepen example.
Layout shift: the problem with adding a border on hover
Waiting until hover to add a border to an element results in layout shift because it increases that elements height and/or width on the page. The button above demonsrates the problem. Once it enters a hover state, a border is added to its bottom edge which causes the elements below it to shift down to accomodate the new height. Having large sections of the page shifting around when an unrelated element is hovered isn’t a great user experience.
Adding a border on hover without layout shift
Let’s break down why this shift occurs. The CSS box model defines how an element’s space is allocated when laying out the page. This space is comprised of an element’s content, padding, border, and margin. If an element’s hover state increases or decreases these values, the layout will have to shift the page layout to accomodate the element’s new dimenstions. This is not necessarily a bad thing, but there are ways around it if a layout shift is not a desired outcome.
As with most cases in web development, there are multiple ways to address this problem. Here are two of many options:
Use a transparent border for the element’s initial state
A border’s space can be preallocated without being visible on the page by setting its width in the initial state and setting its color to transparent. Then on hover, set the border’s color. This technique was used in the button below.
.element {
border-bottom: 2px;
border-color: transparent;
}
.element:hover {
border-color: white;
}
Use a psuedo-element as the element’s border
By setting an ::after
psuedo-element for an element, you can absolutely postion that psuedo-element so it aligns with the bottom of the element and does not cause a layout shift. This technique was used in the button below.
.element {
position: relative;
}
.element:hover::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
border-bottom: 2px solid white;
}