Max SchmittMS
15th August 2020

Bold active-states without layout jumps

Today we'll be looking at a common UI pattern: Making the active item in a navigation bar or menu have a font-weight: bold while all other items have a font-weight: normal.

<style>
.nav-item {
font-weight: normal;
}
.nav-item.is-active {
font-weight: bold;
}
</style>
<nav>
<a class="nav-item is-active">Home</a>
<a class="nav-item">Projects</a>
<a class="nav-item">Blog</a>
</nav>

It's pretty straight-forward but a simple implementation like this comes with a little smudge of dirt:

When the nav-items are displayed in a row, the layout will jump slightly whenever the active item changes. Pay attention to the "Projects" link:

Argh... it just feels a little "off".

The problem is that by changing the font-weight to bold, the element's size increases slightly, causing all the other items to move a few pixels.

How to prevent the layout shifts

To get around these layout jumps, we have to render all nav-items at their maximum widths always.

Now the width of the nav-items depends on their text content. To render them at their maximum widths without JavaScript, we can simply render the text-content with font-weight: bold always but hide this text.

We then use an additional absolutely-positioned text-layer that switches from font-weight: normal to font-weight: bold to display the actual text content without affecting the flow of the layout.

<style>
.nav-item {
font-weight: normal;
position: relative;
}
.nav-item.is-active {
font-weight: bold;
}
.nav-item .label {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
width: 100%;
height: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>
<nav>
<a class="nav-item is-active">
<!--
Always render the text bold, but hide it visually. Using
`visibility: hidden` will also hide it for assistive technology.
This causes the `nav-item` to take up the width that it has
in its active state.
-->
<span style="visibility: hidden; font-weight: bold;">Home</span>
<!--
Render a visible label, using absolute positioning and flex
to position it in the center of the nav-item element.
This label will change `font-weight` depending on its
active-state but won't affect layout because it's
`position: absolute;` It will also never overflow because
the `nav-item` container already has the largest possible
width.
-->
<span class="label">Home</span>
</a>
<a class="nav-item">
<span style="visibility: hidden; font-weight: bold;">Projects</span>
<span class="label">Projects</span>
</a>
<a class="nav-item">
<span style="visibility: hidden; font-weight: bold;">Blog</span>
<span class="label">Blog</span>
</a>
</nav>

It's a little more markup but the good thing is that it requires no JavaScript and does not cause any accessibility issues.

Check out the result:

Isn't that a way calmer experience?

Little tweaks like this don't take a lot of extra time to implement but they really add up to a better user experience overall.

I hope you found this post helpful! You can find a working demo and code on CodePen.


Need help on a project?

I'm available for freelance work.
Send me an email! Or find out more about my services.