Animating pseudo-elements

Pseudo-elements are like getting extra DOM elements for free. They allow us to add extra content, decoration and more to our pages without adding extra HTML, and they can be animated. In this post we’ll use a pseudo-element to add a little visual flair to a button.


Within CSS, we can specify a pseudo-element using ::before or ::after. The pseudo-element is then inserted within your element, between the element and any content. Since it acts as an element of it’s own, it can be styled, positioned and more. The code looks a bit like this:

.pebble::before {
.pebble::after {

At this point, our .pebble element has two virtual elements attached, and we can style them as needed.

Note on “::” versus “:”

It’s generally accepted that we use the double colon :: to denote pseudo-elements (as opposed to pseudo-classes like :hover, :first-child). If you’re adding IE8 support, it’s best to use the single : instead. All other browsers and newer versions of IE support the double.

Don’t forget “content”

When adding pseudo-elements, one thing to keep in mind is the need to specify the content property before they can be made visible on the page. As the pseudo-element is created in an content-less state, we can trick it into appearing by using an empty content like so:

.pebble::before {
  content: ""
  ... more styling goes here...

This should make sure the element is visible on screen.

Example: Shiny button

For this example we’ll use a pseudo-element to create a shiny effect on hovering over a button. Here’s an example of it in action (hover or tap to see effect).

To begin, some HTML:

<button>Oooh SHINY</button>

Since we’re using pseudo-elements, we won’t need any more HTML than this to get going. You may want to add a class to the button if styling more than one type on a page, but for now we’ll use the generic element for simplicity.

Adding the sheen effect

The shiny sheen effect is a linear gradient that passes across the button. To create this we’ll use the after pseudo-element, and position it in a beginning position outside the button:

button::after {
  content: '';
  position: absolute;
  top: -50%;
  right: -50%;
  bottom: -50%;
  left: -50%;
  background: linear-gradient(to bottom, rgba(229, 172, 142, 0), rgba(255,255,255,0.5) 50%, rgba(229, 172, 142, 0));
  transform: rotateZ(60deg) translate(-5em, 7.5em);

The sheen effect is made up of a gradient from the button colour to white and back again. At this point it is now sitting outside the button.

We’ll need to hide the sheen layer so that it only shows on hover. To do this we’ll set the button’s overflow property to hidden. Since the pseudo-element is within the button, this means it’s positioning outside the button won’t be visible.

button {
  background: #e5ac8e;
  color: #fff;
  font-size: 14px;
  border-radius: 0.5em;
  padding: 0 1em;
  position: relative;
  overflow: hidden;
  line-height: 32px;

I’ve put in some other styling also to give the button it’s own look. One thing to point out, is the use of position: relative. I’ve added this property so that the absolutely positioned pseudo-element will exist within the button. Without specifying the position, an absolutely positioned item will position within a parent element.

Adding the animation

Since we’ll be using an animation in this example, there are two steps involved. The first is to tell the browser to use an animation on hover. Then we’ll set out exactly what that animation is using keyframes.

Adding the hover state can be done by stacking the after onto the hover like so:

button:hover::after, button:focus::after {
  animation: sheen 1s forwards;

Here we are telling the browser than on hover, the after pseudo-element is to have an animation. The animation, called sheen, lasts one second and stops at the end without repeating.

The order matters here. Using ::after:hover won’t work as it’ll be telling the browser to react to the hover state of the pseudo-element itself.

I’ve also added the focus state. This means that viewers tabbing through the page will see the sheen effect as well, without needing to hover. (Thank you, Šime Vidas, for the tip)

Let’s specify keyframes for this animation:

@keyframes sheen {
  100% {
    transform: rotateZ(60deg) translate(1em, -9em);

We only need one keyframe in this case. Since the starting position (0%) is implied by the pseudo-element’s starting position, we only describe the end position. In this case the end position is across at the top-right of the button. The browser will then animate the sheen effect across for us.

Browser considerations

The animation property is well supported, as are pseudo-elements. It’s always good to make sure and include -webkit and -moz prefixes for the keyframes and any transforms.