Ask a UI Guy: How should I structure my stylesheets?

Welcome to "Ask a UI Guy," an occasional new feature in which we tackle JavaScript, markup and CSS questions for an audience of server-side developers. Today's topic: strategies for organizing your style rules into reusable components.

CSS doesn't impose much structure on its practitioners. Individual developers must build their own structure if they wish to escape the trap of poorly organized, inefficient code.

If a project kicks off without a clear shape to its stylesheets, subsequent developers are likely to plop additional styles down wherever is convenient. It's hard to build reusable components if every style is a one-off tied to an element id. Stylesheets with a strong skeleton and frequent signposts encourage conscientious code that's easier to maintain.

And let's face it: CSS tools hasn't come as far as JavaScript ones. Sure, Firebug can help you track down why element X looks the way it does. But without a clear picture of a project's overall CSS architecture, it's hard to alter that element's style properties with confidence.

Every stylesheet should tell a story

In my projects, I use a few simple tricks to encourage what I call narrative CSS. My premise is that every stylesheet should tell a story. In other words, developers should understand the structure of an application's markup just by looking at the CSS - and understand exactly where a specific declaration can and will be used without having to grep through the entire codebase.

This tenet is based on my observations of how people make changes to existing projects: They look at the few lines of markup affected by their change, then they find the style rules that affect that markup. If the context of those style rules isn't immediately apparent, then it's difficult to make changes without introducing bugs or creating inefficiencies.

To accomplish this, I use the following guidelines:

  • Start with a good reset.css. I recommend the Eric Meyer version.
  • Use signpost-style comments to create a superstruture in the stylesheet.
  • Use normal comments to create a substructure.
  • Order those signposts and comments - and the rules that appear between them - from general to specific; elements to classes to ids; baselines to overrides:
  • Create id-based style rules only for page-level containers: headers, footers, sidebars, main content areas. Use element- or class-level rules for everything else.
  • When creating reusable utility classes that could go anywhere on the page, scope them only to a class name unique enough to prevent common namespace collision.
  • When creating components that belong in a specific page-level container, scope them to the id of that container.

All of that theory sounds, well, theoretical. How does it play out? Let's look at the skeleton of a typical narrative stylesheet:

  1. Basic HTML elements

    Your first section should include baseline style rules for all common HTML elements. I sometimes mix in a few class overrides on those element-level rules so I can get fine-grained control on, for instance, input elements, which vary wildly depending on their type attribute.

    /************************************************************
    	base element declarations
    ************************************************************/
    
    body{
    	background: url(/sites/pfd/img/chrome/bg.png) top left repeat-x #000;
    	color: #fff;
    	font-family: Verdana, Arial, Helvetica, sans-serif;
    	font-size: 12px;
    	line-height: 1.5em;
    }
    
    h1,h2,h3,h4,h5,h6 {
    	font-family: Helvetica, sans-serif;
    	font-weight: bold;
    	line-height: 1.4em;
    }
    h1 {
    	font-size: 20px;
    }
    h2 {
    	font-size: 16px;
    }
    h3,h4,h5,h6{
    	font-size: 14px;
    }
    
    /* links */
    
    a {
    	color: #08c;
    	text-decoration: none;
    }
    a:hover {
    	color: #444;
    	text-decoration: underline;
    	cursor: pointer;
    }
    
    
    abbr,
    acronym {
    	text-decoration: underline;
    }
    abbr:hover,
    acronym:hover {
    	cursor: help;
    }
    
    /* standard text tags */
    
    b,
    strong {
    	font-weight: bold;
    }
    i,
    em {
    	font-style: italic;
    }
    
    /* forms */
    
    input,
    textarea,
    select,
    .button {
    
    	border: 1px solid #999;
    }
    /*
    	you must give classes to input elements
    	to work around limited CSS 2.1/3 support
    	in some browsers
    */
    input.radio {
    
    }
    input.checkbox {
    
    }
    
    label {
    	display: block;
    	font-weight: bold;
    }
    .button {
    	background-color: #ddd;
    	font-weight: bold;
    }
    .button:hover {
    	cursor: pointer;
    }
    
  2. Reusable utility classes

    Your next section should include class-based rules for reusable components that aren't tied to a specific area of the page: pipe-delimited lists, tabs, menus, definition lists, etc.

    /************************************************************
    	reusable utility classes
    ************************************************************/
    
    /* pipe-delimited list */
    
    ul.pipeDelimitedList {
    
    }
    ul.pipeDelimitedList li {
    
    }
    ul.pipeDelimitedList li.first-child,
    ul.pipeDelimitedList li:first-child {
    
    }
    
    /* tab navigation */
    
    ul.tabGroup {
    
    }
    ul.tabGroup li.tab {
    	display: none;
    }
    ul.tabGroup li.tab.activeTab {
    	display: block;
    }
    ul.tabGroup li.tab h1 {
    
    }
    
  3. Page layout based on the ids of major page-level containers

    When building the superstructure of your page, use ids and scope them within each other to make the markup patterns explicit. Overkill? Maybe. But the goal here is CSS that reads like a blueprint.

    /************************************************************
    	page layout
    ************************************************************/
    
    #container {
    
    }
    #container #header {
    
    }
    #container #main {
    
    }
    #container #main #content {
    
    }
    #container #main #sidebar {
    
    }
    #container #footer {
    
    }
    
  4. Module layout, one page-level container at a time

    Now we get to the heart of the stylesheet: The specific components that can appear within a given area of the page. The navigation in your header? The search box in your side navigation? The shopping cart interface? Think of each one as a reusable component that exists within the context of a page-level container.

    /************************************************************
    	section: header
    ************************************************************/
    
    /* global navigation */
    
    #header .navigation {
    
    }
    #header .navigation ul {
    
    }
    #header .navigation .logo {
    
    }
    #header .navigation .logo a {
    
    }
    
    /* boilerplate */
    
    #header .copyright {
    
    }
    #header .byline {
    
    }
    
    /* shopping cart */
    
    #header .shoppingCart {
    
    }
    #header .shoppingCart ul {
    
    }
    #header .shoppingCart ul li {
    
    }
    #header .shoppingCart ul li.total {
    
    }
    
    /************************************************************
    	section: main content
    ************************************************************/
    
    /* module: article */
    
    #content .article {
    
    }
    #content .article h1 {
    
    }
    #content .article p {
    
    }
    
    /* module: product benefits */
    
    #content ul.benefits {
    
    }
    #content ul.benefit li {
    
    }
    #content ul.benefit li a {
    
    }
    
    /************************************************************
    	section: sidebar
    ************************************************************/
    
    #sidebar .section {
    
    }
    #sidebar .section dl {
    
    }
    #sidebar .section dl dt {
    
    }
    #sidebar .section dl dd {
    
    }
    
    #footer {
    
    }
    #footer .copyright {
    
    }
    #footer a {
    
    }
    
  5. Specialty code tied to float-clearing, JavaScript behaviors, or image scripts such as IEPNGFix

    Finally, deal with style rules likely to contain a grocery list of selectors, such as containers that need their floats cleared or images that need to have their alpha transparency set via script in IE. Since you'll add selectors to these grocery lists over time, it's convenient to have easy access without searching the entire file.

    /************************************************************
    	utilities: float-clearing, png-fixing, javascript, etc.
    ************************************************************/
    
    /* float-clearing */
    
    /* IE6 */
    
    * html #head,
    * html #content,
    * html #content .article,
    * html #rail,
    * html #nav{
    	height: 1%;
    	overflow: visible;
    
    }
    
    /* IE7 */
    
    *+html #head,
    *+html #content,
    *+html #content .article,
    *+html #rail,
    *+html #nav{
    	min-height: 1%;
    }
    
    /* good doggies*/
    
    #head:after,
    #content:after,
    #content .article:after,
    #rail:after,
    #nav:after{
    	clear: both;
    	content: ".";
    	display: block;
    	height: 0;
    	visibility: hidden;
    }
    
    /* IE6 hacks for image transparency */
    
    img,
    #content #rail .railBlock .header,
    #content #rail .railBlock ul.icons li {
    	behavior: url(/css/iepngfix.htc);
    }
    
    /* styles used exclusively by our jQuery plugins */
    
    .curvyCorners {
    
    }
    .someAjaxPlugin {
    
    }
    

Does this make sense? Have a better solution to CSS organization? Let us know in the comments.