Search code examples
htmlcssbuttonvertical-alignment

Mimic button look and behavior


I'm trying to create an element with the button look. I've created a .button class to apply the effect. I've managed to mimic the basic styles (borders, background gradient, hover and active effect). The problem is the vertical alignment of the button text if the button has a height.

If I don't specify a height on the element, there's no need for vertical aligment, and I get the desired result: JSFiddle.

However, if I specify a height on the element, the element's size (height) increases and the text stays at the top: JSFiddle.

To vertically align the text to center/middle, I add a span inside the button element and modify the span.

.button span {
    width: 100%;
    height: 100%;
    display: table-cell;
    vertical-align: middle;
}

But this doesn't work. Even though the parent .button has width and height specified, span won't get these values: JSFiddle.

To get around this problem, I set the width and height on the span to inherit. This somewhat solves the problem, but causes another: JSFiddle.

The inherited width is the remaing space, because box-sizing: border-box; applied on the parent .button . In other words, width = 100px - border - padding 86px. But the inherited height is not the remaing height, but the actual height applied to the parent 50px. This is weird and inconsistent. The text seems to vertically aligned, but it's slightly off. Also the span overflows the .button.

I could remove the box-sizing: border-box; on the .button to get around this problem, and get a perfectly aligned button text and a nice button, but the padding and border will be added to the width and height I specify. If I want a 100x50 pixels button, I'll get 100+border+padding x 50+border+padding pixels button, but a vertically aligned one: JSFiddle.

Is there any other way to make this work?

.button {
  overflow: hidden;
  white-space: nowrap;
  display: inline-block;
  vertical-align: top;
  -webkit-user-select: none;
  font-family: Arial;
  font-size: 13px;
  color: black;
  text-decoration: none;
  text-align: center;
  cursor: default;
  padding: 2px 6px;
  margin: 0;
  /* box-sizing: border-box; */
  border-radius: 2px;
  border-top: 1px solid rgb(166,166,166);
  border-bottom: 1px solid rgb(148,148,148);
  border-left: 1px solid rgb(157,157,157);
  border-right: 1px solid rgb(157,157,157);
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAAAAACFNooQAAAAHUlEQVQI12P4wfSfiYHpP9N/KP0Pyv6Pxobw4CoBqcYYFT1E4a0AAAAASUVORK5CYII=);
  background-size: contain;
}
.button:hover {
  border-top: 1px solid rgb(123,123,123);
  border-bottom: 1px solid rgb(110,110,110);
  border-left: 1px solid rgb(116,116,116);
  border-right: 1px solid rgb(116,116,116);
}
.button:active {
  border-top: 1px solid rgb(166,166,166);
  border-bottom: 1px solid rgb(148,148,148);
  border-left: 1px solid rgb(157,157,157);
  border-right: 1px solid rgb(157,157,157);
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAAAAACFNooQAAAAHUlEQVQI12O4y8TIxMDEyMQIpZmgbAhkQmPDVQIAOFkBLhpTdQIAAAAASUVORK5CYII=);
}
.button span {
  width: inherit;
  height: inherit;
  display: table-cell;
  vertical-align: middle;
}

body > * {
  width: 100px;
  height: 50px;
}
<input type="button" style="width: 150px; height: 50px" value="input">

<a href="#" class="button">anchor</a>

<a href="#" class="button"><span>a > span</span></a>

<p class="button">paragraph</p>

Update: I forgot to mention. I can't set line-height on the span. If I have multiple buttons with variable heights, I need to apply different line-heights on all of them.

I'm looking for a more general solution which would make an element work exactly like a button (width/height specified or not).


I wanna break the upvote button on this.


Solution

  • You can achieve this with flexbox.

      display: inline-flex;
      justify-content: center;
      align-items: center;
    

    jsFiddle


    CODE SNIPPET:

    .button {
      overflow: hidden;
      white-space: nowrap;
      display: inline-flex;
      justify-content: center;
      align-items: center;
      -webkit-user-select: none;
      font-family: Arial;
      font-size: 13px;
      color: black;
      text-decoration: none;
      cursor: default;
      padding: 2px 6px;
      margin: 0;
      box-sizing: border-box;
      border-radius: 2px;
      border-top: 1px solid rgb(166, 166, 166);
      border-bottom: 1px solid rgb(148, 148, 148);
      border-left: 1px solid rgb(157, 157, 157);
      border-right: 1px solid rgb(157, 157, 157);
      background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAAAAACFNooQAAAAHUlEQVQI12P4wfSfiYHpP9N/KP0Pyv6Pxobw4CoBqcYYFT1E4a0AAAAASUVORK5CYII=);
      background-size: contain;
    }
    .button:hover {
      border-top: 1px solid rgb(123, 123, 123);
      border-bottom: 1px solid rgb(110, 110, 110);
      border-left: 1px solid rgb(116, 116, 116);
      border-right: 1px solid rgb(116, 116, 116);
    }
    .button:active {
      border-top: 1px solid rgb(166, 166, 166);
      border-bottom: 1px solid rgb(148, 148, 148);
      border-left: 1px solid rgb(157, 157, 157);
      border-right: 1px solid rgb(157, 157, 157);
      background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAAAAACFNooQAAAAHUlEQVQI12O4y8TIxMDEyMQIpZmgbAhkQmPDVQIAOFkBLhpTdQIAAAAASUVORK5CYII=);
    }
    body > * {
      width: 100px;
      height: 50px;
    }
    <input type="button" value="input">
    
    <a href="#" class="button">anchor</a>
    
    <p class="button">paragraph</p>


    EDIT:

    Semantically speaking, I would rather use a data-attribute to give extra information about the behavior change in my HTML element.

    Here's an example using a level four selector to allow case permutation in your attribute's value: (You can remove the i from the selector since it may not be supported in all modern browsers.)


    DEMO


    [data-type="button" i] {
      overflow: hidden;
      white-space: nowrap;
      display: inline-flex;
      justify-content: center;
      align-items: center;
      -webkit-user-select: none;
      font-family: Arial;
      font-size: 13px;
      color: black;
      text-decoration: none;
      cursor: default;
      padding: 2px 6px;
      margin: 0;
      box-sizing: border-box;
      border-radius: 2px;
      border-top: 1px solid rgb(166, 166, 166);
      border-bottom: 1px solid rgb(148, 148, 148);
      border-left: 1px solid rgb(157, 157, 157);
      border-right: 1px solid rgb(157, 157, 157);
      background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAAAAACFNooQAAAAHUlEQVQI12P4wfSfiYHpP9N/KP0Pyv6Pxobw4CoBqcYYFT1E4a0AAAAASUVORK5CYII=);
      background-size: contain;
    }
    [data-type="button" i]:hover {
      border-top: 1px solid rgb(123, 123, 123);
      border-bottom: 1px solid rgb(110, 110, 110);
      border-left: 1px solid rgb(116, 116, 116);
      border-right: 1px solid rgb(116, 116, 116);
    }
    [data-type="button" i]:active {
      border-top: 1px solid rgb(166, 166, 166);
      border-bottom: 1px solid rgb(148, 148, 148);
      border-left: 1px solid rgb(157, 157, 157);
      border-right: 1px solid rgb(157, 157, 157);
      background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAcCAAAAACFNooQAAAAHUlEQVQI12O4y8TIxMDEyMQIpZmgbAhkQmPDVQIAOFkBLhpTdQIAAAAASUVORK5CYII=);
    }
    body > * {
      width: 100px;
      height: 50px;
    }
    <input type="button" value="input">
    
    <a href="#" data-type="button">anchor</a>
    
    <p data-type="BuTToN">paragraph</p>