:before and :after pseudo classes with transform effects on hover over buttons or links


I have two buttons with secondary tags inside them and a pseudo class that has transforms applied on hover. They are all working fine. They are built in a different way and there’s something I’d like to understand.

The first one has a span within the button. What I don’t understand is why I have to give the span within it the position of relative. If not, the hover effect covers the text.

Here’s the demo: http://codepen.io/CarolinaSawney/pen/EjJeWG

The second button is built also with a several i tags within the button. They hold the text. But in this case this secondary element doesn’t require the position of relative. And the text shows anyway. Instead there’s a z-index on the pseudo element that keeps the content at the top of the stack.

And the second demo: http://codepen.io/CarolinaSawney/pen/yYBMJY

The problem is when i tried to build the first button with the second technique, it doesn’t work.

If you’d prefer to have the code here, here’s the first one:

<link href='http://fonts.googleapis.com/css?family=Lobster|Bree+Serif' rel='stylesheet' type='text/css'>
<button name="button"><span>Transform:hover</span></button>

body {
  padding: 7em;

/* Create a button that doesn't show all the fake strip on hover and also makes it appear gradually */ 

button {
  position: relative;
  display: inline-block;
  vertical-align: top;
  overflow: hidden;  
  width: 15em;
  padding: 1.2em;
  transition: .3s;
  padding: 15px 30px;
  border: solid 2px black;
  border-radius: 5px;
  font: bold 1em 'Bree Serif';
  text-transform: uppercase;
  text-decoration: none;
  letter-spacing: 1px;
  color: rgb(0, 0, 0);
  background-color: #ffff00;

/* Position a skewed strip that moves to the right on hover */ 

button:before {
  content: "";
  position: absolute;
  padding: 0.1em;
  margin: 0 1em 0 0;
  top: -3%;
  right: 100%; /* From the right, a hundred to the left */ 
  height: 100%;
  width: 120%; /*Needs a bit more because it's skewed */ 
  background-color: crimson;
  transform: skewX(-30deg);
  transition: inherit;


/* Move the transform to the right */ 

button:hover:before {
  right: -20%;

/* Make the text readable on hover */ 

button:hover {
  color: #eaeaea;

/* Send the text to the front when the strip covers the button */ 

button span {
  position: relative;

And the second one:

 <link href='http://fonts.googleapis.com/css?family=Noticia+Text' rel='stylesheet' type='text/css'>

<a class="btn" href="#link">

html {
    font-size: 62.5%;

body {
    font-family: 'Noticia Text', serif;
    font-weight: bold;
    text-align: center;
    padding: 7% 5%;
    background-color: #eaeaea;

.btn {
    font-size: 1.4rem;
    text-transform: uppercase;
    letter-spacing: .2rem;
    text-decoration: none;
    overflow: hidden; 
    display: inline-block;
    padding: 0 4rem 0 3.8rem;
    color: #333;
    background: #ffff00;
    cursor: pointer;
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);

/* Create filling for the hover effect and place it under the button. The z-index keeps the text to the top when the strip comes up on hover */ 

.btn:after {
    content: "";
    display: block;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: -1;
    height: 150%;
    background: #5ef7c0;
    pointer-events: none;
    -webkit-transform: translate3d(0, 45px, 0);
    transform: translate3d(0, 45px, 0);
    -webkit-transition: all 0.3s 0s cubic-bezier(0.455, 0.03, 0.515, 0.955);
    transition: all 0.3s 0s cubic-bezier(0.455, 0.03, 0.515, 0.955);
.btn i {
    font-style: normal;
    line-height: 4.5rem;
    display: inline-block;
    height: 100%;
    padding-left: 1px;
    padding-right: 1px;
    -webkit-transition: all 0.3s cubic-bezier(0.19, 1, 0.22, 1);
    transition: all 0.3s cubic-bezier(0.19, 1, 0.22, 1);
.btn i:hover {
    -webkit-transform: translate3d(0, -6px, 0);
    transform: translate3d(0, -6px, 0);
.btn:hover {
    color: #333;

/* Move the :after block up on hover */
.btn:hover:after {
    -webkit-transform: translate3d(0, 0, 0);
    transform: translate3d(0, 0, 0);

Thanks a lot!!!

Transforms create a new stacking context (as does opacity, perspective, filter and a few other new css3 properties). In these cases the background of the element is atomic in that no children can pass underneath.

In your first example no stacking context is present so position:relative is added but if you then add z-index -1 to the overlay it will go behind the background of the parent which is why you needed it on the child instead.

I need a translation for that…can you explain it in plain English? What do you mean by atomic?

I also don’t understand why you say no staking context is present, as I also have a first element, a pseudo class after it and a second element.

Thanks in advance.

Sorry, I assumed from the complexity of code you were using that the terms would be familiar to you but I guess you may have copied and pasted from somewhere else.

In css terms ‘atomic’ means that the element is inescapable by its children in the sense that they can’t escape its ‘stacking context’. A stacking context is when an element becomes the container for positioned children. If you want to absolutely place an element in respect to its parent container then generally you need to have position:relative applied. The position:relative provides a starting point for the child elements to base their co-ordinates on.

The default z-index of a positioned element is auto which means that it is not ‘atomic’ and a child may sit under the parents background if given a suitable negative z-index. However once you apply a z-index other than auto to the parent the parent then becomes ‘atomic’ and all children must appear on top of the parents background (should they overlap).

Transformed elements are automatically atomic and automatically create stacking contexts so no matter what you do the child cannot go below the parent. In your second example this allowed the :pseudo element to be placed under other content with z-index:-1 while not going under the parents background.

In the first example if you set a z-index of zero (z-index:0) on the button and then set a z-index of -1 on the :pseudo element you could then remove the position:relative from the span.

Hope that’s a bit clearer but if not I’ll try an explain in a little more detail later as I am just off out :slight_smile:


That’s very clear, thanks. Two more questions in relation to your reply.

You wrote: “If you want to absolutely place an element in respect to its parent container then generally you need to have position:relative applied”. In what cases you don’t need to do that? I’ve seen examples of buttons where they don’t use position relative in the container and position absolute in the child element.

And also z-index=0 is the same as z-index: auto?

The only cases where you don’t need to do that are when the elements create their own stacking context which are when they have the special properties I mentioned above (opacity, perspective, filter and a few other new css3 properties that you probably won’t run into normal use).

Also you won’t need to add position:relative to the parent if the absolute child element is not positioned with co-ordinates (i.e. auto positioned). In that case its position will be the current position it would have been in the flow of the document and at that point it becomes absolutely positioned and outside the flow of the document. That means the element will most likely keep track with the parent but is not guaranteed and it is safer to add position:relative to the parent just to make sure.

So generally when you want to place a child absolutely in respect of its parent (and not the viewport) you would add position:relative to the parent. Then when you want to control the stacking context and contain the stacking to that parent element you apply a z-index other than auto.

No. z-index:0 (or any z-index value (positive or negative) apart from ‘auto’) creates this ‘atomic’ stacking context I keep mentioning while an ‘auto’ setting (the default) allows the child elements to interact with elements outside of that parents context. For a positoned element the default is z-index:auto and you don;t need to state it. Older versions of IE didn’t understand auto and made all positioned elements atomic anyway.

It is a bit complicated I agree and the rules are slightly more complex with all the new css3 transforms and indeed some browsers have changed the context that fixed positioned elements produce (but I’ll leave that for another day) :smile:


This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.