Contents
This is a straightforward article demonstrating some very basic UI layout concepts using either DIV
or TABLE
elements. The impetus for this is simply that I often struggle with figuring out the nuances of how to position elements in the UI so I get both the look and behavior that I want. By look, I mean the positioning of sections of the UI and the elements within those sections, and by behavior, I mean how the UI behaves when the browser window is resized. I'm not covering things like device screen sizes or replacing a menu bar with a "triple bar" dropdown -- there are component libraries like Bootstrap to handle that.
All of these examples can be coded in Visual Code and viewed side-by-side with Quick HTML Previewer, which of all the HTML preview plugins that I looked at is the only one that I found actually works.
Let's start with something basic -- two inline div
s. The HTML:
<p>Two inline divs:</p>
<div class='border1 quarter inline'>Div 1</div>
<div class='border1 quarter inline m10'>Div 2</div>
And the CSS I'm using to define the border, width, margin, and inline styles:
.border1 {
border-style:solid;
border-width: 1px;
}
.quarter {
width: 25%;
}
.inline {
display: inline-block;
}
.m10 {
margin-left:10px;
}
Yielding:
If you want some in depth reading about the two, I suggest Ternstyle's blog entry and Thoughtbot's blog entry. For now, let's just deal with the practical issues, otherwise known as WTF???
Let's compare that with using a float
style instead. The HTML:
<p>Inline divs using float:<br>
Note a slight shift of Div2 to the left as compared to using inline.</p>
<div class='border1 quarter fleft'>Div 1</div>
<div class='border1 quarter fleft m10'>Div 2</div>
And the fleft
CSS:
.fleft {
float:left;
}
Looks pretty much the same, right? But notice there's a slight spacing difference between the two div
s:
Why is that? Well, read on.
As it turns out, using inline-block
respects any text between the div
s, whereas float
moves the text between the div
s out to the right. This is an important behavioral difference! So the difference between the div
s using inline-block
is because I have spaces in the HTML:
See the indentation of my nicely formatted HTML? Let's exaggerate this difference between inline-block
and float
, getting rid of my left margin and removing the editor's indentation:
<p>Two inline-block divs:</p>
James <div class='border1 quarter inline'>Div 1</div>Tiberius
<div class='border1 quarter inline'>Div 2</div> Kirk
<p>Two float divs:</p>
James <div class='border1 quarter fleft'>Div 1</div>Tiberius
<div class='border1 quarter fleft'>Div 2</div> Kirk
And we get:
Oh wow. Even the carriage return between the inline-block
div
s is adding a space:
<p>Two inline-block divs:</p>
James <div class='border1 quarter inline'>Div 1</div>Tiberius
<div class='border1 quarter inline'>Div 2</div> Kirk
Notice the space before the second div
has now been removed:
Your lovely HTML indentation, and in particular, the auto-indenting that your editor is doing for you, is affecting your layout using inline-block
!
So you've created the perfect layout in your editor and then you run a minimizer which potentially removes the whitespace and carriage returns between your div
s. Suddenly, your UI looks slightly different!
All of the solutions are ugly. From here and here, they are:
- Remove all whitespace and carriage returns between divs. So much for readable HTML.
- Use
float
, but that has side-effects like we saw above with text between the div
s. - Create a container
div
with font-size
of 0
then override the font size in the child div
s. Gross.
And my favorite driving-the-porcelain-bus solution:
James<!--<div class='border1 quarter inline'>Div 1</div>Tiberius<!--<div class='border1 quarter inline'>Div 2</div> Kirk
OK then. So there really is, at least in my opinion, no viable solution that maintains a nicely formatted HTML document and renders the same identical layout when the HTML is minimized. Which basically means, always test your layout as minimized HTML rather than in a preview editor. And of course, there's no preview editor that I know of that will minimize your HTML before feeding it to the browser. Hmmm, there's another article idea! Of course, if you have a really smart minimizer that respects whitespace and carriage returns between and around inline-block
div
s, that would help too. Anyone with better suggestions?
Most of the time, I need layout options that include being able to position an entire section to the left, center, and/or right of the page as well as position the elements within those sections on the left, center, right and top, middle, or bottom. That's not too much to ask for, is it? Riiiight.
Here's the HTML for a simple layout (note I'm using float
):
<p>Left, centered, and right floating divs:<br>
Note the centered div must come last so the left and right<br>
margins can be computed. Furthermore, we can't use "inline-block",<br>
we have to use "float."</p>
<div class='border1 fleft quarter'>Left</div>
<div class='border1 fright quarter taright'>Right</div>
<div class='border1 center tacenter'>Center</div>
and the additional CSS:
.center {
margin-left:auto;
margin-right:auto;
}
.quarter {
width: 25%;
}
.fleft {
float:left;
padding: 0;
margin: 0;
}
.fright {
float:right;
}
.tacenter {
text-align: center;
}
.taright {
text-align: right;
}
And the result:
- We have to use
float
! - The center
div
has to come last!
If we try to use inline-block
with float
, the center auto-margin is ignored. Using this HTML:
<div class='border1 inline fleft quarter'>Left</div>
<div class='border1 inline center quarter tacenter'>Center</div>
<div class='border1 inline fright quarter taright'>Right</div>
we get:
The problem here is that the we're specifying the width of the center region. This means that the center doesn't dynamically expand the way the center float
version does when the browser width shrinks.
Using float
:
Using inline-block
:
Instead, the center div
width, using inline-block
, is determined by the content of the div
. This makes it impossible to right-justify text against the left edge of the rightmost div
. Got that? If there is a solution, I haven't found it. Anyone?
We can get some bizarre effects. Given this HTML:
<p>Playing with wrapper and inner widths:</p>
<div class='border1 fleft quarter'>Left</div>
<div class='border1 fright right quarter taright'>Right</div>
<div class='border1 center tacenter w40p'>
<div class='border1 fleft taleft w40p'>Left: Some long text that forces a line wrap.</div>
<div class='border1 fright right taright w40p'>
Right: Some long text that forces a line wrap.</div>
<div class='border1 center tacenter'>Center: Some long text that forces a line wrap.</div>
</div>
We can resize the width of the browser window and we see:
But wait! A little different width and we get:
This effect is the result of the inner right div
's height forcing the inner-center
text at the bottom to not be able to extend the full width of the center div
. Makes sense, right? The point being, always test your layout with different browser widths. Within reason, of course!
I discovered vanseo design's blog post that solved this problem for me, so what you see here is just a regurgitation of their post. To achieve this:
Requires treating the div
s as table cells! Here's the HTML:
<p>Child element positioning:</p>
<div class='parent border1 fleft w33p h100px'>
<div class="child-top-left"><button>Button A</button></div>
</div>
<div class='parent border1 fleft w33p h100px'>
<div class="child-middle-center"><button>Button B</button></div>
</div>
<div class='parent border1 fleft w33p h100px'>
<div class="child-bottom-right"><button>Button C</button></div>
</div>
and here's the additional CSS:
.w33p {
width: 33%;
}
.h100px {
height: 100px;
}
.parent {display: table;}
.child-top-left {
display: table-cell;
}
.child-middle-center {
display: table-cell;
vertical-align: middle;
text-align: center;
}
.child-bottom-right {
display: table-cell;
vertical-align: bottom;
text-align: right;
}
Funny how we use the text-align
style to align inner HTML elements! So this works because we're creating div
s that simulate table cells, which is a natural lead in to the next section, doing the same thing with tables. But first...
Consider this fragment:
<div class='border1 quarter inline'>Div 1</div>
<div class='border1 quarter inline m10'>Div 2</div>
<div class='border1 quarter fleft'>Div 1</div>
<div class='border1 quarter fleft m10'>Div 2</div>
Resulting in:
That isn't what we want at all! To deal with this, we need to clear the float
elements on the left and right:.
HTML:
<div class='border1 quarter inline'>Div 1</div>
<div class='border1 quarter inline m10'>Div 2</div>
<div class='clear'></div>
<div class='border1 quarter fleft'>Div 1</div>
<div class='border1 quarter fleft m10'>Div 2</div>
CSS:
.clear {
clear:both;
}
Resulting in a "new line":
While a paragraph creates the same effect, it adds undesirable vertical separation between the two div
s.
HTML:
<div class='border1 quarter inline'>Div 1</div>
<div class='border1 quarter inline m10'>Div 2</div>
<p></p>
<div class='border1 quarter fleft'>Div 1</div>
<div class='border1 quarter fleft m10'>Div 2</div>
Result:
Let's say you want 3 div
s where the left and right are of fixed (in pixels) width
and the center div
resizes based on the browser width
. The only solution I found (after perusing SO) is to use the calc
style:
<p>Left and Right divs of fixed width:</p>
<div class='parent border1 fleft w100px h100px'>
<div class="child-top-left"><button>Button A</button></div>
</div>
<div class='parent border1 fleft h100px' style='width: calc(100% - 206px);'>
<div class="child-middle-center"><button>Button B</button></div>
</div>
<div class='parent border1 fleft w100px h100px'>
<div class="child-bottom-right"><button>Button C</button></div>
</div>
Since this calculation is so dependant on the number of div
s in the "row
", I decided to put this in as an actual style
rather than as CSS. The number 206 comes from the left and right div
s being 100 pixels each, plus 1 pixel for the borders for each div
: 100 + 100 + 1 + 1 + 1 + 1 + 1 + 1 = 206
.
The result works nicely regardless of browser window width:
Not ideal with this magic number, and compare this to the solution using tables.
According to the Mozilla docs: "The calc()
CSS function lets you perform calculations when specifying CSS property values. It can be used anywhere a <length>
, <frequency>
, <angle>
, <time>
, <percentage>
, <number>
, or <integer>
is allowed."
A lot of times, you'll see a div
that has a scrollbar when the content exceeds the height. Let's see how that's done using the overflow-y
and height
styles.
HTML:
<p>Left div scrolling:</p>
<div class='parent border1 fleft w100px h100px'>
<div class="child-top-left">
<div class='scrolly h100p'>
<p>Menu Item 1</p>
<p>Menu Item 2</p>
<p>Menu Item 3</p>
<p>Menu Item 4</p>
<p>Menu Item 5</p>
<p>Menu Item 6</p>
</div>
</div>
</div>
<div class='parent border1 fleft h100px' style='width: calc(100% - 206px);'>
<div class="child-middle-center"><button>Button B</button></div>
</div>
<div class='parent border1 fleft w100px h100px'>
<div class="child-bottom-right"><button>Button C</button></div>
</div>
Additional CSS:
.h100p {
height: 100%;
}
.scrolly {
overflow-y: auto;
}
Note that we have to use an inner div
for the child div
. The result is:
The scrollbar
goes away if the height of the child's inner div
is less than the child div
. What we lose though is the ability to vertically align the inner elements; they can still be horizontally aligned. This makes a kind of sense, as why would you have a scrollbar with content that is vertically centered or at the bottom?
Let's cut right to the chase and reproduce the previous layout using actual tables, rows, and columns. Here's the HTML:
<p>Tables instead of Divs</p>
<table class='w100p'>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
<tr class='h100px'>
<td class='border1 w33p vatop'><button>Button A</button></td>
<td class='border1 w33p tacenter vacenter'><button>Button B</button></td>
<td class='border1 w33p taright vabottom'><button>Button C</button></td>
</tr>
</table>
And the CSS:
.w100p {
width: 100%;
}
.taleft {
text-align: left;
}
.tacenter {
text-align: center;
}
.taright {
text-align: right;
}
.vatop {
vertical-align: top;
}
.vamiddle {
vertical-align: middle;
}
.vabottom {
vertical-align: bottom;
}
Resulting in:
Notice the space between the columns? To get rid of that, we have to use the border-collapse
style:
HTML:
<table class='w100p noborders'>
CSS:
.noborders {
border-collapse: collapse;
}
Resulting in:
As with div
s, table columns have hidden spacing you may not be aware of. I'm beginning to realize that it's a good idea to start a UI layout design with borders around everything so you can see exactly what's going on in the layout! This is easily accomplished by specifying the CSS style for your elements, for example:
table {
border-style:solid;
border-width: 1px;
}
th {
border-style:solid;
border-width: 1px;
}
tr {
border-style:solid;
border-width: 1px;
}
td {
border-style:solid;
border-width: 1px;
}
You can then delete this CSS when you're happy with the layout.
Here, the auto
style and the col
elements come into play. The following example illustrates the left and right columns having fixed widths and the center column being sized to fit between the two.
HTML (note I'm using the element CSS described above to set the border for the table
, tr
, th
, and td
elements):
<p>Three column with left and right in fixed pixels</p>
<table class='w100p noborders' style='table-layout:fixed'>
<col class='w100px'/>
<col class='wauto'/>
<col class='w100px'/>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
<tr class='h100px'>
<td class='vatop'><button>Button A</button></td>
<td class='tacenter vacenter'><button>Button B</button></td>
<td class='taright vabottom'><button>Button C</button></td>
</tr>
</table>
Additional CSS:
.w100px {
width: 100px;
}
.wauto {
width: auto;
}
This resulting in the center column resizing as the browser width is increased / decreased while the left and right columns remain fixed width.
As with div
s, let's see if we can get scrolling to work in a column. The solution is quite simple -- put a div
inside the td
! Contrast this with the approach described in the section on div
s, particularly note that the height
when using div
s was specified as 100% while here, it's specified as the column height, 100 pixels matching the outer tr height
.
HTML:
<p>Column Scrolling</p>
<table class='w100p noborders' style='table-layout:fixed'>
<col class='w100px'/>
<col class='wauto'/>
<col class='w100px'/>
<tr>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
<tr class='h100px'>
<td class='vatop'>
<div class='scrolly h100px'>
<p>Menu Item 1</p>
<p>Menu Item 2</p>
<p>Menu Item 3</p>
<p>Menu Item 4</p>
<p>Menu Item 5</p>
<p>Menu Item 6</p>
</div>
</td>
<td class='tacenter vacenter'><button>Button B</button></td>
<td class='taright vabottom'><button>Button C</button></td>
</tr>
</table>
Additional CSS:
.scrolly {
overflow-y: auto;
}
And we get:
So that's as far as I want to take this today. What I've shown here is that you can use div
s and table
s for layout control equally well, though given that some of div
layout requires having the div
behave like a table
and table
cell tends to suggest that using table
s is better than div
s. Also, some of the wonky things like using the calc()
function are not necessary with tables. I hope you at least had fun reading this and perhaps learned a thing or two. And if there's a better way of doing things, please leave a comment!