When you change the font-weight
of a font, the text will typically cause a bit of a layout shift. That’s because bold text is often larger and takes up more space. Sometimes that doesn’t matter, like a vertical stack of links where the wider/bolder text doesn’t push anything anyway. Sometimes it does matter, like a horizontal row where the wider/bolder text pushes other elements away a smidge.
Bolding text on mouse hover causes a layout shift that’s especially noticeable when elements start wrapping. Here’s a nifty trick: add a hidden pseudo element with the same text string but set it to the bold font size 🙌
— Ryan Mulligan (@hexagoncircle) July 20, 2020
See it on @CodePen: https://t.co/kBzZXqqtmi pic.twitter.com/kdZBTLQ0RD
Ryan’s technique is very clever. Each item in the list has a pseudo-element on it with the exact text in the link. That pseudo-element is visually hidden, but pre-bolded and still occupies width. So when the actual link text is bolded, it won’t take up any additional width.
It also sorta depends on how you’re doing the layout. Here, if I force four columns with CSS grid and text that doesn’t really challenge the width, the bolding doesn’t affect the layout either:
But if I were to, say, let those links flow into automatic columns, we would have the shifting problem.
Nice trick!
I’d like to mention another approach to this which would be to use a uniwidth (to not be confused with monospace) font where characters have the same width across all the weights. One such font I like is PT Root UI. I know this might not always work, and is obviously more limiting in terms of design choice, but on the other hand I think it’s a cleaner and less “hacky” solution.
What about the accessibility implications ? Wouldn’t screen readers read the text twice?
I’ve been using -wekit-text-stroke for this. It doesn’t look as good as “real” bold type, but usually isn’t bad under .1em, it may mean you don’t need to load an extra font weight, and despite being non-standard, it has support in every browser. Except IE obviously, but you can use @supports with an overide to show actual bold font, or not care that 1% won’t see that text as intended.
The text in the pseudo is display none, hidden color white or sth like that. So the user never sees it.. Just the browser knows its there and respects the space it occupies.
My thoughts exactly. This is likely not a good solution for those who are concerned with ADA compliance
Screen readers can’t read the content in pseudo elements, so it should be fine.
I would suggest to work with variable fonts that they don’t need any fix and they work perfect
I’ve been using before and after for this type of sizing for years… I’m pleased you mentioned it here. More people should know about this. :)
Using absolutely positioned pseudo elements with a relatively positioned main element can be handy in some cases too.
Is there anything wrong with using text-shadow for this purpose?
Not wrong, but bold and text-shadow aren’t really the same thing.
I know they aren’t the same thing, but visually you can achieve almost identical effect with text-shadow, not affecting the DOM at all.
yes ! it can be done with text shadow but I don’t think we can make same with using text-shadow as this idea.
What about letter-spacing? Maybe it’s possible to find the right amount to compensate for boldness – or maybe just close enough that it works for the typically small amount of letters on a button?
How about text-stroke? https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-text-stroke
Doesn’t work on mobile. Have to tap several times for it to bold
Thanks for this trick.. This shakey effect has bothered me for way over 6 months..
I use a text-shadow, which is more of a pseudo bold, but gets the job done, plus you can pick a different shade
Thanks Paul. I had the best results using your method with:
text-shadow: 0px 0px 1px var(–slate);
I wonder if the
contain
property could be used for this purpose:https://developer.mozilla.org/en-US/docs/Web/CSS/contain
If the text color is not fully black, which I tend to like, I have found that darkening the color on hover can accomplish the same goal as increasing font-weight, if the difference is great enough. Even if there are no layout issues, I prefer that things not jump. :)
Whats content: attr(data-text) / ” for?
It’s a fallback to an empty string – https://developer.mozilla.org/en-US/docs/Web/CSS/content#image_combined_with_text
But why this is needed here?
Depending on how bold you want your text to be scaling works because it doesn’t change the size. I’ve used it on several projects. We went from gray to white text on black and scaled it. The effect worked for us.
a:hover {
transform: scale(1.05);
}
This is by far my favorite solution to accomplish this. Pseudo elements are just that – they’re a CSS element but I wouldn’t consider them a true DOM element. Screen readers don’t read them so people shouldn’t be concerned about them reading the text twice.
But I also just wanted to comment and say as of Sept 2022, @media screen is deprecated and no longer supported https://www.powermapper.com/tests/screen-readers/content/media-query-speech/
This isn’t quite correct. Pseudo-elements are included in the element’s accessible name computation and are announced by browser/screen reader combinations that respect the spec.
In this case however this problem is solved by adding
visibility: hidden
, as the screen reader will ignore elements with that property set.