CSS in the Large
CSS is not a programming language. Its syntax was intended to be easy to grasp for designers – people who think in a visual rather than procedural way. It might be discussed if CSS really fits designers. What I’m sure is that it doesn’t fit programmers, at least the ones I know. Some of them dislike it as a heap of peculiar workarounds for browsers’ limitations. Others point out that CSS lacks a lot of programming constructs, such as variables or expression evaluation (IE’s proprietary extensions, while powerful, look a lot like hacks).
The first argument is particularly valid. Writing stylesheets often requires magical powers and is not straightforward at all. But the problem lies at broken implementations (sometimes referred to as “browsers”) rather than in CSS itself. The second shortcoming is clear when it comes to managing large websites. During the last few months I’ve been working on a custom intranet content management system as a frontend developer. Let me share my experiences from this project. I’m really eager to hear comments from you, especially if you have your own experiences with large scale projects using CSS extensively.
The single installation of our CMS in theory can handle even few thousands of websites arranged into groups. Typical group contains from one to over a dozen of websites. Each group is a separate entity, owned by a customer who’s also responsible for the content. While websites and groups are managed by different people, they all share similar layout and main functions. It assures consistency across the intranet. Websites within groups are even more alike to each other, as they usually serve the same purpose.
As you can see, the above setup is very similar to a typical portal or large e-commerce site. There are independent sections updated by different people but sharing some common branding elements and structure.
During system design phase, I aimed to:
- Preserve consistency across all the sites.
- Allow for design flexibility.
- Maximize code reuse and avoid copy and paste programming.
To create a custom appearance for a group you’d need to put new files at lower level. The set of available files has been defined at the beginning and slightly updated when needed. For example the home icon is always /images/icons/home.gif. If you want to use a completely different icon for the group, just put this new image in the corresponding location on the group level. The same rule applies when you want to introduce further customization on the website level: you just need to place the file in the proper location at the bottom of the directory tree.
How does the CMS know which one to choose? It’s looking for the files from the bottom up. If the image, script or template is found on the website level, then it will be used. Otherwise the application looks for the same file at the group level or, if it’s not here, at the root level. CSS stylesheets are handled a bit different. The ones from lower level do not replace the files above, but instead are appended at the end of their “ancestors”. The server-side mechanism merges all the stylesheets with the same name and sends the result to the browser as a single file. This exception was made for a reason. CSS rules rarely have to be created from scratch — it’s much easier to define common setup and then to extend it by overwriting individual rules or adding new.
How this approach works in practice? Surprisingly well. Since all the files needed for laying out a new site are already at the root level, content managers can quickly create a running site and edit content. They don’t have to wait until designers will finish their work and provide finished design specification. As all websites share the same codebase they are all consistent. It’s also very easy to introduce global changes. All you need is to put the code at the top CSS file, and all the websites will inherit it. Finally, there is no need to copy the code. You just have to write things specific to the group or website.
But there is always “but”
All the good things come at some cost. The biggest difficulty with the hierarchy is to control the differences between portals. It’s easy when they have the same grid and typography, and the only differences are colors or images. Things start to get complicated when each group or even website introduce a new layout. Multiply the number of CSS files by 50, and you will get the picture. Updates that require overwriting a single rule are easy to implement, but layout changes quickly become hard to manage. In order the make it work the designer have to keep the discipline. And have you ever tried to “discipline” your client’s requirements?
Another issue is related to features that are used across few groups but not everywhere. Where should they belong? If you create a copy in each group this feature is needed, you’ll quickly face the problem of managing these different copies. Putting them at the root level sounds better, but in that case all other sites will use bloated CSS stylesheets.
How to overcome these problems? The latter could be addressed by further modularization of CSS files. Currently I’ve split stylesheets into few files. colors.css, grid.css, forms.css handle most distinct areas of a design, while main.css is used for typography, background images and small details. Distinct areas – such as navigation, header, login form – could have their own stylesheets. These stylesheets should be then included on portal level only when needed. The pros of this approach is the minimization of the code bloat — only actually used sets of rules would be included. To make it really work CSS files should be merged on the server. Otherwise dozens of HTTP requests would kill the performance.
Another idea that pops up is to provide few file hierarchies, each defining completely different visual structure. It would let the designer to have more flexibility. Moreover, a single installation of the CMS would be able to handle many diverse groups of independent websites.
I’m using a subjunctive form when writing about these improvements, as I haven’t tried them yet. What’s clear to me, after few months of learning by trial and error, is that there is no silver bullet. Each solution involves compromises and has liabilities. Managing the code of 50 websites can get tricky. Things get even more interesting when three frontend guys work on the same code simultaneously. But CSS, with all its quirks, glitches and flaws, simply works. Updates and fixes are quick. Usually I don’t even touch HTML templates: the rule “write once and forget” can be applied only with web standards approach.
Yet, I’m sure some things could be improved. What are your ideas? Do you have similar experiences? Please feel free to share!
But I am right that merger of CSS files on server doesn’t allow them to be cached in browser?
It’s completely transparent to the browser. Besides, since the CSS is parsed by the server-side application, we have a detailed control over HTTP headers and we can decide how the files will be cached.
Interesting article – however I’ve worked out a bit different workflow for my system, parts of which I describe below:
CSS: I have two css files in root it’s main.css which resets some styles (like puts all margins and paddings to 0, sets Verdana font etc).. I’m using this stylesheet as template when i start to code css.
I’ve got there as well default.css which I use when I don’t have styles on project yet but want to have structure look more readable than it is with just user agent’s stylesheet is applied.
After all I don’t share any parts stylesheets between projects as mainly in my case they’re very different. So I don’t have tens of different css files which are included or not.
However I keep some repetable styles in php classes. Actually there are 2 classes. first css class is mainly to get away with browser differences (I serve different stylesheets to IE and modern browsers) e.g. css::opacity(‘0.7’) normally will output opacity property but for IE will output filter property.
Second class is cssSmart which I use is for specific logic or blocks of css that I’m reusing, e.g. image replacement method, clear fix etc.
I remember that I’ve started with pure css files for include but after all I found this solution as limited as I wanted to have it working like functions – output content dependent on sent parameters – so with time I started using php classes for that.
All above is going only in development phase, for production site I produce static css and js files. I keep versioning them adding number to end of file name. So when I update file I’m sure that browser refreshes it as well.
Medyk: sounds interesting. I myself was rather skeptical about mixing CSS and server-side. Although stylesheets are parsed by Java servlet to resolve paths and allow the hierarchy to work, they contain CSS rules only, and no scripting at all. I decided to go that for the sake of simplicity.
It also means that 1k file will use two TCP packets instead of one (one for the file itself, the second for HTTP headers). In fact it has an impact on performance of the server (especially since CSS files are parsed), download time and bandwidth usage. I agree that these are completely unsignificant issues when we are talking about a weblog or small website. But it does count on high-traffic websites.
But when you have really many websites, few front-end developers (not to mention server-side guys) and hundreds of stylesheets, it quickly becomes a real problem, not only a philosophical issue. The code performance is one thing, but developers’ efficiency is at least as much important. Unmaintainable code means delayed schedules and angry managers :).
About that multitude of requests for css:
– if your css files are being already parsed by the serverside
code while not introducing a new pseudo-directive to the css
that will effectively ‘include’ another stylesheet into the current one?
The include command could also use your inheritance hierarchy ofcourse but the end result would be a single css file.
This would be a first usable idea from the ‘meta css language’ departament and i think it’s worth a try.
There is an @import directive in CSS. So the server-side parser could be enhanced to recognize this rule and merge files on the server, instead of sending another HTTP request.
i do not think it’s a smart idea since in this way we will loose the ability to do the standard import when we actualy want to do this.
how about @local-import ? or anything else?