Vincent Zhang

Engineering, Design & Productivity

CSS: thicker border on hover


This article reviews some solutions on the CSS thicker border on hover problem. If you want a TL;DR and my opinionated best solution, please see the last section of this article.

The problem

This is a classic problem in CSS: you have a HTML element, let’s say a very generic <div> for the rest of our article. You want to apply a thick border when user hovers on it. The simple solution is to add a border on the pseudo-class :hover. However, because we are adding a border on hover state that doesn’t exist before, this usually introduces a change of the div’s fluidity (an element’s outer height and width). As a result, it will move and also disrupting its surrounding elements.

Here’s a code sample showcasing the problem. Try hover on the light green square and you’ll notice the problem.

See the Pen CSS thicker border on hover – base case by Vincent (@karutoCodePen.

Solution #1: always have a border

The essence of our problem is that a border that only appears on hover state makes the div gain some extra width and height. In order to not let this dimension change happen at all, we will just let the div always have a border, the only change on hover is the border’s color. You can set the border color to either be the same as your background, or use the value transparent.

Here’s a code sample showcasing the “always have a border” solution.

See the Pen CSS thicker border on hover – base case by Vincent (@karutoCodePen.

Solution #2: negative margin or padding

The first solution, “always have a border” approach, would not work if your div has a non-transparent border normally.

Now the question becomes “how to change border thickness without moving the div”. Here I introduce a different way of thinking: instead of “faking” the normal border to accommodate the hover border, we will let the div itself accommodate the border. We will set the div’s margin negative, offsetting the exact thickness of the border on both normal and hover states.

Pro tip: you can use the CSS calc() function to dynamically generate this instead of hard-coding another margin value into your code.

Here’s a code sample showcasing the “negative margin” solution.

See the Pen CSS thicker border on hover – base case by Vincent (@karutoCodePen.

This negative value approach also applies to padding. The question of using padding or margin depends on what governs the div’s positioning in normal state. Remember: we are writing reactive CSS here.

Here’s a code sample showcasing the “negative padding” solution. Notice I did not have any margins set in this demonstration.

See the Pen CSS thicker border on hover – base case by Vincent (@karutoCodePen.

Solution #3: pseudo element

The second solution, “negative margin or padding” approach, would not work if your div already uses margin and padding normally to achieve positioning results, because now you can’t freely modify them and the negative calculations become tricky really fast.

Here I have another solution that uses ::before which places a special pseudo element as the first inner child of the original element.

Without further ado, here’s a code sample showcasing the “pseudo element” solution.

See the Pen CSS thicker border on hover – base case by Vincent (@karutoCodePen.

Let’s talk about what the CSS is doing in detail:

We give the ::before pseudo element a content definition, so that it occupies a node.

Position absolute gives us precise positioning control relative to the next parent element with relative or absolute positioning, which is the original div itself. By doing so we are also removing the pseudo element from the flow of the page, so that its positioning won’t affect surrounding elements and vice versa.

Width and height at 100% will make sure the pseudo element is the exact size of the original div.

Finally, we are applying the hover border and negative margin trick from solution #2, only this time, we won’t be disrupted!

To sum up, I believe this is a good solution if you don’t want to mess with a div’s margin or padding, which is quite frequently used for normal purposes. However, the downside is using CSS pseudo element, which to some people is even less readable than negative margin in solution #2.

You could argue that this solution will be challenged if a div’s ::before pseudo element is already used to achieve other CSS effects and we can’t modify its properties freely. I would counter-argue that while it’s possible, it is such a rare case that you’ll have that and a thicker border on hover at the same time.

But perhaps you have an even better solution which will trump this scenario? What are your tips and tricks when dealing with the div moving with a thicker border on hover? Feel free to share in the comment section below.

Leave a comment