• Ghost ButtonsA ghost button is a transparent button with a basic button shape, with a thin solid border. The background is filled with color to highlight the button while hovering.
  • direction aware: Direction sensing Is the ability to determine which direction the mouse is coming from the button.

In this article, we’ll build a ghost button. Implementing the button is simple, but the fun and tricky part is filling the button background color from the direction the mouse enters.

Here’s a button we’ve done!

codepen-demo

Most of the time, when we hover, we transition background-color to match the border color. In some designs, buttons may be filled left to right and top to bottom to enhance the visual effect. As an example, fill from left to right:

codepen-demo

If we mouse over the right side of the button and fill from the left, we will have a bad experience!

The experience would be better if the button filled from our hover point.

How do I make a button directional? The first thing that comes to mind is JavaScript, but we can also do it with CSS with some tags.

Let’s take a look at the final result:

codepen-demo

Next, we break down the implementation steps.

basis

Let’s start by creating a button. It’s easy!

<button>Boo!</button>
Copy the code

We use CSS custom properties for styling, which is easier to maintain.

button {
  --borderWidth: 5;
  --boxShadowDepth: 8;
  --buttonColor: #f00;
  --fontSize: 3;
  --horizontalPadding: 16;
  --verticalPadding: 8;

  background: transparent;
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  box-shadow: calc(var(--boxShadowDepth) * 1px) calc(var(--boxShadowDepth) * 1px) 0 # 888;
  color: var(--buttonColor);
  cursor: pointer;
  font-size: calc(var(--fontSize) * 1rem);
  font-weight: bold;
  outline: transparent;
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  transition: box-shadow 0.15 s ease;
}

button:hover {
  box-shadow: calc(var(--boxShadowDepth) / 2 * 1px) calc(var(--boxShadowDepth) / 2 * 1px) 0 # 888;
}

button:active {
  box-shadow: 0 0 0 # 888;
}
Copy the code

codepen-demo

We implemented a button and hover effect, but no padding. Let’s move on!

Add filling

We create additional elements for the state of the button fill. Hide it with clip-path. Clip-path is set when the mouse hovers over the button.

They must be aligned with the parent button. Here our CSS variable will show its advantage.

We could have done it with a pseudo-element, but it doesn’t meet our four needs, and it interferes with accessibility… We’ll talk about that later.

Let’s start with a left to right fill effect. On the front page we will add a SPAN tag that has the same content as the button.

<button>Boo!
  <span>Boo!</span>
</button>
Copy the code

Now we want to overlap the span with the button.

button span {
  background: var(--buttonColor);
  border: calc(var(--borderWidth) * 1px) solid var(--buttonColor);
  bottom: calc(var(--borderWidth) * -1px);
  color: var(--bg, #fafafa);
  left: calc(var(--borderWidth) * -1px);
  padding: calc(var(--verticalPadding) * 1px) calc(var(--horizontalPadding) * 1px);
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
}
Copy the code

Finally, we hide the elements by clipping, and update the clipping rules to make them visible when hovering.

button span { --clip: inset(0 100% 0 0); -webkit-clip-path: var(--clip); clip-path: var(--clip); 0.25 s help ease the transition: clip - path; / /... Remaining div styles } button:hover span { --clip: inset(0 0 0 0); }Copy the code

codepen-demo

Add orientation awareness

So how do you sense direction? We need four elements. Each element will be responsible for detecting hover entry points. Using clip-path, we can divide the button area into four sections.

We added four spans to the button and placed them on four sides to fill the button.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
</button>
Copy the code
button span {
  background: var(--bg);
  bottom: calc(var(--borderWidth) * -1px);
  -webkit-clip-path: var(--clip);
  clip-path: var(--clip);
  left: calc(var(--borderWidth) * -1px);
  opacity: 0.5;
  position: absolute;
  right: calc(var(--borderWidth) * -1px);
  top: calc(var(--borderWidth) * -1px);
  z-index: 1;
}
Copy the code

We position each element and use CSS variables to give them a background color and clipping rules.

button span:nth-of-type(1) {
  --bg: #00f;
  --clip: polygon(0 0, 100% 0, 50% 50%, 50% 50%);
}
button span:nth-of-type(2) {
  --bg: #f00;
  --clip: polygon(100% 0, 100% 100%, 50% 50%);
}
button span:nth-of-type(3) {
  --bg: # 008000;
  --clip: polygon(0 100%, 100% 100%, 50% 50%);
}
button span:nth-of-type(4) {
  --bg: # 800080;
  --clip: polygon(0 0, 0 100%, 50% 50%);
}
Copy the code

To test, we change the transparency of the element while hovering.

button span:nth-of-type(1):hover.button span:nth-of-type(2):hover.button span:nth-of-type(3):hover.button span:nth-of-type(4):hover {
  opacity: 1;
}
Copy the code

Oops, here’s the problem. If we enter and hover over a segment, and then hover over another segment, the filling direction will change. This doesn’t look right. To solve this problem, we can set z-index and clip-path while hovering to fill this space.

button span:nth-of-type(1):hover.button span:nth-of-type(2):hover.button span:nth-of-type(3):hover.button span:nth-of-type(4):hover {
  --clip: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  opacity: 1;
  z-index: 2;
}
Copy the code

codepen-demo

together

Now we know how to create a fill animation, and we know how to determine the direction. So how do we put them together to achieve the desired effect? The answer is peer selector!

When we hover over a block, we can fill in the specified element.

First, we need to update our code:

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <b>Boo!</b>
  <b>Boo!</b>
  <b>Boo!</b>
  <b>Boo!</b>
</button>
Copy the code

Next, we need to update our CSS so that we can reuse the padding styles from left to right. But you need to set a different clip-path for each element. We set up the first one on top, the second on the right, the third one on the bottom, and the fourth one on the left.

button b:nth-of-type(1) {
  --clip: inset(0 0 100% 0);
}
button b:nth-of-type(2) {
  --clip: inset(0 0 0. 100%); }button b:nth-of-type(3) {
  --clip: inset(100% 0 0 0);
}
button b:nth-of-type(4) {
  --clip: inset(0 100% 0 0);
}
Copy the code

The final step is to update the clip-path of the element when hovering over the direction block.

button span:nth-of-type(1):hover ~ b:nth-of-type(1).button span:nth-of-type(2):hover ~ b:nth-of-type(2).button span:nth-of-type(3):hover ~ b:nth-of-type(3).button span:nth-of-type(4):hover ~ b:nth-of-type(4) {
  --clip: inset(0 0 0 0);
}
Copy the code

At this point, our direction-aware ghost button is implemented.

codepen-demo

accessibility

When the button is inaccessible, the following status is displayed.

These extra elements make the screen reader repeat the reading four times. So, we need to hide them.

<button>
  Boo!
  <span></span>
  <span></span>
  <span></span>
  <span></span>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
  <b aria-hidden="true">Boo!</b>
</button>
Copy the code

So there’s no repetition.

This is

With extra elements and CSS we can implement direction-aware ghost buttons. Use preprocessors or put them in the application as a component so we don’t have to write them every time.

  1. Here is an example of filling text effect through orientation awareness, which is basically the same as the idea in this paper: Direction Aware filling text effect
  2. This article summarizes a number of examples of mouse hovering to determine direction, both pure CSS implementation, and JS implementation. Direction Aware Hover Effects

Translator: Mark Wong

Original text: css-tricks.com/ghost-butto…