CSS3 advanced layout module: templates. Discussion and proposal.

Submitted by naught101 on Fri, 11/28/2008 - 18:56

CSS template-based layouts, or something like them, have been a long time coming. John Resig has blogged about them recently, echoing the attitudes of a few people, it seems. I generally agree: this looks great, and will be a vast improvement for HTML+CSS web development: finally HTML document structure will be largely separate from visual layout. This is something that CSS grids/tables completely fail to do - divs still have to be in row>column order: a semantic change from HTML tables, and nothing more, and they still aren't supported by ie yet anyway (EDIT: Xanthir points out below that I was confused: CSS3-grid is actually a completely separate proposal to tables, and it's basically the same as what I suggest here, albeit without the ability to name the grid).

But...

Yep, of course there are a few things I'm concerned about (and as there should be - if there weren't I'd know I hadn't been looking hard enough). First, there are a few minor points (Disclaimer: I may have missed or misunderstood parts of the spec. Feel free to correct me):

Minor Issues

  • The value the template element has been called the return of ASCII-art. It's not. The layout doesn't have to visually line up - you can have multiple columns on one line. But if it is used on one line, how does the last row height (/Xem) work? Does it conflict with the column heights that follow? Could this be made more separate? Maybe a comma after each row, and another symbol after the last row? Actually, that wouldn't be needed, since the column-spacing row doesn't start with a quote mark. Something like:
    Example VI modified version
    body {
    height: 100%;
    display: "a   .  b  .   c"  /2em
             ".   .  .  .   ."  /1em
             "d   .  e  .   f"
             ".   .  .  .   ."  /1em
             "g   .  h  .   i"  /2em
             5em 1em * 1em 10em
    }
    body {
    height: 100%;
    display: "a   .  b  .   c"  2em,
             ".   .  .  .   ."  1em,
             "d   .  e  .   f",
             ".   .  .  .   ."  1em,
             "g   .  h  .   i"  2em,
             5em 1em * 1em 10em
    }

    This would be somewhat more understandable on a single line (same example, without the filler columns and rows):

    Example VI
    display: "a b c" /2em "d e f" "g h i" /2em 5em * 10em;
    Modified
    display: "a b c" 2em, "d e f", "g h i" 2em, 5em * 10em;
  • This solution still has the problem that if 5 columns are defined in the diagram, and only 3, or 6 column widths are defined, something weird will happen. I guess it wouldn't be hard with 3 to just add define the first three columns, and us "*" for the 4th and 5th. And for 6, just drop the last one... (See "Backwards Compatibility and Graceful Degradation, below)
  • What happens with odd shapes? Automatic fail? Like this:
    "aaaccd" < a-defined cells odd-shaped
    "aabbbd"
    "aabbbd"
    "aadddd" < L-shaped d cell

Not Quite a Complete Solution

This implementation, while far better than what's been the standard so far, is still somewhat limited, in two major ways:

  • Sub-Child problem: The layout is restricted to direct child elements, meaning that two elements of html that should appear within one another structurally can't appear side by side visually. Also, sub-children can't become part of the parent layout (I assume). This is not a killer, since most likely there aren't going to be many situations in which this will need to happen: usually visual and navigational elements should be fairly separated from the main content element, and all three within the same parent element. (EDIT: this isn't actually a Sub-Child problem, it's a cell-naming problem, see comments below)
  • The method of defining the layout could be problematic if it needs to change on-demand. Changes might occur either because some parts don't appear on some dynamically generated pages, or because things move as JavaScript changes DOM elements. I think this has the potential to be painful: basically the entire layout defined by the parent element would have to change depending on the page, so every possible page layout combination would need to be defined (and each combination would need it's own CSS identifier). I can't see a way to easily dynamically change the Ex.VI layout (above). I guess this could be overcome by having nested templates, but that would make the sub-child problem worse.
  • This is mostly a pet hate, and probably isn't really relevant, but the display: property is already used in css. I disagree with the display: table-* values already. It means you can't define an element as a table element AND as "hidden" or "none", but I guess they are usually mutually exclusive anyway. Nonetheless, something like layout: or template: would make more sense to me (I think the table-* values should also have their own property, perhaps grid:...)

A better way?

Might there be a better way? Nicholas Shanks has a decent idea in a comment on John Reisg's blog post. The basic idea has a number of benefits:

  • It appears to fit more with the general CSS style of defining properties and values.
  • It would be much easier to dynamically change, either server-side or client-side.
  • It has the potential to completely remove the sub-child-as-cell problem (See below).
  • It would be harder to fuck up (impossible to make non-rectangular shapes).
  • It would allow overlaps and intrusion (See the graphic)!
  • It works with the already existing width: and height: properties (although maybe the current proposal does too).

Nicholas's version is not without it's own problems though. I don't understand why he uses CSS3 pseudo-elements. I think a grid element should definitely be a whole element, just as a <td> cell is in an HTML table. His method also involves no way of naming the template (and thus escaping the sub-child problem, see below). His method may take up more text in a CSS file, depending on the layout.

My proposal is a modification of Nicholas's, which addresses the first two of these problems, and maybe the third (haven't thought about a lot of cases yet).

The parent element would be defined thus:

#container {
template: "container name"
}

and the child thus:

.cell {
template-cell: "container name";
template-cell-col: 1;
template-cell-row: 1;
template-cell-rowspan: 2;
template-cell-colspan: 1;
}

Where template-cell: could contain -row and -col, like the background: property can contain background-image: and background-color:. I don't know how -rowspan and -colspan: would fit into that, but perhaps -row: and -col: could accept row/colspans like:

template-cell-col: 1-3;

(for spanning columns 1 to 3, I'm not sure if the hyphen is an appropriate symbol).

Ultimately, it could be condensed thus:

.cell {
template-cell: "name" 1-2 2;
}

The naming is important, because it means that the cell elements don't rely on their place in the document structure to find out what their parent element is. The child can be inside 50 nested divs, but once parsed, it will be ripped out, and become a higher-level element (though only visually). This could also be done by simply referencing an ID. An ID could reduce the possibility reduce the chance of accidentally defining the  template: property in a generic element, like <div>, but it would loose some of similarity in style between the parent and cell property definition.

Comparing a complex example:

Example XVII Proposed version
body {
display:
"A  A  A  A  A  A  A  A  A" /5cm
".  .  .  .  .  .  .  .  ." /0.25cm
"B  .  C  C  C  C  C  C  C"
"B  .  C  C  C  C  C  C  C"
"B  .  C  C  C  C  C  C  C"
"B  .  C  C  C  C  C  C  C"
"B  .  C  C  C  C  C  C  C"
"B  .  D  D  D  D  D  D  D"
"B  .  D  D  D  D  D  D  D"
"B  .  E  E  E  .  F  F  F"
"B  .  E  E  E  .  F  F  F"
"B  .  E  E  E  .  F  F  F"
 * 3em * 3em * 3em * 3em *
}

h1 {
position: a;
margin-bottom: 1.5em
}

#toc {
position: b;
margin-right: -1.5em;
}

#leader {
position: c;
}

#art1 {
position: d;
}

#art2 {
position: e;
}

#art3 {
position: f;
}
body {
template: "main";
height:5cm;
}

/* dealing with gap rows/cols */
#headerspacer {
template-cell: "main" 1 1-9;
height: 0.25cm;
}

#leftspacer {
template-cell: "main" 1 1-9;
width: 3em;
}

#bottomspacer {
template-cell: "main" 10-12 6;
width: 3em;
}

h1 {
template-cell: "main" 1 1-9;
margin-bottom: 1.5em
}

#toc {
template-cell: "main" 3-12 1;
margin-right: -1.5em;
}

#leader {
template-cell: "main" 3-7 3-9;
}

#art1 {
template-cell: "main" 8-9 3-9;
}

#art2 {
template-cell: "main" 10-12 3-5;
}

#art3 {
template-cell: "main" 10-12 7-9;
}

This solution has a more CSS feel - the Working Draft proposal has already been described as looking "a bit hacky" by Ben Ward (he is still very approving of the proposal though). The largest problem with this is that it doesn't deal with the 3ems on the 4th, 6th and 8th column. If these were required, more spacer elements could be added (although I already see the current ones as redundant - why not use margins?). I don't quite understand their purpose, but I assume it's basically the same as min-width: 3em - that would have a similar effect, and reduce the complexity of the grid somewhat.

Obviously, this method probably has holes in it that I don't see, if you see any, please point them out.

Backwards compatibility and Graceful Degradation

This is always going to be the biggest problem and can't be overcome by the above solutions, and will require some kind of major-browser manufacturer collusion, if it's to be accepted widely. I posted this RFE for gecko (firefox, etc) specifically for this problem: https://bugzilla.mozilla.org/show_bug.cgi?id=466974

Graceful degradation is the flip side of the same coin: document structure will have to maintain some visual usefulness if the template fails. Templates will fail, either because they aren't implemented in the browser, or because they have a bad value - mis-matched column numbers, non-rectangular layouts (L shaped boxes, etc). But ultimately, the spec is implemented accros browsers, that will be the designer's problem, and not the fault of the browser implementation. Once this (or something like it) is implemented accross browsers, structure will finally be free from layout constraints.

Conclusion

I offer my solution in the hopes of making people think about things in ways they haven't before, and of kindling discussion. If the final outcome is basically what the Advanced Layout Module Working Draft already proposes, then so be it. Nothing is ever perfect, there are no backwards steps in any of the solutions already put forward. What ever happens it will be good. Let's hope it can happen soon.

Comments

I've only skimmed the working draft (bad me, but I'm short on time).

I can't see how the working draft causes the "sub-child" (should this be just "child", "sub" seems superfluous?) limitation. The examples use children as the cells, but this just seems to be because they're comparing it with the table model, which does require parent&gt;child relationships in the markup.

Yes, it would be annoying to have to define a layout for each page element combination (and having to rely on the markup to specify which combination it was would be even more annoying). Perhaps this can be overcome by not setting dimensions for empty rows/columns and specifying dimensions directly on the elements that are to appear in the table (and then rows/columns with no or hidden elements would shrink to zero)?

Funny shaped areas: perhaps this is intentional? Perhaps the elements placed in the funny shape are intended to flow into the space (the way elements do around conventional left or right floated elements)? Perhaps the neatest way for this to be described is to use the grid format in the working draft?

I think use of the display and position properties is appropriate (or appropriate enough). Like you say, the "none", "table" and proposed template values are all mutually exclusive (we don't care whether it used to be a table or template layout if it's being hidden). Actually, I retract that: when using JS to dynamically hide elements, one would have to store the current "display" value before setting it to "none", and reinstate when unhiding the element. Not a big issue, but a bit Boring.

There's still a problem, due to the possibility of nested templates: if the first template and the nested template both have an "a" cell, I would imagine the element defined with <em>position: "a";</em> would default to being a cell of the template closest in the hierarchy. You're right though, this is not a sub-child problem, since on re-reading parts of the Draft it's obvious that a parent-child relationship isn't necessary, it's simply a problem with multiple templates with the same cell names. Some way to define a name for each template, or possibly having a way to select a layout by ID selector, would fix that.

No, not setting the width/height wouldn't over come it in all cases, ie:
"a a a a"
"b c c c"
"b c c c"
"d e e e"
If you simply want to remove "d", and extend either "b" or "e" to that position, the template won't let you, because of the existing dimensions of the other element.

The funny shapes thing is plausible, but element shape isn't mentioned in the spec at all. If you were going to do something like this, why limit yourself to weird tetris shapes? Why not just use something like SVG? It's almost certainly going to be implemented widely before this CSS3 module is...

I guess that's true about the display property. If the element needs to stay there but be invisible, there's visibility: hidden. The display property just feels huge an convoluted. There are almost two distinct sets of values within it:
Single-element display values, like "run-in", "block" and "inline"
Multiple-element values, like "table", and then the new template system, designed for layout
and "none", which is unique, and "marker", which is also.
Perhaps just splitting the second group of into a similarly generic <em>layout</em> property?

Xanthir, FCD (not verified)

Mon, 12/01/2008 - 02:31

I already replied to your proposal on the www-style mailing list, but I'll reply here as well to make it more easily searchable.

Essentially you're reinventing the wheel - what you describe is merely a rewriting of the CSS3 Grid Positioning Module ( http://www.w3.org/TR/css3-grid/ ). Don't worry, though, basically every proposal made in response to Template Layout that I've seen is of this form, so you're not alone. ^_^

The WG needs to put a reference into Template referring to Grid Positioning, since Grid is basically Template's big brother. Template is extremely easy to use while authoring and easy to visually get an idea of how the page is laid out. Grid how more power, but isn't quite as intuitive. The two together are a good mix I think.

Also, in response to your last comment, display *will* be split into two properties, display-role and display-model, with pretty much the distinction you are making. The display property by itself will then be a shortcut property, like margin is for margin-left, margin-top, etc.

Finally, that quote you use from Ben Ward's blog is EXTREMELY dishonest. It's used completely out of context, which is disgusting. The full sentence you grabbed the quote from is "Sounds a bit hacky, perhaps even silly but I think it’s a good move; it’ll be very easy to explain and grasp." You directly imply that the Ben Ward disapproves of Template Layout, and rely on the reader to correct that misapprehension themselves. You need to either take out that reference altogether, or put in a parenthetical note that Ben still believes Template Layout to be a good idea.

Firstly, you're right, that quote does make it look bad, it wasn't intended that way. I'll re-word it.

Re: CSS3 grids: I know about them, I mentioned them in the first paragraph. The main problem with CSS Grids is that there grid cells have to be in the correct order in the HTML. This proposal doesn't require that: due to the "name" value of the template property. Also, this proposal allows intrusion (described in the advanced layouts WD), which CSS Grids doesn't.

I fully realise that my butting into a w3c process that's already many years old is a bit impudent, so thanks for the positive response :)

Xanthir, FCD (not verified)

Mon, 12/01/2008 - 14:58

(Thanks for correcting the quote. I retract my harsh words.)

Nope, follow the link I provided. You're talking about CSS Tables, I'm talking about CSS Grid Positioning. Completely different things.

In Grid Positioning, you give an element a grid with the grid-rows and grid-columns properties. You can specify row widths and column heights with any of the CSS length units, plus flex units (units that distribute remaining width between themselves proportionally. Then you use the normal position: and width:/height: properties with the gr unit (for "grid unit", corresponding to the context-sensitive width or height of a grid row/column, and determining the context is a little complex but generally does what you'd expect) as appropriate.

Duplicating the template layout you use for comparison would be something like (I'm going off the top of my head, so consider this pseudocode):
body {
grid-rows: 5cm .25cm 5* 2* 3*;
grid-columns: * 3em 2* 3em 3em 2* 3em;
position: relative; //to establish this as a positioned ancestor
}

h1 {
height: 1gr;
}

#toc {
position: absolute;
left: 0;
top: 2gr;
width: 1gr;
height: 3gr;
}

#leader {
position: absolute;
left: 2gr;
top: 2gr;
height: 1gr;
width: 5gr;
}

#art1 {
position: absolute;
left: 2gr;
top: 3gr;
height: 1gr;
width: 5gr;
}

#art2 {
position: absolute;
left: 2gr;
top: 4gr;
height: 1gr;
width: 2gr;
}

#art3 {
position: absolute;
left: 5gr;
top: 4gr;
height: 1gr;
width: 2gr;
}

Note that in my email to the list a week back or so about the template layout, I proposed adopting a proper flex unit, so your example could be simplified quite a bit more to:
body {
display:
"aaaaaaa" /5cm
"......." /.25cm
"b.ccccc" /5*
"b.ddddd" /2*
"b.ee.ff" /3*
* 3em 2* 3em 3em 2* 3em;
}

(Though you'd probably want to replace the last two row-heights with "intrinsic", since you're putting pictures in there which may have varying heights.)

There is still a slight limit in Grid Positioning that doesn't exist in your proposal, namely that you can only position based on the nearest positioned ancestor, but this is the same limitation that exists in normal CSS positioning. (Note that you can still use gr units without positioning if you want, and they'll instead inherit from the nearest grid ancestor, which may be different from the nearest *positioned* grid ancestor.)

And don't worry about butting in. Advanced Layout hasn't moved in years. Designer response is the surest way to get something worked on. ^_^

My proposal is now reduced to one thing: Name grid ancestors and their sub elements :). I guess in most cases there's not going to be any serious need to avoid a HTML-structure hierarchy that doesn't match the visual structure if grids are used (since all content and all visuals are always included in the body anyway).

Thanks for your response Xanthir, very educational. I had a feeling that I should sit on this for a few days after I wrote it, but I guess I got over excited. I'll have a read of the grid spec, it sounds good.

Xanthir, FCD (not verified)

Tue, 12/02/2008 - 00:10

No problem. I like educating. ^_^

Note, though, that your newly reduced proposal can be generalized quite a bit for much greater usefulness.

First, realize that all Grid actually does is set up a custom context-sensitive length unit and then specify where it can be used. This is why Grid is so easy - it can interact with all the existing properties because it lives on a completely orthogonal level, that of the unit rather than of the property.

So, when you say you want the ability to name grid ancestors, what you're really saying is that you want to be able to specify positioning ancestors. Wanna work on a proposal together? ^_^