CSS: bolding some text without changing its container's size

I have a horizontal navigation menu, which is basically just a <ul> with the elements set side-by-side. I do not define width, but simply use padding, because I would like the widths to be defined by the width of the menu item. I bold the currently-selected item.

The trouble is that in bolding, the word becomes slightly wider, which causes the rest of the elements to shift slightly to the left or right. Is there a clever way to prevent this from happening? Something along the lines of telling the padding to ignore the extra width caused by the bolding? My first thought was to simply subtract a few pixels from the padding of the "active" element, but this amount varies.

If possible I'd like to avoid setting a static width on each entry and then centering as opposed to the padding solution I currently have, in order to make future changes to the entries simple.


I had the same problem, but got a similar effect with a little compromise, I used text-shadow instead.

li:hover {text-shadow:0px 0px 1px black;}

Here's a working example:

body {
  font-family: segoe ui;
}

ul li {
  display: inline-block;
  border-left: 1px solid silver;
  padding: 5px
}

.textshadow :hover {
  text-shadow: 0px 0px 1px black;
}

.textshadow-alt :hover {
  text-shadow: 1px 0px 0px black;
}

.bold :hover {
  font-weight: bold;
}
<ul class="textshadow">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li><code>text-shadow: 0px 0px 1px black;</code></li>
</ul>

<ul class="textshadow-alt">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li><code>text-shadow: 1px 0px 0px black;</code></li>
</ul>

<ul class="bold">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
  <li><code>font-weight: bold;</code></li>
</ul>

jsfiddle example


The best working solution using ::after

HTML

<li title="EXAMPLE TEXT">
  EXAMPLE TEXT
</li>

CSS

li::after {
  display: block;
  content: attr(title);
  font-weight: bold;
  height: 1px;
  color: transparent;
  overflow: hidden;
  visibility: hidden;
}

It adds an invisible pseudo-element with width of bold text, sourced by title attribute.

The text-shadow solution looks unnatural on Mac and doesn't utilize all the beauty that text rendering on Mac offers.. :)

http://jsfiddle.net/85LbG/

Credit: https://stackoverflow.com/a/20249560/5061744


The most portable and visually pleasing solution would be to use text-shadow. This revises and shows examples of Thorgeir's answer using Alexxali's and my own tweaks:

  li:hover { text-shadow: -0.06ex 0 currentColor, 0.06ex 0 currentColor; }

This puts tiny "shadows" in your font's current color (use your font's color name/code in place of currentColor if necessary) on both sides of each letter using units that will scale properly with font rendering.

warningWarning: px values do support decimal values, but they won't look so great when the font size changes (e.g. the user scales the view with Ctrl++). Use relative values instead.

This answer uses fractions of ex units since they scale with the font.
In ~most browser defaults*, expect 1ex8px and therefore 0.025ex0.1px.

See for yourself:

li:hover      { font-weight: normal!important; text-shadow: none!important; }
.shadow0      { text-shadow: inherit; }
.shadow2      { text-shadow: -0.02ex 0 currentColor, 0.02ex 0 currentColor; }
.shadow4      { text-shadow: -0.04ex 0 currentColor, 0.04ex 0 currentColor; }
.shadow6      { text-shadow: -0.06ex 0 currentColor, 0.06ex 0 currentColor; }
.shadow8      { text-shadow: -0.08ex 0 currentColor, 0.08ex 0 currentColor; }
.bold         { font-weight: bold; }
.bolder       { font-weight: bolder; }
.after span   { display:inline-block; font-weight: bold; } /* @SlavaEremenko */
.after:hover span  { font-weight:normal; }
.after span::after { content: attr(title); font-weight: bold;
                     display: block; height: 0; overflow: hidden; }
.ltrsp        { letter-spacing:0px; font-weight:bold; } /* @Reactgular */
.ltrsp:hover  { letter-spacing:1px; }
<li class="shadow0">MmmIii123 This tests shadow0 (plain)</li>
<li class="shadow2">MmmIii123 This tests shadow2 (0.02ex)</li>
<li class="shadow4">MmmIii123 This tests shadow4 (0.04ex)</li>
<li class="shadow6">MmmIii123 This tests shadow6 (0.06ex)</li>
<li class="shadow8">MmmIii123 This tests shadow8 (0.08ex)</li>
<li class="after"><span title="MmmIii123 This tests [title]"
                   >MmmIii123 This tests [title]</span> (@SlavaEremenko)</li>
<li class="ltrsp"  >MmmIii123 This tests ltrsp (@Reactgular)</li>
<li class="bold"   >MmmIii123 This tests bold</li>
<li class="bolder" >MmmIii123 This tests bolder</li>
<li class="shadow2 bold">MmmIii123 This tests shadow2 (0.02ex) + bold</li>
<li class="shadow4 bold">MmmIii123 This tests shadow4 (0.04ex) + bold</li>
<li class="shadow6 bold">MmmIii123 This tests shadow6 (0.06ex) + bold</li>
<li class="shadow8 bold">MmmIii123 This tests shadow8 (0.08ex) + bold</li>

Hover over the rendered lines to see how they differ from standard text.

Alter your browser's zoom level (Ctrl++ and Ctrl+-) to see how they vary.

I added two other solutions here for comparison: @Reactgular's letter spacing trick, which doesn't work so well since it involves guessing font width ranges, and @SlavaEremenko's ::after drawing trick, which leaves awkward extra space so the bold text can expand without nudging neighboring text items (I put the attribution after the bold text so you can see how it does not move).


In the future, we'll have more variable fonts capable of things like changing font grade via font-variation-settings. Browser support is ramping up (Chrome 63+, Firefox 62+) but this still requires more than just standard fonts and few existing fonts support it.

If you embed a variable font, you'll be able to use CSS like this:

/* Grade: Increase the typeface's relative weight/density */
@supports (font-variation-settings: 'GRAD' 150) {
  li:hover { font-variation-settings: 'GRAD' 150; }
}
/* Failover for older browsers: tiny shadows at right & left of the text
 * (replace both instances of "black" with the font color) */
@supports not (font-variation-settings: 'GRAD' 150) {
  li:hover { text-shadow: -0.06ex 0 black, 0.06ex 0 black; }
}

There is a live demo with a slider to play with various grades on the Mozilla Variable Fonts Guide. Google's Introduction to variable fonts on the web has an animated GIF demonstrating a toggle between a high grade and no grade:

animated Amstelvar Alpha font demo with toggling grade axis