Technology News

Creating Directionally Lit 3D Buttons with CSS

Creating Directionally Lit 3D Buttons with CSS

I’m not too sure how I stumbled into this one. But something led me to this tweet:

And, to me, that’s a challenge.

The button design is neat. But I didn’t want to do a direct copy. Instead, we decided to make a “Twitter” button. The idea is that we create an almost transparent button with a social icon on it. And then that social icon casts a shadow below. Moving our mouse across the button shines a light over it. Pressing the button pushes it onto the surface. Here’s the final result:

In this article, we’re going to look at how you can make it too. The cool thing is, you can swap the icon out to whatever you want.

The Markup

My first-take approach for creating something like this is to scaffold the markup. Upon first inspection, we’ll need to duplicate the social icon used. And a neat way to do this is to use Pug and leverage mixins:

mixin icon() svg.button__icon(role='img' xmlns='http://www.w3.org/2000/svg' viewbox='0 0 24 24') title Twitter icon path(d='M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z') 

Here, we’ve created a mixin for rendering an SVG of the Twitter icon. This would render the Twitter icon if we invoke it like so:

+icon() 

Doing that will give us a big Twitter icon.

See the Pen 1. Render An Icon by SitePoint (@SitePoint)
on CodePen.

Because social icon sets tend to use the same “0 0 24 24” viewBox, we could make the title and path arguments:

mixin icon(title, path) svg.button__icon(role='img' xmlns='http://www.w3.org/2000/svg' viewbox='0 0 24 24') title= title path(d=path) 

Then our Twitter icon becomes

+icon('Twitter Icon', 'M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z') 

But, we could pass it a key — and then have the paths stored in an object if we have many icons we wanted to use or repeat:

mixin icon(key) - const PATH_MAP = { Twitter: "M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" } svg.button__icon(role='img' xmlns='http://www.w3.org/2000/svg' viewbox='0 0 24 24') title= `${key} Icon` path(d=PATH_MAP[key]) +icon('Twitter') 

This can be a neat way to create an icon mixin to reuse. It’s a little overkill for our example, but worth noting.

Now, we need some markup for our button.

.scene button.button span.button__shadow +icon('Twitter') span.button__content +icon('Twitter') span.button__shine 

It’s always good to be mindful of accessibility. We can check what our button gives off by checking the Accessibility panel in your browser’s developer tools.

The accessibility panel in Chrome

It might be a good idea to put a span in for our button text and hide the icons with aria-hidden. We can hide the span text too whilst making it available to screen readers:

.scene button.button span.button__shadow +icon('Twitter') span.button__content span.button__text Twitter +icon('Twitter') span.button__shine 

We’ve got different options for applying those aria-hidden attributes. The one we’ll use is changing the mixin code to apply aria-hidden:

mixin icon(key) - const PATH_MAP = { Twitter: "...path code" } svg.button__icon(role='img' aria-hidden="true" xmlns='http://www.w3.org/2000/svg' viewbox='0 0 24 24') title= `${key} Icon` path(d=PATH_MAP[key]) 

Another neat way with Pug is to pass through all attributes to a mixin. This is useful in scenarios where we only want to pass some attributes through:

mixin icon(key) - const PATH_MAP = { Twitter: "...path code" } svg.button__icon(role='img' xmlns='http://www.w3.org/2000/svg' viewbox='0 0 24 24')&attributes(attributes) title= `${key} Icon` path(d=PATH_MAP[key]) 

If we check the Accessibility panel again, our button only reads “Twitter”. And that’s what we want!

The Styles

Here’s the part you came for — how we style the thing. To start, we’ve dropped this in:

* { transform-style: preserve-3d; } 

That allows us to create the 3D transforms we need for our button. Try switching that off in the final demo and you’ll see that everything breaks.

Let’s hide the span text from our eyes. We can do this in many ways. One recommended way to hide an element from our eyes, but not those of the screenreader, is to use these styles:

.button__text { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } 

Before we start working on our button, we’re going to tilt the scene. We can do this using a transform. Here we chain the transform to get it into the position we want. I spent a bit of time tinkering with values here on live stream to get it close to the original:

.scene { height: var(--size); position: relative; width: var(--size); transform: rotateX(-40deg) rotateY(18deg) rotateX(90deg); } 

You’ll notice a size variable there too. We’re going to drive certain things for our button with CSS variables. This will make it handy for tinkering with values and the effect. Usually, we’d put these under the scope they’re required in. But, for demos like this, putting them under the :root at the top of our file makes it easier for us to play with.

:root { --blur: 8px; --shine-blur: calc(var(--blur) * 4); --size: 25vmin; --transition: 0.1s; --depth: 3vmin; --icon-size: 75%; --radius: 24%; --shine: rgba(255,255,255,0.85); --button-bg: rgba(0,0,0,0.025); --shadow-bg: rgba(0,0,0,0.115); --shadow-icon: rgba(0,0,0,0.35); --bg: #e8f4fd; } 

These are the variables we’re working with, and they’ll make sense as we build up our button.

Continue reading Creating Directionally Lit 3D Buttons with CSS on SitePoint.

Learn Snowpack: A High-Performance Frontend Build Tool
Using AbortController as an Alternative for Removing Event Listeners

Related Articles

Start Serving Optimized Images in Vue

Exploring the Potential of the Metaverse: A New World Awaits
Images have become extremely important to the effectiveness of websites. They speak a 1000 words, attract attention, and stimulate emotions. However, web performance is also a growing problem for most…

8 Tips for SEO-Friendly Mobile Design in 2022

8-tips-for-seo-friendly-mobile-design-in-2022
Mobile SEO is the process of optimizing your website to make sure your site looks incredible, functional, and legible on mobile devices. When you perform mobile SEO, you provide a…

State of CSS 2022 Survey Now Open

State of CSS 2022 Survey Now Open
The State of CSS survey recently opened up. Last year, the survey confirmed everyone’s assumptions that TailwindCSS is super popular and CSS variables are mainstream. It also codified what many…

Form Validation with PHP

Exploring the Potential of the Metaverse: A New World Awaits
In this article, we’ll build and validate a small web form using HTML and PHP. The form is created using HTML, and validation and processing of the form’s contents is…

WordPress 6.8.2 Maintenance Release

Exploring the Potential of the Metaverse: A New World Awaits
WordPress 6.8.2 is now available! This minor release includes fixes for 20 Core tickets and 15 Block Editor issues. For a full list of bug fixes, please refer to the…