What makes the text on a element vertically centered?

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:

From “source/layout/style/res/forms.css”

*|*::-moz-button-content {
    display: block;
    /* Please keep the Multicol/Flex/Grid/Align sections below in sync with
       ::-moz-scrolled-content in ua.css and ::-moz-fieldset-content above. */
    /* Multicol container */
    -moz-column-count: inherit;
    -moz-column-width: inherit;
    -moz-column-gap: inherit;
    -moz-column-rule: inherit;
    -moz-column-fill: inherit;
    /* Flex container */
    flex-direction: inherit;
    flex-wrap: inherit;
    /* -webkit-box container (aliased from -webkit versions to -moz versions) */
    -moz-box-orient: inherit;
    -moz-box-direction: inherit;
    -moz-box-pack: inherit;
    -moz-box-align: inherit;
    /* Grid container */
    grid-auto-columns: inherit;
    grid-auto-rows: inherit;
    grid-auto-flow: inherit;
    grid-column-gap: inherit;
    grid-row-gap: inherit;
    grid-template-areas: inherit;
    grid-template-columns: inherit;
    grid-template-rows: inherit;
    /* CSS Align */
    align-content: inherit;
    align-items: inherit;
    justify-content: inherit;
    justify-items: inherit;
}

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.

From “source/layout/forms/nsHTMLButtonControlFrame.cpp”

// Center child in the block-direction in the button
// (technically, inside of the button's focus-padding area)
nscoord extraSpace =
    buttonContentBox.BSize(wm) - contentsDesiredSize.BSize(wm);

childPos.B(wm) = std::max(0, extraSpace / 2);

// Adjust childPos.B() to be in terms of the button's frame-rect:
childPos.B(wm) += clbp.BStart(wm);

nsSize containerSize = (buttonContentBox + clbp.Size(wm)).GetPhysicalSize(wm);

// Place the child
FinishReflowChild(aFirstKid, aPresContext, contentsDesiredSize,
                  &contentsReflowInput, wm, childPos, containerSize,
                  ReflowChildFlags::Default);

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.

Leave a Comment