No Time Dad

A blog about web development written by a busy dad

Stacked button with Tailwind

Sometimes buttons are where creativity goes to die. I’ve written about it before, but I love seeing examples of buttons that aren’t standard. When I see an app with a creative button it makes me think that there might be something special about the app. Someone took the time to make a clever button, so surely that’s carried throughout the app too. Sometimes that isn’t true, and a clever button is just a clever button.

I like buttons that move or change shape when I hover on them. I don’t know why, but I just think they’re fun. Most buttons have a slight background color change on hover, which is fine, but moving the button slightly on hover or changing its shape somehow takes extra thought.

I’ve grown fond of stacked buttons that “lift off” the page on hover. They have a cool 3D feeling to them that I like. Sometimes I’ll just move my mouse over them for a few minutes and think about how they’re implemented. That’s how into buttons I am.

I noticed the Larvel site had some of these stacked buttons that lifted on hover. I thought it would be fun to try to recreate one of them using Tailwind. I noticed that the Laravel site is using Tailwind, but I’d like to see how far I can get without referencing their source.


Thinking about the button

Before I actually build the button, I’d like to think about how the button works and how I might implement it. This helps me build a mental model that I can work from. I’ll start with a few things I know about the button. I don’t know everything about it, but I think I might know enough to lay the groundwork.

Here are some things I’m thinking about with the Laravel button:

  • The button probably consists of at least 2 divs. One div for the top and one div for the bottom.
  • Both the top div and button div appear to have the same border.
  • The top div likely contains the text and icon, but I’ve been tricked by this before.
  • On hover, the top div “lifts” off of the bottom div up and slightly to the left. This is probably done with some variation transform on :hover.
  • The bottom div doesn’t move on hover, which means it probably has position: absolute applied.

Now I have a strong set of assumptions to work from. Some of these will almost certainly turn out to be wrong, but the point of the list is to help me get started.

Building the button

The first thing I’ll do is create two buttons in a container div. From past experience, I know that elements with position: absolute applied can be difficult to position. Having a parent div makes it a little easier, but not much. I don’t have a solution for this problem, it’s just something I’m aware of.

Below is my starting point, with almost no styles applied. My blog has a global.css file which normalizes buttons across browsers, so mine may look different than default button elements.

  <button>Button 1</button>
  <button>Button 2</button>

Now I can apply my positioning changes to the buttons. Both of the buttons will have position: absolute applied via Tailwind’s absolute utility class. They’ll also both sit inside a parent div that has position: relative applied via the relative utility class. Knowing that they both need absolute positioning is from past experience, but just applying position: absolute to one of them and seeing how it looks is a valid way to figure this out.

I need to figure out which button is the top button and which is the bottom. The obvious choice is button 1 as the top since it is, in fact, on top of button 2. I’ll apply Tailwind’s hover:bg-red-500 to confirm this.

<div class="relative">
  <button class="absolute hover:bg-red-500">Button 1</button>
  <button class="absolute">Button 2</button>

Hovering over the stacked buttons does not change the color. Even having built a similar button before, this was a surprise to me. I thought for sure that the top button would change colors. Which means that the first button is not actually on top. The reason for this has to do with the stacking context. Which is probably a concept I should be more familiar with at this point, but for now trial and error works. I’ll switch the hover:bg-red-500 to the second button and give it another try.

<div class="relative">
  <button class="absolute">Button 1</button>
  <button class="absolute hover:bg-red-500">Button 2</button>

Ah, red! So, now I know which button is on top. Again, this is not a very scientific way to figure this out but it was relatively quick. The next thing I need to do is move the top button on hover. It should move towards the upper left of the page, left and up.

Moving the button can be done by add Tailwind’s transform utility class, as well as two hover: classes. One will be -translate-y-{n}, which moves the button n pixels up. The second class will be -translate-x-{n}, which moves the button n pixels left.

At this point I think it’ll be helpful to remove the background color and apply a border instead. I can then get an idea of how many pixels I need to move on hover. I’ll also remove the text from the button on the bottom since it won’t technically be visible.

The other thing I need to keep in mind here is the size of the button. Since I’m removing the text from the bottom button, I need to specify its width or height or it will be invisible on the page.

<div class="relative">
  <button class="absolute bg-white border border-blue-500 w-20 h-12"></button>
  <button class="absolute bg-white transform hover:-translate-y-1 hover:-translate-x-1 border border-blue-500 w-20 h-12">Sign up</button>

Try hovering over the button above to see it move. I’m getting pretty close now to the original example. The problem now is that the button movement on hover happens quick. The example from Laravel doesn’t move that quick. It likely has a transition property applied.

Tailwind makes transitions painless for the most part. I can add the following utility classes to my top button to acheive the transition I’m after transition delay-100 duration-100 ease-in-out.

<div className="relative">
  <button className="absolute bg-white border border-blue-500 w-20 h-12"></button>
  <button className="absolute bg-white transition delay-100 duration-100 ease-in-out transform hover:-translate-y-1 hover:-translate-x-1 border border-blue-500 w-20 h-12 text-blue-500 font-semibold">Sign up</button>

Again, try hovering over the button to see it in action. The button should raise slightly, giving it that “lift off” the page quality I mentioned at the start.

Conclusion & final code

I think I got pretty close to the Laravel button in this example. Not exactly a perfect match, but very close. One thing I noticed is that the hover effect can be a little jittery if I hover on the bottom button. There is probably some sibling selector hack I could do to avoid this, but it might be a little too in-depth for this post.

I haven’t tried to use this button anywhere yet, but I know it’s going to be a headache when I do. I just haven’t found a great way to position absolute elements yet. I feel like I always revert to negative margins or other wierdness to get them where I want them to go. I guess that’s just something I need more practice with.

Final code & demo
<div className="relative">
  <button className="absolute bg-white border border-blue-500 w-20 h-12"></button>
  <button className="absolute bg-white transition delay-100 duration-100 ease-in-out transform hover:-translate-y-1 hover:-translate-x-1 border border-blue-500 w-20 h-12 text-blue-500 font-semibold">Sign up</button>