A button with an arrow that moves on hover


3 min read

I have a button with an arrow to the left of it. On hover, the arrow should move to the right and the whole button should have the same background colour as the arrow. What it needs to look like is:



Button on hover

button on hover

The HTML is pretty simple for this:

<button>I am a button</button>

You might think it would make sense to add the arrow in here. But in the situation I had I couldn't do that, I could only add it with a pseudo-element.

Now we add the CSS for the button:

button {
  position: relative;
  padding-block: .5em; /* top and bottom padding */
  padding-inline-start: 2em; /* left padding */
  padding-inline-end: 1em; /*right padding */
  border: none;
  border-radius: .25em;
  background-color: transparent;
  font-size: 1.5em;
  cursor: pointer;

I'm using logical properties here. Mostly because I'm very used to using padding-block (and padding-inline) as a shorthand and wanted to keep this consistent. Buttons have a background colour and border by default and don't have an obvious cursor to say you click on it (which seems totally illogical to me). I've just added a bit of a rounded corner to make it look prettier once you hover.

Then we add the arrow:

button::before {
  position: absolute;
  content: '->';
  background-size: 1em;
  inset-block-start: 50%;
  inset-inline-start: .25em;
  transform: translateY(-50%);
  padding: .25em;
  border-radius: 50%;
  background-color: pink;
  line-height: 1;

When I did this for real I had an arrow svg given to me. But just using some text is enough to give the gist, even if it doesn't look as pretty (and isn't completely square, therefore the circle around it isn't completely circular). I've given it some positioning so it's to the left of the text.

Now for the hover:

button:hover {
  background-color: pink;
button:hover::before {
  inset-inline-start: calc(100% - 1.75em);

If we'd used 100% here then it would put the very left edge of the arrow at the right edge of the button. If this was an image then I'd have specified how big the image would be and could therefore move it back by that size. Because it's just text, I've estimated how wide the text is.

The trouble we now have is that the button is on top of the text. So we want to swap the padding around on the left and right. Which is a good case for using custom properties:

:root {
  --button-padding: 1em;
  --space-for-arrow: 2em;

button {
  /* replace original inline paddings: */
  padding-inline-start: var(--space-for-arrow); /* left padding */
  padding-inline-end: var(--button-padding); /*right padding */

button:hover {
  padding-inline-start: var(--button-padding); /* left padding */
  padding-inline-end: var(--space-for-arrow); /*right padding */

Now it's working, we just need some transitions - this is the reason we continued to use the same property to position the arrow.

button {
  transition: padding 250ms ease, background-color 250ms ease;

button::before {
  transition: inset-inline-start 250ms ease;

And it all works! Here is the final code: