I've been interested in the reflections (or appearances of reflections) in pure CSS. In this post, I'll go through my steps in playing round with creating DOM elements that appear to have reflections.

Let's start with just a button


<div class="reflect-button">Button</div>

Button

.reflect-button {
    cursor: pointer;
    font-size: 3rem;
    display: inline-block;
    background: white;
    color: #fd4444;
    border: #fd4444 0.125em solid;
    border-radius: 0.2em;
    padding: 0.2em 1.1em;
    margin: 0.2em;
}

I've added some padding, border and border-radius to give this div the basic appearance of a button. The display: inline-block allows elements to lay inline, but also have margin and padding, which is perfect for buttons.

Using em for the padding allows us to change the font-size: and the padding adjusts with it.

Now we can add a glowing effect to the text and button.

Button

One text-shadow
Button

Two text-shadows
Button

Text shadows and outer box-shadow
Button

Text shadows and box-shadows

.reflect-button {
    font-size: 3rem;
    display: inline-block;
    color: #fd4444;
    border: #fd4444 0.125em solid;
    border-radius: 0.2em;
    padding: 0.2em 1.1em;
    margin: 0.1em;

    text-shadow: 0 0 0.125em rgba(246, 73, 73, 0.66), 
                 0 0 0.5em #fd4444;

    box-shadow: inset 0 0 0.5em 0 #fd4444, 
                0 0 0.5em 0 #fd4444;
}


I've added two text-shadow: to the text. The first one gives the text itself a glow around it, and the second brightens text itself, giving the appearance that it is lit from the back.

I've also added two box-shadow: tags to the div. One of them gives a glow around the div, and the second one has the inset keyword which adds the shadow inside of the element. That makes it appear to glow all around.

Now we can adding the components for the reflection

Button

This involves CSS pseudo-elements, to insert something before the element. Whenever using pseudo-elements, you need to have a content property. In this case, I just want the content to be blank. I've added position: absolute to the pseudo-element just to make positioning a bit easier, and then also added position: relative to the .reflect-button itself. Just for demonstration purposes I have made this element just a blue box that fills the container.


.reflect-button {
...
    position:relative;
}

.reflect-button::before {
    content: '';
    position:absolute;
    background: blue;
    top:0;
    left:0;
    width:100%;
    height:100%;
}


Now we can apply some transforms to this pseudo-element.

Button


.reflect-button::before {
    content: '';
    position:absolute;
    background: #fd4444;
    top:120%;
    left:0;
    width:100%;
    height:100%;
    
    transform: perspective(1.5em) 
               rotateX(42deg) 
               translateZ(0.6em) 
               scale(1, 0.2);

    filter: blur(1em);
}

This uses the perspective transform. The perspective transform has to be declared first, before any of the other transforms. The rotateX() transform rotates that element, and the perspective() transform tells it the axis about which to rotate. I then use translateZ() just to move it up slightly and then scale() in order to just make it slightly smaller, while preserving the angles.

Then the final piece is just to add the blur() filter:

Button

Looks good, it gives the appearance of light reflecting off a surface. But what about the text? Well, that gets a bit trickier, and requires some slight bodging..

We'll have to change the markup to have a parent container, and then also add a data-text property to the child container.

<div class="reflect-button-container">
    <div class="reflect-button" data-text="Button">Button</div>
</div>

This data-text property is something we can grab in CSS. I wasn't able to grab the actual div content text in pure CSS, but data properties can be accessed.

Button

Using another pseudoelement, we can duplicate the text using content: attr(data-text.


.reflect-button-text {
    position:relative;
}
.reflect-button-text::before {
    content: attr(data-text);
    color: white;
    position: absolute;
    top: 50%;
    left: 50%;
    width: 100%;
    height: 100%;
}

Now we need to rotate this text, similarly to how to we did for the box. However, we want to transform from the bottom.

Button

.reflect-button-text::before {
    content: attr(data-text);
    position: absolute;
    color:white;
    top: 0;
    left: 50%;
    width: 100%;
    height: 100%;
    transform-origin: bottom;
    transform: perspective(1.5em) rotateX(180deg) translateZ(-0.8em) scale(1, 0.2);
    line-height: 0.865em;
}

I've also reduced the top: property, because it affects the translateZ() transform. Fiddling with the perspective() and scale() properties produces some fun results too. We can now bring back all the properties we added for the border and reflect box we did before also. This is why I added a container div element, so that I could address a ::before element on that too.

Button

The complete CSS for this effect


.reflect-button-container {
    position:relative;
    cursor: pointer;
    font-size: 3rem;
    display: inline-block;
    color: #fd4444;
    padding: 0.2em 1.1em;
    margin: 0.3em 0.2em 1.5em;
}

.reflect-button-container::before {
    content: '';
    position: absolute;
    background: #fd4444;
    top: 60%;
    left: 0;
    width: 100%;
    height: 100%;
    transform: perspective(1em) rotateX(49deg) 
        translateZ(0.1em) scale(1, 0.2);
    filter: blur(1em);
    z-index: -1;
}


.reflect-button-text {
    position:relative;
    cursor: pointer;
    font-size: 3rem;
    display: inline-block;
    color: #fd4444;
    border: #fd4444 0.125em solid;
    border-radius: 0.2em;
    padding: 0.2em 1.1em;
    margin: 0.3em 0.2em 0.1em;
    text-shadow: 0 0 0.125em rgba(246, 73, 73, 0.66), 
        0 0 0.5em #fd4444;
    box-shadow: inset 0 0 0.5em 0 #fd4444, 
        0 0 0.5em 0 #fd4444;
}

.reflect-button-text::before {
    content: attr(data-text);
    color: rgba(0, 0, 0, 0.27);
    position: absolute;
    text-shadow: 0 0 20px rgba(0, 0, 0, 0.22);
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    transform-origin: bottom;
    transform: perspective(1.5em) rotateX(180deg) 
        translateZ(-0.9em) scale(1, 0.2);
    line-height: 0.865em;
    filter: blur(0.02em);
}

We can also avoid the whole pseudoelement stuff by using -webkit classes. Note that these features are highly non-standard and will not work the same (or at all!) across devices or browsers. However, if it works on your device, it does look pretty neat.

Button

We use all the general styling that we did above, and then also set the background using the element() function on the ID of that element.

We can then use the -webkit-box-reflect to reflect that element, apply a gradient and get the look of the reflections. I've not been able to reproduce the same angle and perspective as we did with the transform above, but presumably using this class on a separate element and transforming that might work.

<div class="wkreflect">Button</div>

.wkreflect {
    display: inline-block;
    border: #fd4444 0.125em solid;
    color: #fd4444;
    border-radius: 0.2em;
    margin-bottom:5em;
    position: relative;
    top: 15%;
    left: 50%;
    transform: translateX(-50%);
    
    font-size: 3rem;
    font-weight: 700;
    -webkit-box-reflect: below 0 
                        -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0.3, transparent), to(white));
    background: element(#wkreflect);
}