Microblog: creating interactive and semantic toggleable CSS buttons
Shailesh Vasandani
Posted on December 16, 2020
Hello everyone! As part of this new series, I'll be delivering some short and sweet tips and tricks about random things throughout the website — ranging from a unique visual interaction, to a nice bit of JavaScript code, to anywhere in between. Hopefully this also means that I'll be able to write a bit more frequently, so keep an eye out of those posts over the next few months!
Toggle time
Today, we'll be looking at the toggleable tags on my website, which as you may have noticed (or not), have changed somewhat in the past few days. Instead of boring hover states whose background color fades in and out, they now peek out from the bottom and fly up when they're toggled. While this effect is super simple for a normal link, making it toggleable posed a fair few challenges.
Some of these challenges included buggy animations, since toggling a tag would lead directly from an :active
CSS state to a :hover
state, and some issues were also caused by JavaScript updating elements or redrawing the DOM nodes. I solved most of these challenges by using two pseudo-elements instead of one, but enough talking — let's look at some CSS!
.tag {
position: relative;
border: 1px solid var(--text);
padding: 0.5em;
margin: 0.2em;
transition: 0.1s linear;
}
.tag:after {
content: '';
position: absolute;
background: var(--text);
opacity: 1;
z-index: -1;
bottom: 0px;
left: 0px;
width: 100%;
height: 0px;
transition: none;
}
.tag:before {
content: '';
position: absolute;
background: var(--text);
opacity: 0;
z-index: -1;
top: 0px;
left: 0px;
width: 100%;
height: 0px;
transition: none;
}
Here we have the styling for each tag, along with their respective pseudo-elements. So far so good.
.tag:hover {
cursor: pointer;
transition: 0.1s linear;
}
.tag:not(.active):hover:after {
height: 3px;
opacity: 1;
transition: 0.1s linear;
}
.tag:not(.active):hover:before {
height: 100%;
opacity: 0;
transition: none;
}
Now when we hover over a tag that's not active, we use the :after
pseudo-element to create that nice peeking effect. The :before
element here stretches up to fill the tag, but has its opacity
set to 0, so we can't see it anyway. Now for the part that makes it all work smoothly: the :active
state:
.tag:active {
color: var(--bg);
transition: 0.1s linear;
}
.tag:not(.active):active:after {
height: 100%;
opacity: 1;
transition: 0.1s linear;
}
.tag:not(.active):active:before {
height: 100%;
opacity: 0;
transition: none;
}
But wait! How is it both :active
and :not(.active)
? Well, the :active
state occurs when a user presses down on the button. In this case, the :after
pseudo-element fills up the entire tag, giving it a solid background. The :before
pseudo-element remains the same, but that's all about to change:
.tag.active {
color: var(--bg)
}
.tag.active:after {
height: 0px;
opacity: 0;
transition: none;
}
.tag.active:before {
height: 100%;
opacity: 1;
transition: none;
}
Assuming the user let go of the mouse while clicking on the tag, the :after
pseudo-element immediately shrinks back down to 0 height, and the :before
pseudo-element immediately takes its place. The user shouldn't notice anything happen though, because we've been matching the :before
pseudo-element to the :after
for some time now. Now we repeat the whole process, but in reverse:
.tag.active:hover:after {
height: 0px;
opacity: 0;
transition: none;
}
.tag.active:hover:before {
height: calc(100% - 3px);
opacity: 1;
transition: 0.1s linear;
transition-property: height;
}
Since the :before
pseudo-element is aligned to the top, we need to use CSS calc()
to get the right height. The effect we get here is almost like an inverted version of the original, but actually there's no inversion at all, just some fancy pseudo-element usage. Last but not least, let's take a look at what happens when an active
tag is clicked:
.tag.active:active {
color: var(--text);
}
.tag.active:active:after {
height: 0px;
opacity: 1;
transition: none;
}
.tag.active:active:before {
height: 0px;
opacity: 1;
transition: 0.1s linear;
}
That's right, we now have an .active:active
selector. A bit confusing, but unfortunately I was too lazy to rename the active
class. With this set of styles, the :before
pseudo-element rises up, giving the impression that the negative space on the bottom is sliding up to cover the tag.
That's all for today! I hope you enjoyed this slightly shorter format, and maybe even learned a trick or too. Perhaps one day down the line I'll write a post explaining more of the styling behind my website, but right now I think staying short and sweet is my best bet. Be sure to take a look at the post on my website, and until next time!
Posted on December 16, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.