Blog Entry - 19th July 2008 - Programming - CSS

Vertically Challenged


Introduction

I have been wrestling with a particular frustrating aspect of styling HTML, and that is vertical alignment.

For some reason, I wanted to set the height on an <input type="text"> and have the text aligned in the middle (between top and bottom).

I have been playing with Opera 9.5, Firefox 2, and IE 7.

The following sets out my explorations so far.

If you have any other bright ideas, or I have made any glaring errors here, please let me know.

0 - About the vertical-align:middle property

At the outset it is important to understand that this, and its limits. It is quite complex, and the following is an incomplete attempt to summarise:-

(a) If used on td elements and elements set to display:table-cell, it aligns the contents of the element relative to the element's box.

(b) If used on inline elements it aligns the contents of the element relative to the line of text of which that inline element forms part, but only if the element is an img or has display:inline-block set. To quote the spec: "This property affects the vertical positioning inside a line box of the boxes generated by an inline-level element."

In fact, browsers very wildly in how they implement (b) - try the following on each:-

<div style="height: 100px; width:200px; border:1px solid gray;">Normal Text
<span style="vertical-align:text-bottom; display:inline-block;">Aligned Text</span>
</div>

editplay

(c) In any other case, it has no effect.

1 - What happens if I just set the input height?

  • Opera - YES - Middle by default
  • Firefox - NO - Top by default
  • IE - NO - Top by default
<input style="height:100px" value="Some text">

editplay

2 - What if I set the line-height to the height of the input?

  • Opera - YES
  • Firefox - NO - Stubborn as a mule
  • IE - YES
<input style="height:100px; line-height:100px;" value="Some text">

editplay

3 - What if I set the top padding?

This will work, if I know in advance the pixel height of my input's font.

I then set the top padding as 50 - (pixel height / 2)

I have come accross this conversion table (Reed Design), but something tells me that this could get messy.

Points	Pixels	Ems		Percent
6pt	8px	0.5em		50%
7pt	9px	0.55em		55%
7.5pt	10px	0.625em		62.5%
8pt	11px	0.7em		70%
9pt	12px	0.75em		75%
10pt	13px	0.8em		80%
10.5pt	14px	0.875em		87.5%
11pt	15px	0.95em		95%
12pt	16px	1em		100%
13pt	17px	1.05em		105%
13.5pt	18px	1.125em		112.5%
14pt	19px	1.2em		120%
14.5pt	20px	1.25em		125%
15pt	21px	1.3em		130%
16pt	22px	1.4em		140%
17pt	23px	1.45em		145%
18pt	24px	1.5em		150%
20pt	26px	1.6em		160%
22pt	29px	1.8em		180%
24pt	32px	2em		200%
26pt	35px	2.2em		220%
27pt	36px	2.25em		225%
28pt	37px	2.3em		230%
29pt	38px	2.35em		235%
30pt	40px	2.45em		245%
32pt	42px	2.55em		255%
34pt	45px	2.75em		275%
36pt	48px	3em		300%

Also similar here

Although this has been said of this chart: "That chart is total bunk. There is no relationship between px and pt." Source.

4 - Regroup

OK I thought, it was not going to be possible to do it for all browsers, within the input.

What if I simulated the desired result, by leaving the height of the input to itself, and looking at aligning the input within a 100px high container.

5 - A Table Cell - vertical-align:middle

  • Opera - YES
  • Firefox - YES
  • IE - YES
<table style="table-layout:fixed; border:0;" cellspacing=0 cellpadding=0>
<tr><td style="height: 100px; width:200px; border:1px solid gray; vertical-align:middle;" onclick="alert(this.offsetHeight + ' ' + this.offsetWidth);">
<input style="border-width:0px;" value="Some text">
<tr><td>
</table>

editplay

The vertical-align property, when used in this context, aligns the contents of the td.

Yes, this would be the best solution; but I thought I would plough on to see if CSS had any other alternatives to this.

6 - display:table-cell / vertical-aign:middle;

  • Opera - YES - Unless position is absolute
  • Firefox - YES - Unless position is absolute
  • IE - NO - Not supported
<div style="height: 100px; width:200px; border:1px solid gray; display:table-cell; vertical-align:middle;" onclick="alert(this.offsetHeight + ' ' + this.offsetWidth);">
<input style="border-width:0px;" value="Some text">
</div>

editplay

This makes the div behave like a table cell, though I would say that it can throw up some unexpected problems in practice when you are reading height,width,borderWidth and other properties.

Note that you cannot use the vertical-align property to align the contents of a block-level element unless that element is a td or is set to display:table-cell.

7 - line-height on container element

I then tried simply setting the line-height on the container element to the container element's height.

  • Opera - NO
  • Firefox - NO
  • IE - NO
<div style="height: 100px; width:200px; border:1px solid gray; line-height:100px;">
<input style="border-width:0px; " value="Some text">
</div>

editplay

However, if I introduced some actual text, it worked, almost:-

  • Opera - YES
  • Firefox - YES
  • IE - NOT REALLY - The text alone will align to the middle, but when you introduce the input box you get very unpredictable behaviour, it either doesn't work, or it vanishes, or aligns to the bottom, or something else entirely.
<div style="height: 100px; width:200px; border:1px solid gray; line-height:100px;">
My Input: <input style="border-width:0px; display:inline " value="Some text">
</div>

editplay

This is presumably because the text establishes a line (a strut) against which the input can be positioned.

However, if you try to hide the My input text in any way (e.g. wrap it in a zero width span or hide it off screen with a negative margin) Opera and Firefox start to fail in different ways, again presumably related to a failure to establish a line.

So all in all, this was not feasible.

8 - Hard core JavaScript

The last resort was to use JavaScript, which involved either setting padding on a container element or positioning the top of input to a value calculated as: half the height of the container element MINUS half the height of the input.

This is only useful if you give the input a fixed height font. If you use em then problems will arise if the user changes the font size, because the input will get bigger, and so its position will need to be re-calculated.

Conclusion

There is no intrinsic property of an input element that allows you to do align its content to the middle of its height, and the HTML specification does not (I think) have anything to say on the matter.

Line-height might have done it, but Firefox lets us down this time.

The alternative is to mimic the desired look, by aligning the input in the middle of a container element that has the desired hight. Unfortunately, there is is no cross-browser way to achieve this through HTML and CSS, other than using a table.

Accordingly, in the end, it was either:-

  • Succumb to tables and align the input within a td element with vertical-align:middle.
  • Use display:table-cell for Opera and Firefox, and use <input style="line-height:100px"... in IE
  • Throw JavaScript at the problem in a big way.

Indeed it was surprising how poorly browsers handle vertical alignment outside a table. Every time I thought I had found a solution, one of the browsers would play up; and don't even get me started on the odd extra margins that IE inserts for its implementation of the input control.


Comment(s)


Sorry, comments have been suspended. Too much offensive comment spam is causing the site to be blocked by firewalls (which ironically therefore defeats the point of posting spam in the first place!). I don't get that many comments anyway, so I am going to look at a better way of managing the comment spam before reinstating the comments.


Leave a comment ...


{{PREVIEW}} Comments stopped temporarily due to attack from comment spammers.