31Aug2009
Filed under: agile, release blog
Author: exortech
I’m a big fan of CSS – it keeps things looking consistent, it separates structure and design, and it keeps markup clean, simple and maintainable. The greatest strength and weakness of stylesheets is their scope. A large number of pages are typically styled by a single stylesheet. This is great for consistency and reuse, but it means that it can be difficult to assess the impact of changing a style without verifying every page that uses it – in every supported browser! Changing a global style could break the layout somewhere in the site in ways that could easily go unnoticed.
Hence, when releasing software to production every week, the cost of making style changes can be prohibitive. It is difficult to regression test all impacted pages in all browser combinations within a reasonable amount of time. Another challenge is that in CSS there are many ways to achieve the same thing, though each approach could render differently in different browsers. And in a site that is changing rapidly, existing designs need to evolve to incorporate new features, usability improvements, user feedback and better ways of doing things.
Stylesheets can’t be seen as being too risky to change. Otherwise they will be subverted through local inline styles and other workarounds (the same goes for common javascript libraries or pretty much any shared component for that matter). While local styles may seem expedient for that particular page or feature, they only introduce inconsistencies and make the site more difficult to maintain in the long term. So what’s a web dev to do?
We’ve developed an approach to help deal with this problem. I accept that there are probably smarter ways to achieve this – if you have a better idea, please let me know. Here goes:
- All styles should be relative to some top-level class. For example:
form.standard { margin: 0px 150px; }
The class describes the type of component that is being styled – the type of table, form, or component we are designing here. In the example, we are defining a “standard” form, which is a particular type of form. Using a top-level class allows for other types of forms to be styled as required.
- All sub-elements are defined relative to the top-level class:
form.standard label { margin-left: -150px; }
All labels for a “standard” form have a negative left margin. It is acceptable (even preferable) to style bare tags as long as the style is relative to a top-level class. This keeps the markup simple and consistent. We only add CSS classes to child elements as required.
- The corollary to the above is that it is not acceptable to style bare tags (unless we are doing a style reset). The problem with styling bare tags directly is that it locks us in to one specific style and that makes it difficult to evolve the design of the site. For example:
label { font-weight: bold }
means that all form labels will be in bold. If we introduce a form later that shouldn’t have bold labels, we will have to explicitly override this style (which tends to be brittle and limiting).
- Now, if we need to evolve an existing style, we have a few options. Say we want to replace the “standard” form with a new design. Rather than change the styles for the “standard” form class directly, which would immediately impact every page where this style is used, we can incrementally and selectively rollout the new design on a page-by-page basis. If the new style is significantly different from the initial style, we can fork the original style by defining a new top-level class:
form.danger { background-color: red; }
Simply by changing the class attribute for each form element from “standard” to “danger”, we can then roll out the new style to select pages testing the design in all supported browsers as we go. Think of this being like continuous integration for site design.
- If the style changes are relatively minimal, we can override the style for specific elements. One way to achieve this is to use multiple top-level CSS classes. For example, we could incrementally apply both the “standard” and the “danger” classes to the form elements on each page testing as we go. The “danger” class could override styles as required – though dealing with precedence can be tricky. Alternately, the new class could be defined relative to a top-level identifier. This solves the precedence problem as styles defined relative to an identifier take precedence over styles that are relative to a class. Another option is to define specific classes for the child elements to be restyled – but this means changing a lot more markup during the rollout.
That’s it. It seems pretty simple – intuitive even. But I haven’t found many references to how others tackle this problem. Again, if you have a better idea, please let me know.
David Hirtle
August 31st, 2009 at 11:32 pm
The key to maintainability seems to be avoiding dependencies between styles because at some point someone (possibly you, weeks later) will come along and change a depended-upon style.
But to avoid dependencies, you end up duplicating styles, which also creates maintainability problems. For example, when forking a class (as described in #4), you’ll probably end up duplicating a lot of styles. On the other hand, overriding styles (as in #5) would create dependencies.
Here’s an interesting discussion about Redundancy vs. Dependency:
http://www.mezzoblue.com/archives/2005/01/20/redundancy_v/
I think the described approach will work if everyone adheres to it. I wonder if there will be problems with people using different top-level classes, though. For example, what if someone wraps form.standard in div.container and starts using that as the top-level element?