Link with a line after which change places on hover


3 min read

I recently had a design that included a link with had a line after the text. When hovering on the link the line should end up before the text.

The obvious answer to this was to have two elements positioned using flex. Then on hover just add flex-direction: row-reverse. Except you can't animate flex-direction.

The other idea is to use a pseudo element and set it to be right: 0 and on hover it'll become left: 0. Except you can't animate from right to left.

Here's how I ended up doing it.

First, the set up to make this work as a demo.

 <a href="#" class="link">Link goes here</a>

Here is just a simple link.

:root {
  --line-width: 80px;

body {
  display: flex;
  justify-content: flex-end;
  padding: 20px;

I'm setting the width of the line and the gap between the text and the line as a custom property as I'll be using it in multiple places. And setting the whole thing to be on the right, as this element is always positioned on the right in the design I was given. Then adding some padding so it's not all right up against the edge of the browser window.

.link {
  position: relative;
  padding-right: calc(var(--line-width) + var(--link-line-gap));
  font-size: 1.5rem;
  transition: padding 300ms ease;

Here I'm setting the link to be relatively positioned so the line after it can be positioned relatively to it.

Without the padding the text is right up to the edge of the body padding, which doesn't leave any space for the line. I know how wide the line is and how far away it is from the text as I've made those custom properties. So the right padding will be the sum of the two.

Then I've increased the font size to make sure it's visible in the example and added a transition to the padding as I'll be changing that on hover.

.link::after {
  content: '';
  position: absolute;
  left: 100%;
  top: 50%;
  width: var(--line-width);
  height: 2px;
  background-color: blue;
  transform: translate(-100%, -50%);
  transition: left 300ms ease, transform 300ms ease;

There are a few things going on here. Setting the left position to 100% means that the left edge of the pseudo element is at the right edge of the link. Except that I added padding on the right edge of the link, which means this will be way too far to the right. The transform brings it back by its own length, so its right edge is at the right edge of the link.

Then I vertically centre it, set a width, height and colour. And transition the two properties I'll be changing on hover.

Then we get into the hover states.

.link:hover {
  padding-left: calc(var(--line-width) + var(--link-line-gap));
  padding-right: 0;

Moving the line over to the left means that it now needs the same padding on the left to fit the line in if the browser is small. Removing the padding on the right moves the text over by the width of the line and the gap between them.

.link:hover::after {
  left: 0;
  transform: translate(0, -50%);

Now I can move the link over to the left and update the transform so it's no longer moved left by its own width.

And that's all there is to it! Here's the whole code: