« Previous Main Next »

CSS for Widgets: friends don't break friends' styles

Profile picture for Mark Stickley Mark Stickley

Post categories: ,

14:15 UK time, Monday, 1 February 2010

Hello. My name's Mark (as it says up at the top of the post) and I'm on the team developing BBC iD, the new membership system that's being gradually rolled out across the BBC site.

One of the important features of BBC iD is the status bar, which sits in the top right of every page. The idea is that if you click the sign in link, or a relevant link anywhere else, we bring up a JavaScript overlay which allows you to sign in without leaving the page. It's designed to be a seamless experience, and we think it comes pretty close.

While building the HTML, CSS and JS for the project, a key part of my job has been to ensure that our code doesn't break any of the pages into which it's included. What's more, I have to be confident that the CSS defined for the page doesn't break any of the BBC iD components. Actually this is pretty tricky, but I'll explain how I approach this problem.

A word on w*dgets

First, I have to apologise to my colleagues and anyone else who puts the term 'widget' on the same level as a filthy curse word. When I talk about 'widgets' I'm just trying to find a short way of saying something like "chunks of HTML of any size supported by CSS (and possibly JavaScript) strategically inserted into a web page, the source of which you may not necessarily influence or control". It doesn't exactly trip off the tongue which is why I'm tempted to use the 'W' word, despite it's ambiguity.

The term 'widget' - and therefore this post - has a massive scope which covers much more than the BBC iD status bar and overlay. It will affect most developers, if not now certainly in the near future.

Before I continue, however, I need to cover some of the basics of CSS. For anyone who is already au fait with things like specificity, feel free to skip this section, if you want to.

Inheritance & Specificity

CSS selectors are based on inheritance and specificity. Let me just take a moment to explain that for those who don't know already, because knowing this has saved me countless hours of headaches and frustration.

Inheritance

Some styles are inheritable. When you give an element an inheritable style, the children of that element will have the same style unless another selector says otherwise. Examples are: font-family, color, font-style, font-size.

Other styles like border, background and width are not inherited.

Specificity

This is a concept that is less well-known, or at least less well-understood.

Specificity is calculated on a per-selector points system. The higher the number of points accrued, the more important the styles within that selector are considered.

A simple tag is worth 1 point:

a{}
p{}
label{}

A class comes in at 10 points:

.post{}
.error{}
.section{}

Mix them up for higher scoring combinations:

a.external span{}

That one is worth 12 (1 + 10 + 1) points.

ids are worth a massive 100 points.

Other things worth noting are:

  • Pseudo-classes (E.g. :hover) are worth 10 points.
  • Pseudo-elements (E.g. :first-line) are worth just one point each.
  • The * selector is worth nothing at all.
  • If a selector has more ids than another, it will always win no matter how many classes and elements the other has.
  • If a selector has more classes than another, it will always win no matter how many elements the other has (assuming neither has any ids).
  • Selectors with an equal number of points will use the order they are defined to decide which is more important. The most recently defined (further down the CSS file) is the most important.
  • Inline styles using the style="" attribute do not hold a points value - they simply take precedence over any conflicting rules.

Overriding styles

Just because one selector is deemed more important than another, it doesn't mean that the styles defined in the less important selector are void. Only the individual styles that the more important selector specifically defines are overridden, if they have already been defined by a less important selector.

Think of it like this: all selectors are used and applied in the order lowest scoring to highest scoring, identical or similar styles overriding what's already there.

I have adapted this information from multiple sources. It's the way I find it easiest to think about, but if you feel you are still a bit in the dark have a look at some other people's explanations:

Be considerate, but don't expect it back

This specificity model is what we have to work with, whether we like it or not.

Any CSS file loaded by a document could affect any part of that document; conversely, any part of the document can be affected by any CSS file it includes. When you're working in an environment where your HTML and CSS share a space with someone else's you have to be considerate and defensive in equal parts.

Let's assume that the people writing CSS for any one page fall into two basic categories: Page author and Inclusion author. (We could call them 'Widget authors', but I've spent enough in the swear-box already.)

Being considerate

Coding considerately means doing your absolute best to make sure your styles don't mess about with parts of the page that you have no business messing about with. Let's start with includes.

The considerate Inclusion developer

It's so easy to get this right, there's really no excuse not to.

Because the inclusion is only a small part of the page, it's fair to say that it will have it's own container element, probably a div. You aren't interested in styling anything on the page except what is directly inside that container, so make that clear in your CSS.

Wrong:

h4{}
p{}
.entry span{}

Right:

#inclusionname h4{}
#inclusionname p{}
#inclusionname .entry span{}

By namespacing your CSS in this way, you are ensuring that your styles will not interfere with the styles on the page, as you might imagine those selectors in the 'wrong' example could.

It's worth mentioning that if your inclusion is called something that might well be used as an id elsewhere on the page, you should probably go a step further and use a more specific id to define the namespace. Here are some ideas:

#inclusionname-inclusion h4{}
#inclusionname-pageinclude p{}
#inclusionname-originatingurl-co-uk .entry span{}

The considerate page developer

It's a little harder as a page developer to avoid writing styles that will affect inclusions in your page. By the nature of CSS, it will cascade styles to elements further down the tree making namespacing harder. This doesn't mean that there's nothing you can do, however. Far from it, in fact.

Here are a few guidelines that will help avoid breaking inclusions:

Don't put styles on bare tags

By 'bare tags' I mean selectors like this:

p{}
li{}
span{}

It might seem reasonable to place a line-height of 8em on all the lis in your page, but it's probably not what any inclusion developer will be expecting.

The more specific you can be in your selectors, the less likely they will be to leak into areas where they shouldn't be. A lot of developers give their pages a container div directly inside the body. At the very least you can namespace to that:

.container p{}

This might not seem too useful at first, because everything on the page is within the container. But if you use Glow (other JavaScript frameworks are available) to add an overlay, this is generated and placed at the very end of the body element. Styles namespaced to the container will not affect the overlay. So simple, so effective.

Going one better: if your style is only used in the header, footer or the main content area, specify it that way.

.header p{}
.main li{}
.footer span{}

Not only does it prevent unexpected style leakage (whether you have any inclusions in your page or not) but it makes the file easier to maintain, as you can tell at a glance that changing a particular declaration will only affect that specific part of the page.

There are limits to the depths of namespacing you can go to before you end up having to repeat your styles (defeating the point of the 'cascading' part of cascading style sheets). Just be as specific as you can be within reason. You'll know you've done your best, and as a result you'll get a nice warm fuzzy feeling inside.

Being defensive

Of course, you can't presume that all other developers value this nice warm fuzzy feeling. They won't necessarily have done their bit to protect your interests, especially as an inclusion developer, since namespacing pages can only work so far.

This is where defensive styling comes in. This is a bit of an exercise for the ol' grey matter, so grab a nice hot cup of tea, rich in Brownian Motion - the ultimate thinking aid - before you continue.

The defensive inclusion developer

The bad news here is that any styles defined for the main page with a higher specificity than your styles will interfere with your inclusion. And you have no way of telling how specific the page styles are, because this will vary from page to page.

Also, because of the cascading nature of CSS, you can have the most specific styles ever but unless you set every possible style on each selector then there is still a chance that some undesirable rogue styles that you didn't override might creep in.

This leaves us with two options:

  1. CSS Resetters
  2. Applying the styles directly in a style attribute

Despite being the only sure-fire way of getting the styles you want, applying every possible style to each element within a style attribute goes against the principle of separated style and content; it's nasty, messy, hard to maintain and generally not clever. So essentially we only have one option.

The thing about CSS resetters is that they're designed for the whole page; they're there to provide a common base line across all browsers, overriding the browsers' default styles and setting sensible defaults - something that Mat went into in some detail last year. Because of this, they are all extremely overridable, usually basing their styles on a single element selector.

If we were to apply a reset to an inclusion, we would want it to be overridable so we can style it properly. We also need to namespace our resets so they don't affect the rest of the page. This requires at least a class to base the reset upon.

.reset{}

Of course, any style from the page that contains an id will obliterate any reset styles which leads to the logical conclusion of reset ids.

#inclusionname-reset{}

This has the limitation of only being able to be used once, as ids are unique. Also, any page style that uses more than one id will override that.

I could go on, but suffice it to say the more specific you make your reset the less likely it is that the page styles will affect your inclusion. But in turn, however specific your reset styles are, the styles for your inclusion will have to be even more specific.

You need to pick a level that suits you and the needs of your inclusion. You can never be 100% sure that your styles are safe, but if you take these precautions you stand a far better chance.

Barlesque, the inclusion that provides the header, footer and various pan-site modules on the BBC website comes with a re-useable class-based resetter (.blq-rst) which we apply to the BBC iD inclusions; I feel this is an appropriate level of protection.

The defensive page author

As a page author, if you come across an inclusion that breaks styles in your page I would simply recommend not using it. If the author of that inclusion has not thought carefully enough to namespace their styles, I wouldn't want their HTML or JavaScript on my page either.

If you have no choice in the matter, you need to get in touch with the author and point them in the direction of this post!

Food for thought

I haven't come across anything so far, but it might be possible to solve the Defensive inclusion author problem with some JavaScript. All it would need to do is to read in the styles from a specific stylesheet, parse them, and apply the computed styles to each element in a style attribute. Maybe that's a subject for a future blog post...

Comments

 

BBC iD

Sign in

BBC navigation

BBC © 2014 The BBC is not responsible for the content of external sites. Read more.

This page is best viewed in an up-to-date web browser with style sheets (CSS) enabled. While you will be able to view the content of this page in your current browser, you will not be able to get the full visual experience. Please consider upgrading your browser software or enabling style sheets (CSS) if you are able to do so.