Microblog: creating interactive and semantic toggleable CSS buttons
Tags: microblog, css, webdev, frontend
Published: 2020-12-16 21:27:57

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

![](https://shaile.sh/blog/static-assets/img/toggleable-css-tag-states.gif)

Today, we'll be looking at the toggleable tags on [my website](https://shaile.sh/), 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](https://shaile.sh/codes), and until next time!