DEV Community

Cover image for Microblog: creating interactive and semantic toggleable CSS buttons
Shailesh Vasandani
Shailesh Vasandani

Posted on • Edited on • Originally published at shaile.sh

Microblog: creating interactive and semantic toggleable CSS buttons

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;
      }
Enter fullscreen mode Exit fullscreen mode

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;
      }
Enter fullscreen mode Exit fullscreen mode

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;
      }
Enter fullscreen mode Exit fullscreen mode

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;
      }
Enter fullscreen mode Exit fullscreen mode

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;
      }
Enter fullscreen mode Exit fullscreen mode

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;
      }
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (0)