Christoph Christoph - 3 months ago 9
CSS Question

What makes the text on a <button> element vertically centered?

It seems there is some magic around the

<button>
element that I don't understand.

Consider this markup:

<button class="button">Some Text</button>
<div class="button">Some Text</div>


And this CSS:

.button{
background: darkgrey;
height: 40px;
border: 2px solid grey;
width: 100%;
box-sizing: border-box;
font-size: 14px;
font-family: helvetica;
text-align: center;
margin-bottom: 20px;

/*I'm aware I could use this to center it*/
/*line-height: 40px;*/
}


What makes the text in the button element vertically centered? Webkit seems to predefine a
-webkit-box-align
with a value of
center
for the
<button>
element. If I set that to
initial
the text is no longer aligned to the center. But that doesn't seem to be the full magic, since on the other hand I had no luck centering the text on the div using the
-webkit-box-align
property.

Here is a fiddle: http://jsfiddle.net/cburgdorf/G5Dgz/

Answer

I know this is a couple of years old, but I'll add my thoughts after some investigation in to issue while writing a reset stylesheet for a project.

NOTE** This is based on looking through the Firefox source because it was the easiest to obtain and read through. However, based on similar behaviour in other browsers the implementation is probably similar.

Firstly, the main issue here is that <button> elements - atleast in Firefox - are built with an internal element between the <button> tag and it's children. In Firefox it's called moz-button-content and isn't something that can be reached with CSS and has been set to display block without inheriting the height of the button, you can see this style declaration in the useragent stylesheet

Because you can't affect any of the styles on this element, you are forced to add you styling on the <button> tags. This leads into the second issue - The browser is hard coded to vertically position the content of the button.

Given these two issues you can start to see how the button force the content to be centered, consider:

   <button> tag

+------------------------+ ^
| button extra space     | |
|                        | |
+------------------------+ |
|| ::moz-button-content || | button height
||   display: block;    || |
+------------------------+ |
|                        | |
| button extra space     | |
+------------------------+ v

If you give the button a height - like the 48px from your fiddle, the text will be centered because the moz-button-content element is display block and will only have the height of the content (most likely the line-height of the content by default) and when put next to another element you get this behaviour:

* {
  box-sizing: border-box;
  margin: 0;
  border: 0;
  padding: 0;
  font-family: san-serif;
  background: none;
  font-size: 1em;
  line-height:1;
  vertical-align: baseline;
 }

button, a {
  height: 3em;
}

button {
  background: red;
}

a {
  display:inline-block;
  background: green;
 }
<button>Button content</button>
<a>Link Content</a>

This bug and this bug in the Firefox issue tracker was about a close as I could find to any actually documented bug. But the threads give the impression that despite this not appearing in any actual spec, the browsers have just implemented it this way "because the other browsers are doing it that way"


There is a work-around to the issue if you actually want to change the default behaviour, but it doesn't completely solve the problem and YMMV depending on your implementation.

If you insert a wrapper <span> with display: block as the only child of the button and put all your content inside it you can use it to skip over the moz-button-content element.

You will need to make this <span> element have height: inherit so it correctly fills the height of the button and then add your normal button styling to the <span> instead, you will get basically behaviour you want.

    * {
      box-sizing: border-box;
      margin: 0;
      border: 0;
      padding: 0;
      font-family: san-serif;
      background: none;
      font-size: 1em;
      line-height:1;
      vertical-align: baseline;
     }

    button, a {
      height: 3em;
    }

    button {
      background: red;
    }
    button::-moz-focus-inner {
      border: 0;
      padding: 0;
      outline: 0;
    }

    button > span {
      display: block;
      height: inherit;
    }

    a {
      display:inline-block;
      background: green;
    }

    button.styled > span , a.styled{
      padding: 10px;
      background: yellow;
    }
    <button><span>Button content</span></button>
    <a><span>Link Content<span></a><br/>
    <button class="styled"><span>Button content</span></button>
    <a class="styled"><span>Link Content<span></a>

It's also worth mentioning the appearance CSS4 rule (Not yet available):

While this is not a viable option (as of the 5th January) yet. There is a proposal to redefine the appearance rule in the CSS4 draft that might actually do the right thing an remove all assumptions made by the browser. I only mention it for completeness because it may become useful in the future.

UPDATE - 30/08/2016 You should actually use a <span> instead of a <div>, as div's aren't valid children for <button> elements. I have updated the answer to reflect this.