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
Doug Ransom | June 22, 2005 09:35 AM
I think CSS styles are swell, even if I end up replacing what should be markup (i.e. v8m 2a5 with v8m 2a5
If you want to look a styles done wrong, try Microsoft word. It is impossible to get a consistent looking document in any reasonable amount of time (or error free way).
Rob Waring | June 24, 2005 06:47 AM
You actually contradict yourself in the article. In the paragraph "CCS is hard to use" you say that CSS should not require programming knowledge. Then further down you mention " .Caption extends .normal" which is an OO programming function and
"#if (browser == 'Microsoft' && version width: 400px;
#else
width: 300px;
#endif"which appears in pretty much any programming language you care to name.
Did I miss something or have you just shot yourself in the foot?
jonmeyer | June 24, 2005 10:37 AM
It is certainly inconsistent to argue that something is too complex and then argue for two new features!
But both based-on styles and simple conditionals can be presented in a drag-and-drop GUI authoring tool without forcing designers to crack open a code editor or to learn programming languages. Programs like InDesign have similar features today.
The features must be restricted to simple cases (e.g. conditionals should be limited to matching against User Agent vendor and version; based-on styles should be restricted to single inheritance).
Both features would (I believe) end up simplifying people's stylesheets and making them more transparent.
Keith | July 18, 2005 10:38 PM
Some very good points you make - I've been arguing much the same against these standards evangelists for some time, but they refuse to see that their pride and joy is inherently flawed :(
As for those people commenting above who are claiming that you contradict yourself, they need to open their minds.
Extends is an OO keyword - that's all. it doesn't matter how it's implemented, the concept behind it is simple. "You take this thing, and extend it a bit to make this new thing". It's far less complex than "you need to add this piece of hack here, and this symbol group to make your page look right" or "you need to float THIS element, and then clear these elements, and then you need to make sure that this other element is still in the 'document flow'"
Open up your minds damn you - just because it looks like C, doesn't mean it has to be that 'complex' for people to understand. It's not syntactical complexity that makes CSS a dog, it's the fact that CSS itself has no semantical structure that works, and that the various models used within CSS are far too numerous and complicated, even seasoned professional developers can't get their heads around them.
Hampus | November 23, 2005 04:50 AM
You are completely right! That "contradiction" is just stupid. The lack of features sometimes makes it harder! Try to eat without a fork and knife, now would that be simpler? You need the right tools to do something in a fast and simple way!
Something else that annoys me about CSS is that it seems to be designed purely for "documents" and therefore requires some ugly hacks to do a cooler layout, like getting things side by side. We are of course dealing with HTML-documents, but people want to do more advanced and graphical pages too (I think the last ten years show that)... I really wonder why there isn't better support for that.Bepo | December 24, 2005 01:25 AM
Web development is backwards. It seems like every company that develops web sites is trying to reinvent the product development process and they are all doing it WRONG!
In all other industries designers design, thats what they do best. For some reason in the web world the coders have been able to push a large part of their work load off on designers. If you look at the web space as a whole you can see that we have a billion web pages that all pretty much look the same. If you ask a designer to design a web page and he knows he will also have do the css and html then the design is going to be limited to what he knows about css and html. If you set the designer free from those constraints then you WILL get fresh and innovative designs.BTW: I'm not a designer.
sollaa | January 24, 2006 10:54 PM
To Keith and Jon Meyer:
Cascading Style Sheets are already designed within an OO framework. What Jon derisively referred to as "Cascading" is simply OO Class Inheritance. Similarly, we should think of "rules" as methods. We create a new object from the class "foo.css" by inserting @import foo.css or another similar mechanism. There is a reason why the C-style subroutine notation was used for rules -- these are instructions for the browser!
Obviously, there is no reason to implement yet another full featured Turing complete computer language to do typesetting. There are enough JavaScript vulnerabilities to make me wary of using another Turing complete language in a web browser -- especially since a full language is not necessary. I do, however, agree that the language could be richer. Here I have in mind what Jon brought up about "conflating style and structure." It would be nice if I could write something along the lines of:
.class1 {
(width: auto)->Variable;
height: 1.6 * Variable;
}or even
.class2 {
width: Variable;
}But even then, you'd have to be very careful not to allow arbitrary arithmetic, or you'd end up with a Turing complete language again.
Also, keep in mind that you're complaining about broken browser implementations. This isn't the language's fault -- Perl, for instance, is far more complex than CSS. Blame lazy developers for not implementing 10 year old standards, let alone CSS2.
jonmeyer | January 25, 2006 06:58 AM
"Cascading" is simply OO Class Inheritance
Incorrect.
With CSS, cascading depends on dynamic properties of the document. For example, if there is an element A in a document, and you then modify the DOM's shape (include other elements either as siblings or ancestors of A), this effects the inheritance cascade for A - effectively, the "type" of A depends on the structure of other elements within the DOM at runtime.
In classic object oriented langauges, inheritance is specified lexically in the text of the program. You explicitly state that A inherits from B. Once you've created an object A and added it to a datastructure, you cannot change its type or inheritance. Adding other objects beneath or above A in the datastructure does not alter A's type or inheritance.
JavaScript is one unusual exception to this - with JavaScript, you can change the prototype chain dynamically at runtime, muting objects after they are created. However, this is considered bad form.
But even then, you'd have to be very careful not to allow arbitrary arithmetic, or you'd end up with a Turing complete language again.
To make a turning complete language you need the equivalent of flow control (conditionals and branches) - arbitrary arithmetic doesn't give you this. Also, why the arbitrary fear of "turing complete languages" - what harm have they done? we have a far better understanding of them than of CSS's cascade.
complaining about broken browser implementations. This isn't the language's fault ... blame lazy developers.
This is the pot calling the kettle black ("its not the standard fault, its the people implementing the standard who are to blame").
I suggest ou try to implement CSS yourself before calling developers "lazy". From my first hand experience, implementing CSS is incredibly difficult. Making it perform well is even harder.
If the CSS standard had started out more like a traditional based-on styling system, without the cascade, and with a good reference implementation, then those "lazy" developers would have had a much better chance of getting it right.
sollaa | March 1, 2006 05:44 PM
In classic object oriented langauges, inheritance is specified lexically in the text of the program. You explicitly state that A inherits from B. Once you've created an object A and added it to a datastructure, you cannot change its type or inheritance. Adding other objects beneath or above A in the datastructure does not alter A's type or inheritance.
I refer you to http://www.w3.org/TR/CSS21/cascade.html#cascade, since you appear to not be familiar with CSS cascading specification. The issues you describe are implementation faults.
To make a turning complete language you need the equivalent of flow control (conditionals and branches) - arbitrary arithmetic doesn't give you this. Also, why the arbitrary fear of "turing complete languages" - what harm have they done? we have a far better understanding of them than of CSS's cascade.
Arbitrary arithmetic is sufficient for a Turing complete language. I refer you to http://en.wikipedia.org/wiki/Recursive_function. I have already explained my views on sand boxed Turing complete environments, vis a vis JavaScript.
I suggest ou try to implement CSS yourself before calling developers "lazy". From my first hand experience, implementing CSS is incredibly difficult. Making it perform well is even harder.
The standard is obviously long and complex, but I have little sympathy for paid developers who do not satisfy their software's specification.
jonmeyer | March 1, 2006 08:39 PM
Arbitrary arithmetic is sufficient for a Turing complete language. I refer you to http://en.wikipedia.org/wiki/Recursive_function
Hey :-) Two can play that game. I refer you back, to http://en.wikipedia.org/wiki/Arithmetic. Recursive functions and lambda expressions no fair.
kryptx | March 7, 2006 06:03 PM
I refer you to http://www.w3.org/TR/CSS21/cascade.html#cascade, since you appear to not be familiar with CSS cascading specification. The issues you describe are implementation faults.
I'm not sure why Jon didn't respond to this, but I don't think you're grasping his objection. The statements he made are not describing actual, undesired CSS behavior; they are describing desired CSS behavior: "Once you've created an object A and added it to a datastructure, you cannot change its type or inheritance. Adding other objects beneath or above A in the datastructure does not alter A's type or inheritance."In an object-oriented language, we define inheritance explicitly using the programming language itself. By contrast, in CSS, we define inheritance implicitly by arranging our elements in a particular way. CSS does not provide a way to inherit styles unless the document is arranged such that the child is contained within the parent; i.e. if the child is actually a DOM child of the desired parent. Not only that but ALL children always inherit their parents' attributes unless they are overridden. There's no way to relate two objects unless they are related within the DOM, and conversely no way to explicitly specify that a particular DOM child should not inherit styles from its parent (except by overriding each style manually).
jonmeyer | March 7, 2006 08:58 PM
Thanks kryptx. I'd decided a continued debate on the finer details of OOP probably wasn't worthwhile given that sollaa and I can't even agree on the difference between arithmetic and lambda calculus :-)