Cybergrain.com has now been retired. This article has been preserved because of it's popularity.

Thanks for visiting

Jon Meyer, www.jonmeyer.com

December 14, 2004

CSS Considered Unstylish - or why CSS sucks

Caveat: I worked at Microsoft for a year, and was heavily involved in the CSS discussion there. See More on Avalon Styling and CSS.

I think styling systems are fantastic, and I am very much in favor of the power and expressiveness that can be achieved through styles. A web with styles is much better than a web without.

However, CSS is the wrong styling system for the web.

CSS defines a mechanism for doing visual styling which requires visual designers to learn rules-based programming.

I've watched several web designers working at their CSS, their brows knitted with frustration. I frequently am asked questions like "Why is that extra whitespace appearing at the top of the div?" "How do I get these two pieces of text to line up?" "Why is that text blue instead of red?"

Using styles should not require programming or debugging skills. It should be based on drag-and-drop. Designers are used to the wonders of Illustrator. The web should be just as powerful.

Microsoft and Adobe have both independently jettisoned much of the CSS engine in their next-generation UI offerings (XAML and FLEX). FLEX claims to use CSS, but that's just good marketing. When you look under the hood and read the manual you realize that although Flex adopts some of the CSS syntax, it omits nearly all of the CSS selectors and cascade. Both Adobe and Microsoft experienced the drawbacks of cascading styles, and opted for something simpler. If only the open source community could follow suit.

In the future, the CSS standards group needs to offer solutions for based-on-styles, modular style sheets, browser detection, and a good WYSIWIG experience.

For the rest of this post, I talk about some of the major shortcomings of CSS.

CSS is Hard To Use

In his book "Cascading Style Sheets", Eric Meyer (no relation) says that CSS is easy to use.

I don't agree.

CSS uses a complex "cascade" to determine which rules apply to an element - something that takes fifteen pages in a book to explain.

As a result, there is no good authoring tool experience for CSS. Instead of direct manipulation, drag and drop, and visual design tools, web designers working with CSS must crack open their code-editors and start typing in text rules. CSS was not designed with drag-and-drop authoring in mind.

Code editors are not easy to use.

For example, consider these rules:

   h3 > strong { color: red; }
   h3 + strong { color: green; }
   h3:first-child { color: blue; }

Quick quiz: What color will this be:

   <h3><strong>Hello World</strong><h3>

?

How do you explain this to a visual designer? You need a good debugger and a lot of time to figure this stuff out.

Poorly Designed Language

The CSS rules-based langauge is:

* Too complex

* Has no reference implementation

* Has no runtime browser detection mechanism.

The result? A lot of headaches for designers.

Too Complex

CSS should simplify features and provide more orthogonality.

CSS has many features which are hard to figure out. Just one example is collapsing margins (see here). Collapsing margins are a sophisticated feature, and there is no easy way to turn collapsing margins off.

For example, consider the following rule:

    div { margin: 20px; }

CSS states that if a block has margins, they "collapse". To stop margins from collapsing, a block must have a non-zero padding or border. So to turn off collapsing you have to write:

    div { margin: 20px; padding: 1px; }

That introduces a side effect - the div has a 1 pixel padding. So you probably adjust the margins to account for the added padding:

    div { margin: 19px; padding: 1px; }

You've just added two side effects to the stylesheet because there is no "off" switch for collapsing margins. This is a classic example of CSS complexity.

It is also confusing when you are reading stylesheets - because CSS stylesheets don't encode the designer's intentions. If you see padding on an element, is it there for extra space in the design, or to turn off collapsing margins?

Rather than fancy conditions like "collapsing margins are turned off when there is either a border or padding", CSS should define a simple off switch like:

    p { margin: 20px; margin-collapse: none; }

There are plenty of other cases where the CSS folks chose complex and inconsistent behaviors over simple and predictable ones. Just for kicks, here's one more: coordinate systems.

In CSS, the width or height of an element don't include the margins/padding/border of the element. However, top/bottom/left/right DO take margins/padding/border into account. So elements have two frames of reference: one for width/height, one for top/bottom/left/right.

But wait, there's actually a third coordinate system: When an object is positioned absolutely, its top/left/bottom/right are relative to the parent layout element's position, including the parents margin and border but not padding.

To do any complex layouts, therefore, you must juggle three frames of reference. It would have been simpler and more consistent to use a single frame of reference for all three cases.

No Reference Implementation

CSS should define a reference implementation.

Given all the complexities in CSS, its surprising that there are no reference implementations.

The CSS community argues that "a reference implementation isn't possible, because it would amount to a full web browser." This is a poor argument. Any software standard can be broken into subparts, and there can be simplified reference implementations for each of those subparts.

In CSS, there should be reference implementations for the CSS block-level layout mechanism, for example. This could be created without having to implement all the inline and table layout rules. Multiple test cases could be created for this subset. These would be invaluable for implementors. Over time, by adding enough of these subset reference implementations, a comprehensive set can be created.

At the very least, for every feature, there should be an example, with a bitmap image, in the standard, showing what the results should look like.

No Runtime Detection

CSS should provide ways to detect which implementation is in use at runtime, so you can create different rules for different runtimes.

CSS is complex and lacks a reference implementation, so its very likely your stylesheets will need to be tweaked on different browsers. But there is no standard way to do that in CSS.

The CSS community argues that adding "browser detection" to the CSS standard would make browser implementors lazy - it would reduce the imperative for implementors to fix bugs, since there is an easy escape clause for users.

This is a poor argument. In the real world, implementations will always differ. So stylesheets will always need ways to target certain rules to specific browsers. Since there is no support for it in the standard, people resort to hacks.

Instead of seeing "clean" browser detection like this:

#if (browser == 'Microsoft' && version < 6)
    width: 400px;
#else
    width: 300px;
#endif

you see 25,000+ pages referencing things like this:

  /* IE5 Box Model Hack */
  width:400px; 
  voice-family: "\"}\""; 
  voice-family:inherit;
  width:300px;

This famous hack is hard to understand, hard to maintain, and impossible to justify.

Lacking Key Features

CSS lacks certain key features that other style systems have, including based-on styles, and ways to namespace stylesheets to make them more modular.

No Based-On Styles

CSS should support based-on styles.

One of my first big disappointments with CSS was its lack of based-on styles. There is no way to say "make this style use all the properties of that style, and add some changes relative to that style". In other words, you can't write things like:

    .Normal {
        font: helvetica normal 12pt;
    }
    .Caption :based-on(Normal) {
        font-size: 50%; /* half normal's font height */
        margin: +1em;   /* add an extra line of margin too */
    }

This is probably one of the most fundamental features of a styling system. Desktop publishing tools since the 80s have all had based-on styles. I don't know how W3C missed this.

Instead, in CSS, you have to write:

    .Normal {
        font: helvetica normal 12pt;
    }
    .Caption { 
        font: helvetica normal 6pt;
        margin: 1em;
    }

Now the relationship between these two styles is now lost.

CSS advocates argue that CSS doesn't require based-on styles, because you can do the same thing either using the cascade or using multiple classes, e.g.

    /* use the cascade */
     p { font: helvetica normal 12pt; }
     .Caption { font-size: 6pt; margin: 1em; }
or, with multiple classes:
    <p class="Normal Caption"> ... </p>

The problem with either of these mechanisms is that they lose the formal relationship between Normal and Caption - when I am reading the style sheet, there's no way I can know that Normal and Caption are related, or that Normal and Caption are intended to be composed in this way; the designer's intentions are lost.

CSS substitutes a well-understood and long-established explicit relationship (based-on styles) with something that is far more complex and relies on implicit semantics (and a mix of rigor and guesswork on the part of the designer).

No Module Mechanism

CSS should define best-practices for creating modular stylesheets

More and more websites are adopting templating systems - where a page is constructed from a set of templated pieces.

On a templated site, a single output page contains bits that come from many places. So the question arises: where do you put the styles?

If you put the styles in a single global stylesheet, the stylesheet gets large and cumbersome to maintain.

If you split the styles out and put a different stylesheet in each template component, you end up with lots of stylesheets and then you get surprising interactions between stylesheets.

The problem is that CSS has no tools for modularising a stylesheet to contain its effects. As soon as you write a rule like:

    td div { font: red; }
you are opening the door to unexpected interactions down the road.

A good practice is to always use class-based styles and explicit assignments:

    .blogpost_td_div { font: red; }

But then you have to do all the namespacing for your stylesheet yourself.

CSS does not have good namespacing or modularity mechanisms today.

Deeper Issues

The basic technical issues I've listed above could all be resolved in future CSS versions. However, I think CSS also suffers from deeper flaws at the root of its design.

CSS purports to separate style and content, but in fact it radically fails to do so. Rather, the opposite is true - CSS actually conflates style and structure. CSS stylesheets impose many implicit restrictions on the HTML structure. These restrictions are poorly defined and underspecified.

For example, consider the HTML:

   <h3 id="hello"><span>Hello World</span><h3>

This very common structure is used for Image Replacement.

It works with CSS rules like:

    h3 span { display: none; }
    #hello { background-image: url(...) ...; }

This CSS only works if the <h3> element has a <span> element embedded within it. In other words, the CSS encodes a structural dependency - the HTML must be the right shape or the CSS fails to work, and the user will see something that looks wrong (e.g. both the image and the text).

These structural dependencies are implicit, meaning that, when I write some HTML, I can't in any automated way test the HTML to see if it conforms to the dependency requirements of the stylesheet. Instead, I have to look at the results in a browser to see if they look right.

So, unlike XML Schemas, which support validation, CSS stylesheets have no validation mechanism. I can't write in CSS something that says "if the element with id=hello doesn't have a span as a direct child, generate a warning."

[I don't quite understand why standards-advocates scream when an XHTML document fails to validate, but somehow remain silent over this total lack of validatability of CSS].

Structural dependencies in CSS are nearly always underspecified: The h3 span selector above impacts any span element that occurs within h3 elements, even spans that are nested in other elements in the h3. Probably the designer only wanted to modify the span in the h3, but unfortunately IE6 doesn't support the direct-descendent selector (+), so a much broader rule has been used instead. The designer's intentions have been lost.

Underspecified structural dependencies are a deep property of CSS, and have many unpleasant consequences. For example, consider the task of taking a CSS stylesheet designed for one HTML structure and "porting" it to another HTML structure. You first have to decode the designer's intentions (encoded implicitly in the ruleset), then figure out how to map those intentions to the new structure. This can be hard.

As a case study, take a look at CSS Zen Garden. Lots of beautiful styles, all designed with that one very specific piece of content.

Once, as an experiment, I tried to copy part of one of the garden's stylesheets to my site. I discovered it was quite a challenge!

The story went like this: I copy-pasted a few style rules from their sheet to my sheet - but things looked strangely wrong. So I started guessing, adding more rules from the source sheet to my sheet; now the part I was styling looked right, but my footer was all messed up! So I deleted a few rules and ...!

It requires a lot of patience to do these kinds of operations in CSS. Rules have a tendency to get tangled together, and its hard to pull them apart. Often, its faster to start from scratch than to modify an existing stylesheet.

Relationships between Styles

A basic issue is that CSS has no way to explicitly specify the relationships between styles (e.g. how one style depends on another). Designers are forced to do this by using the cascade.

In other words, to get styles A and B to apply to a piece of markup in CSS, you cannot define style B in terms of A (B is A but bigger and with an extra border), Rather, in CSS, you arrange for A to apply to some parent element, and for B to apply to a child element in the document. An explicit relationship between styles is converted to an implicit relationship dependent on document structure. Yikes!

Often, to create more complex relationships between styles, designers add extra elements to the HTML. For example, many designers as a matter of course wrap a series of <a> tags in a <ul> <li> structure. Those extra elements are useful in the cascade, but once again mask the designer's intentions.

Because the relationships between styles are only manifested in the cascade, to determine how something looks, the browser needs all of the styles, and all of the cascade. This makes many operations much harder. e.g. implementing copy/paste for HTML/CSS and preserving appearance is hard. The new HTML may be pasted into a different point in the cascade - how do you modify the styles or stylesheets to preserve the appearance? My guess is that many coding cycles have been spent on that problem.

This is unfortunate. The two relationship systems (between-styles and from-styles-to-markup) should be separated. Stylesheets should contain a list of named styles, where each style is defined in terms of other styles (B is A but bigger and redder). Then, there should be a separate and very direct mechanism for indicating how a named styles maps to document markup - one that doesn't rely on a runtime cascade.

The easiest mechanism is to simply give every element a single explicit style attribute e.g. <p class="caption"> -- and stop there, no cascade! If you can define styles with relationships to other styles, this is sufficient. In fact, explicit style assignment with based-on-styles is very fast and powerful. And, more important, very suited to a drag-and-drop WYSIWIG authoring tool (Word, Frame, InDesign, Illustrator ...): You select some markup, click on a style, and bingo - you are done.

CSS sort-of supports this via its "class" styles, but without based-on styles the system is fundamentally hobbled. You are forced to use the cascade to achieve composition.

The CSS crowd will respond "Yes, but isn't it great that CSS lets you retrofit style to content, without touching the content, through a few simple rules?". I say no - as soon as you put structural dependencies in the stylesheets ("h3 span { }") then the stylesheets cannot be used on different content. And the runtime requires a complex cascade mechanism.

Rules-based programming language is the wrong solution for styles. Rules are hard to debug, hard to implement, hard to test, hard to validate, and a huge performance hit to boot. I've looked at the code for CSS in Firefox. Maintaining the runtime CSS state turns a simple SetXXX action into an astonishing amount of code.

I prefer other alternatives for mapping styles to content. For example, you can create a Schema for documents which defines how elements in the document are assigned styles. The schema could define document structure and at the same time indicate styles to use. Then, as the browser validates a document against the schema, it also automatically assign styles.

Or, alternatively, use a one-time dynamic XSLT transform to map an unstyled document to the same document but with styles assigned. Sounds funny? Google Maps uses client-side dynamic XSLT transforms today...

Or use a CSS-like rule set to assign named styles to elements, but apply it once, at tree-construction time, and not for every single property set.

Any of these 3 approaches are far easier to test than the rules-based programming of CSS, since you can look at the document structure before and after the named styles have been applied. And you can click on something on screen and ask which style it is using.

Conclusion

To conclude, I believe CSS is too complex, has no good WYSIWIG experience, and misses basic features found in other styling systems.

CSS would be stronger if it only offered named styles, based-on styles, no cascade, and support for modules/namespacing. Rather than a fancy untoolable rules-based programming approach, I would much prefer a separate mechanism (e.g. a one-step XSLT transform) for annotating unstyled HTML with the appropriate style names.

I only hope that CSS can become more stylish.

Comments