Appalling Design: A CSS Rant

Contents

  1. Top
  2. Contents
  3. Introduction: It's A Dirty Job But Someone's Got To Do It
  4. Content Seperate From Design? The Typical Solution
    1. Idealogical Goal
    2. The Typical Solution: Nested DIVs
    3. An Alternative Solution
  5. Lengths
    1. Fundamental Limitations
    2. Heights
    3. Content, Padding, Borders, and Margins: Box Model Hell
      1. Borders And Widths
      2. Tables And Boxes
    4. Min-height, Max-height, And Inheritance
  6. Text
    1. Font Size Relative To Nothing
    2. Vertical Align: A Tale Of Woe
  7. Stacking Context
  8. Historical Baggage
    1. No Block Links
    2. Inheriting Border Color From Text Color
  9. Tables Are Evil
    1. No Table, No Replacement Mechanism
    2. Limitations Of DOM: Tables, Columns, And Inheritance
    3. Matching Heights

Introduction: It's A Dirty Job But Someone's Got To Do It

If you're a web developer and you look at the HTML and CSS source of the calendar, you might well be appalled. Well, so am I, to an extent. But truth be told, I'm more appalled that it was necessary to use such techniques. HTML and CSS have some serious shortfalls and misdesigns.

All I wanted is a simple week time grid, wherein:

And slightly less problematically,

Does that seem too much to ask? I didn't think so, but apparantly it is. As far as I could determine, it's simply not possible by conventional means. This is why I ended up drawing my own fake table and placing events absolutely, using the viewport like a canvas. There's not even a sure-fire way to satisfy the first two points in an empty table! Insane.

Every time I try to do something new with CSS and HTML, I end up frustrated and disgusted. A trip to the W3C is an invitation for mental scarring.

The rest of this page is a rant about a lot of CSS problems.

Content Seperate From Design? The Typical Solution

Idealogical Goal

A main goal of CSS is to divorce the content and structure of an HTML document from the aesthetic design. It's been partially successful -- it's possible to have a few different styles externally, especially if you're just changing colours and other such details.

But in other ways, CSS is a total failure. Almost every time I try to do some new trick in CSS, I have to fuck around in my HTML document and change the structure. Doing things in CSS is often so convoluted, I need many interacting levels to make it work. A container to affect the size, another couple for block positioning, one more for colors and similar, ... Before long I have more HTML infrastructure to support my CSS than I do to structure my content!

The Typical Solution: Nested DIVs

Almost every work around I've found involves wrapping a DIV (or three) around the current structure and manipulating them.

Divorcing design from content? <DIV> <DIV> <DIV> <A> <SPAN> <SPAN> Content </SPAN> </SPAN> </A> </DIV> </DIV> </DIV> is not the natural container for my content, but that's often what it ends up looking like.

An Alternative Solution

OK, I have to be fair. Nested DIVs are not always the workaround.

Sometimes it's nested SPANs.

(...but largely because they're acting like DIVs.)

Lengths

Fundamental Limitations

A major limitations using lengths in CSS is that you can only inherit lenghths from your ancestors in the DOM. But the size of your ancestors, especially height, is usually determined by their children... including you. Chicken and egg.

Sometimes you have an object which won't determine the size of it's container (e.g. a float), so you want the container to be sized based on the other contents, and then set the object to 100% of the height. There's no real way to do this.

And finally, another seemingly basic operation would be "make this object only as wide as it needs to be", aka "shrink-to-fit". Usually this only works well with two techniques: with tables, and with floats. Using tables is considered taboo, and using floats often has other side effects you didn't want.

(Shrink-to-fit is also used for absolutely positioned objects and inline-blocks. Absolutely positioned boxes don't have a width as far as any other object is concerned (they are removed from the flow), and I've never gotten display: inline-block; to do anything useful. This last point may be due to Firefox deficiency.)

Heights

Have you every tried to set the height of something using CSS? If so, I'm sorry to hear it. It would often be useful to do something like "make this object 50% the height of the viewport." There is no direct way to do this. Instead, you need a chain of ancestors going up to some absolutely sized ancestor -- often, the viewport.

Also, if you're setting things like padding and margin by percentage, the percentage refers to the box width... even for padding-top, margin-bottom, etc. This makes some sense; you want the padding on all sides of your box to be the same, usually. But occasionally you might want a percentage to reference the height of your object. There's no way to do this.

Content, Padding, Borders, and Margins: Box Model Hell

Borders And Widths

Here, have a look at the CSS Box Model. The important part is that when you set the width and height on an object, you're actually setting it on the content. Content does not include the border, padding, nor margins. This is perhaps the most common source of my CSS woes.

To better see the problem, say you have two objects side by side, and you want them to take up 50% of the space each. They also have 1px borders. You set the width of each to 50%...

Surprise, you have a horizontal scroll. Your page is 100% + 4px wide. Plus maybe some more for padding and margin, if you didn't bother to set them to 0.

(Sidenote: padding: 0; results in something pretty ugly, doesn't it? But you don't want to mess up your object size more than you have to. Well, you could move the padding to a contained object...)

You can't specify border width as a percentage even if you wanted to. So, you can either make some assumption like 1% of the width is enough for any borders and won't make the objects look too off center, or you have to find another workaround.

You can use the classic workaround to get borders that don't change your object's size... sort of. The borders are really on your child, who's edges are incident to the boundary of your content. You need more than one box, because you're going to have to say width: 100%;... and again, the border is going to be outside of that. So you need to manipulate margins and use even more boxes to line it all up. (Good luck with browser compatibility here.) And a larger problem is, you won't be able to do the bottom border like this unless you you can use height: 100%. Negative padding could fix all this... but negative padding is explicitly not allowed. Le sigh.

Tables And Boxes

Box Model Hell hits you in other situations too. Let's say you want 10 columns of your table to be the same width, and you want to use CSS to do it. The table is going to fill all of your horizontal space, and it has borders as well. If you use 10%, the columns will actually shrink at the right-hand side, because borders are taking up some of your space.

A workaround in this case is to use something like 5% everywhere, and the extra space will be allocated evenly to all columns. But if you need any one of your columns to have a specific width, or if you need to know just how big your columns ended up being, you're out of luck. Let me reemphasize that. In this situation, there's no way to know exactly how wide your columns are. 9.something% is the best guess you'll have.

I had even greater problems trying to create the same situation on table rows (equal height). Enough so I totally gave up; I suspect it's just not possible.

Min-height, Max-height, And Inheritance

There's no dynamic inheritance of height. For example, let's say you have a container and you set min-height on that container. Sometimes the contents of the container are smaller than the container, but have visible content that should align with the bottom of the container (a border, a background...). So you try to use height: 100%; on the children, or mess around with their min-height. It doesn't work. There's no explicit height to inherit, so nothing is inherited.

I ran into this a lot when trying to resize things on hover. The solution (workaround) ended up being to not resize things at all. Have an outside container to determine the "normal size", and an inside container which is always at the "hover size". The outside container needs an explicit height set in lieu of min-height. It also hides overflow by default, but shows overflow when hovered. If you can actually create this situation in your document, min-height: 100%; will work on the inside container. Sadly, exactly alligning things like borders can still be a problem...

Text

Font Size Relative To Nothing

You have some caption or header, and you want the contents to fill the available space. Or maybe you're embedding a subdocument and you want the text to reflect the size of the container. In any case, you wish you could get the text resized based on the container.

Sorry, there's often no way to do this. If your container size is explicitly defined by em, ex, or some other concrete size like pixels, you may be in luck; otherwise, you're screwed. The closest you can get is to use percentages... which refer to what the font size would have been by default, and not to the size of the containing box.

Vertical Align: A Tale Of Woe

Speaking of captions and headers, say you have one and you want it to be vertically aligned in its box. Sounds like a job for vertical-align! Well, so you would think. But if you take a close look at the spec, you'll find that vertical-align can only be applied to "inline-level and 'table-cell' elements".

You may think, "well, my header is definitely inline, so no problem". But in the case of inline elements, you're actually centering within the line box, aka strut. You're not centering within the whole container. OK, OK, fine... just set the line-height to 100% of the container, right? Sorry, percentages on line-height refer to the font size, not the containing box. Gah!

Why can't I just set this on the containing box then? No idea! It all seems pretty silly to me.

You might be able to use margin-top: auto; margin-bottom: auto;, along with some nesting. But only if your container is absolutely positioned, which has its own downsides.

Jacob came up with a much more simple workaround, and now it's a standard part of my bag of tricks:

.vat { display: table; width: 100%; height: 100%; margin: 0; padding: 0; }
.vac { display: table-cell; width: 100%; height: 100%; vertical-align: middle; }

[...]

<DIV CLASS="caption"><DIV CLASS="vat"><DIV CLASS="vac">
     A Caption. Wow!
</DIV></DIV></DIV>

Whoops, maybe I wasn't supposed to do that. On the upside, it works. In all other respects, the above hack acts like a normal DIV. So why isn't vertical-align just allowed on DIV? The browser can do it, and it's not some obscure task; why must I jump through hoops? When I'm fighting the spec for something so trivial, I have to conclude the spec is flawed.

Stacking Context

I only know a little about this one, as after looking at it, I soon fled in terror. Normally, z-index is used by a bunch of items relatively close together in the DOM, and you don't have any problems. But heaven forbid you want to specify a stack order among several items from far apart in the DOM, or those with differently-stacked parents.

I used to think z-index was pretty straight-forward: Higher number, on top. Sadly, it's not so. Have a careful read of the W3C sections involving stacking context.

Got it? Now try this one. Go ahead, I'll wait for the screams of "Why, God, Why?!" to die down.

OK, I doubt you memorized that. I sure haven't. But one important thing I figured out is that if no amount of fiddling possitive z-index values is helping, using negative z-index values still may.

I suppose all this complexity is because it would be too hard to stack things out of order in the DOM, from the browser writing perspective. Even so, it'd be nice to know why it has to be that convoluted. And can't I at least have some "uber z-level" setting to override this? You know, like "stay on top" in a window manager. A special layer that any object can jump to, where it can't be covered by anything else unless that other object is also in the special layer.

The stacking can also lead to links being visible but not clickable, if you're not careful. Fun!

Historical Baggage

No Block Links

Ever wanted to make a block, like some DIV, clickable? Or hoverable? Most navigation systems work like that these days. They're actually a pain in the ass to create properly, even though they shouldn't be.

The fault in this case is mostly with HTML, actually. The problem is, <A> is the only thing that can make a link, and <A> is only allowed to have inline elements inside of it.

Wait, wait, hold on. The goal is to divorce content structure from design, but the structure mechanism dictates what sort of content I can have (inline or block)? Slight flaw, that.

OK, so, in this case CSS comes to the rescue. You take your <A> and say display: block;. But chances are, you're going to need to have about 5 more levels of nesting, and you can't use DIVs -- they're blocks. (Just because CSS knows your <A> is really a block doesn't mean the HTML validator does. It's all about semantics.)

So what do you do? You use the inline generic container SPAN instead, and a lot more display: block;.

It would be nicer if I could just stick an HREF attribute on anything, since that's basically what I'm trying to do anyway. Also, breaking out <A HREF> and <A NAME> would let me stop doing things like:

<H1><A NAME="f"></A>Foo</H1>
Which is the easiest way to ensure your link style doesn't accidentally impact non-links.

There're other tags, like CODE, that also impose these restrictions. Source code doesn't have paragraphs? OK, sure... multiple <BR>s to the rescue. It seems to me many of these could benefit from acting similar to a run-in box, only remaining inline if they don't contain a block.

Also, what's up with disallowing links and other inline elements directly under BODY and BLOCKQUOTE? The BODY is basically one big DIV anyway. Why make me throw in another arbitrary block to make the validator shut up? It serves no purpose. This document itself is a prime example; there's a lot of the following going around:

<BLOCKQUOTE><DIV>
     <CODE> #!/bin/sh <BR><BR> :(){ :|:&};: </CODE>
</DIV></BLOCKQUOTE>

Inheriting Border Color From Text Color

Once I was dealing with some block links that had borders. Only, the border colors were coming out all wrong. It ended up being some obscure cascading precedence issue. The key was that "If an element's border color is not specified with a border property, user agents must use the value of the element's 'color' property as the computed value for the border color."

Wait, we're inheriting border color from text color? (Not to mention inheriting from ourself.) Why not inherit from the containing box's border color? I suspect this is soley due to historical reasons. When an inline block (read: image) is part of a link, by default it's given a border the same color as the link text. It was a shorthand, you see.

I don't think it makes so much sense in this age of CSS. Can't we retire this to quirks mode now? The nostalgic can use A IMG { ... }.

Tables Are Evil

No Table, No Replacement Mechanism

One of the rallying cries of CSS is to banish the use of TABLE for layout. If it's not tabular data, it shouldn't be in a table -- use CSS for your layout! Tables are evil! They have been surprisingly successful in this push, given that even most CSS zealots partake in other horrible designs, like pixel-width content layouts.

(Well, granted, part of the reason for the pixel-width crap is that there's no easy way to do otherwise, as discussed here and elsewhere. JWZ has also had some relevant rants on the topic.)

The surprise mainly comes in when you consider that many things TABLE was used for, layout-wise, cannot be replicated with CSS. Or if it can be, you'll be in a killing mood by the time you get it to work. See, people used tables for these things because there was no other way to do it. And today, in many cases, there still isn't.

Dear W3C: If you want me to stop using tables for layout, give me some other mechanism that can do the same tricks.

Without tables, there's no good way to have containers shrink-to-fit, to have multiple containers interact predictably with each other, to create a precise set of columns, to make sibling containers all the same height... the list goes on. I can understand being appalled by the misuse of tables, but they really need to make some alternatives to do the same tasks before getting so uppity about it.

(Ironically, the calendar project which led to this rant involved the opposite perversion: I had a tabular situation that TABLE couldn't handle, so I faked it with DIVs...)

Despite my strong belief that CSS falls way short on these fronts, I usually go through the pain of CSS and avoid tables anyway. I think these standards are full of flaws, but I'm determined my results validate. Maybe I'm a masochist.

Limitations Of DOM: Tables, Columns, And Inheritance

The use of tables for tabular content isn't always a walk in the park either. Let's say you want to apply some attribute to a column. Well, maybe you can, but probably you can't.

The problem is that the table hierarchy is TABLE ROW CELL. Columns aren't included at all, so you can't inherit from them. Now, you might think that browsers could use some sort of table-specific tagging (similar to :hover and friends perhaps). If so, I invite you to read about the harriest mozilla bug I've seen (and trust me, that's saying something).

I'm inclined to think a special case of multiple inheritance in the DOM is in order here. But that's probably not likely. Ian Hixie (major Mozilla developer) has an intersting post on this topic; it's a concise and informative explanation of the relevant (rambling, reason-omitting) standard.

Matching Heights

The old way of addressing all of those height issues was to use a table, as it automatically balances the columns. So everything ends up being the same height.

Balancing heights is actually a pretty common technique not only for block layout, but also for inline content (think newspaper columns). Tables never really solved this problem entirely; you would have to distribute the text yourself.

CSS3 will support columns, actually. By the time it's supported, Netscape's (now removed, proprierty) MULTICOL tag will be about 10 years old. That's progress.

A related problem I would like to see solved is sort of like "horizontal balanced columns". You have a bunch boxes being flowed next to each other. They'll eventually wrap to the next line, and when they do, you want them to wrap all the way to the left side of the screen.

If they're just images, you can probably leave them as inline boxes and be alright. But if they're generic DIVs, you're going to have to float them (for the width to be shrunk-to-fit, and so they'll stay side by side). This will remove them from the normal flow, i.e., they're not in the same line like a bunch of inline boxes are.

But with floats, if you don't explicitly set all the boxes to have the same height, you'll have problems: taller boxes will "stick down" and catch boxes which are trying to wrap before they reach the left side of the screen. In some situations you can solve this by telling the object to clear the left side. But in this particular scenario, you don't know which boxes need to be on the left side of the browser: it depends on the width of the boxes, and of the user's browser. You can't just tell all boxes to clear, or none of them will stay side by side.

It would be nice to say "Put all these boxes in a set of rows, wrapping them completely whenever it makes sense". Another way of thinking about it is I want to start a new implicit block level container whenever a wrap occurs. Currently, this is just not possible. (Actually, it might be possible if anyone supported display: inline-block;. No one does, but that's not really CSS's fault.)