Note: This article is esoteric-could-be-should-be wishing for future browsers. If you only like to hear about what you can use right now, you won’t like this. You’ve been warned. 😉
At first, when the HTML5 working group added the scope attribute I was skeptical. I thought, “oh dear, this is going to be another way for developers to cause massive duplication and inconsistency.” I still do worry, but I’m more excited about the tool and hoping we can also find really great things to do with it.
Scope is great for mashups
The first that comes to mind, and the reason it was created, is mashups. Imagine you want to pull in a twitter feed or a video into your page. Unless you intend to rewrite it all (and often widgets block such fine grained control), you’ll probably be allowing someone else’s CSS onto your page. That can be daunting to say the least.
For example, the twitter widget I use pulls in ~1.36kb of CSS that could potentially interfere with my site styles — and it does it via JavaScript, so unless I’m ready to switch to using the API rather than a widget, I’m stuck allowing their styles on my page.
The way to sandbox those styles today is to be sure each style starts with a twitter specific class name. You can see from their code that they did a very good job of it. None of their styles will be polluting mine. Unfortunately, not all widget CSS will be so enlightened (especially not ad code). HTML5 offers another option for allowing authors to sandbox styles called the scope attribute. David Storey has a much better explaination of scope on his blog, I’ve adapted this example from his:
<div> <style scoped> p { color: blue; } </style> <p>The text in this paragraph will be blue</p> <p>And in this paragraph too</p> </div> <p>This paragraph is out of scope so will be gray</p>
If your browser supports it, it will look like this:
The text in this paragraph will be blue
And in this paragraph too
This paragraph is out of scope so will be gray
There are a lot of really great use cases for this, widgets, ads, even large companies with multiple teams putting functionality on a single page. On the other hand, there is a use case for which it falls flat: components. I’d love to see it extended so it could work for both.
Scope is more than a starting point, it’s a donut
Donuts by mamaloco
The interesting thing about UI components (different than ads or widgets) is that they can sometimes contain other unrelated components. You can have a tabset that contains headings, paragraphs, media blocks, or even (holy ugly!) another tabset. Ideally, you would scope the CSS of the tabset to the tabs only, so it can’t bleed outward and change styles on the rest of the page, but you also want to prevent the styles from bleeding inward to content components. So we need a way of saying, not only where scope starts, but where it ends. Thus, the scope donut. The styles affect the donut shape, but not the other components which can be found in the donut hole.
Figure 1: Tabs from Let’s Freckle, a time tracking software.
It has always struck me as odd that scope is declared in the HTML, because ideally with reusable components you wouldn’t want to repeat that code all over the place. Each of the tabs has different content related to my Freckle settings. The content styles aren’t specific to the tabs, but may also be used in other parts of the page. We want to make that reuse possible, by keeping our tabs from affecting the way content (the components in the donut hole) components look. I’d like to do this once, in the CSS, and then have it apply anywhere the component is used.
The first step is to be able to define that component, including both it’s HTML and CSS. Let’s start with a basic box. It’s HTML might look something like this:
<div class="box tabs"> <div class="hd"> Box Header </div> <div class="bd"> Box Body </div> </div>
The scope of a box begins at the wrapper div with the class box
, and ends at the box body. We don’t for example want the styles on the box spilling over into whatever content we put in the box. I’m not sure how exactly to say this in CSS, but I like the idea that using nested selectors implies meaning about the relationship of those parts. e.g. nesting the selectors means that the sub nodes belong to the same component.
In other words, if I applied a text color to my box, it would change the color of text in the header, but not to subnodes in the body. Let’s take a look at an example syntax. I don’t really know what the right syntax is, but I’m more and more convinced it is worth finding a syntax to express it.
.box { border: 1px solid gray; color: red; // would cause text in the header to be red, but not inside the body. scope: start; & .hd { border-bottom: 1px solid gray; } & .bd { scope: end; } }
This basically says that hd
and bd
belong to the box object and that the scope of these styles starts at .box
and ends at .bd
. It prevents styles from bleeding either up or down. Note: I do wonder if we need to say where it starts given that the nature of CSS is that it is namespaced to wherever the selector begins. It also makes me wonder why in the world scope is in the HTML rather than the CSS. It seems oddly out of place. Moving on…
There are several ways to mark up tabs, but let’s assume the HTML of the tabs component looks like this:
<div class="box tabs"> <div class="hd"> <ul class="tabControl"> <li><a href="#">Personal</a></li> <li><a href="#">Date & Time</a></li> <li class="current"><a href="#">Password</a></li> <li><a href="#">API</a></li> <li><a href="#">Timer</a></li> <li><a href="#">Rounding</a></li> </ul> </div> <div class="bd"> <ul> <li class="tab-bd">Tab 1 Content</li> <li class="tab-bd">Tab 2 Content</li> <li class="tab-bd current">Tab 3 Content</li> <li class="tab-bd">Tab 4 Content</li> <li class="tab-bd">Tab 5 Content</li> <li class="tab-bd">Tab 6 Content</li> </ul> </div> </div>
What you can see is that tabs are an extension of an ordinary box. They have a head and body wrapped in a div with the classes box
and tabs
. What makes the tabs unique is that both the head and body are filled with unordered lists which correspond to the tab control and tab body respectively.
Figure 2: Tabs with the donut highlighted. We don’t want any styles falling either into the donut hole. This where scope ends. (See? It’s donut colored! Forgive me, this diagram is very very ugly.)
We could try to express the next bit by combining three tools:
- nesting for defining the component structure,
- extends so we don’t need to repeat anything we already know about boxes in the code for tabs, and
- a new property “scope” which tells the browser where to stop allowing styles to bleed down.
.tab { extends: .box; // so inherits the starting scope & .tab-bd { scope: end; } }
So, any styles for tabs should only apply to the nodes between the start and end of scope, or between .box
and .tab-bd
. Is this the right syntax? I’m not sure actually, but I tend to like it.
Figure 3: the region we want styles to apply to.
Figure 3 sums up what I’m asking for — a way to apply styles only to the divs which make up my object and not to content that simply happens to be inside it. You can do that today with careful use of style and the child selector “>”, but in a world where some content is trusted more than other content, it might be easier to just be able to say explicitly which donut of elements I want a particular set of styles to apply to.
You may have heard the Henry Ford quote:
If I’d asked customers what they wanted, they would have said “a faster horse”.
I think lots of us want *way* more than just a faster horse. 🙂
What do you think? Useful? I’ve been holding on to this post for four months because I wasn’t sure about it, because it is quite different from how things work now, but I thought I’d put it out there and see what other people think.
Comments
7 responses to “Scope donuts”
I’m not sure about specifying a start and end point for a scope — it seems like it could be a CSS debugging nightmare. What might be better is to simple define a container and the domain of the styling scope.
There could be the domain of the container itself (scope: container), only its children (scope: children) or both container & children (scope: all)
What about a ‘block external styles from touching this node’ instead of trying to wrap it in CSS? Something like, ‘inherit: none’ to block CSS inheritance.
.tab {
inherit: none; // blocks all styles DOM nodes above this widget from affecting the ‘donut’
extends: .box; // explicitly reuses rules of the ‘box’ class
& .tab-bd {
inherit: none; // blocks all styles from the ‘donut’ DOM nodes from bleeding into the ‘hole’
}
}
It’s actually kind of an opposite of the ‘inherit’ value; instead of inheriting values normally not inherited, it stops the inheritance and leaves you clean inside that element. Combining that with your ‘extends’ would allow you to ‘reset’ inside the component and then add what that component needs. Just a thought…
@Tim; That’s how I understand Nicole’s example as well, with one exception; “inherit: none” seems like it would cancel all inherited styles, not just the style of the .tab. It would make the ‘hole’ of the donut plain white, instead of showing the background, so that’s much less re-use friendly. So you need to establish the extent of the scope, in some way.
Syntax-wise, I’d prefer something like:
.tab-bd { ends-scope: .tab }
To me, that seems more css-like, and it would make it more explicit which selector scope is being closed.
@michiel – yes, inherit none would do something like adding an iframe I suppose. That might be interesting too.
I’ve wanted something like this for a long time.
In particular, I want to be able to “stop the cascade” at a given point. Like hold an umbrella up and shield an element and all its children. The “inherit: none;” idea + a mixin of base classes feels like a good way to do that.
Recently we’ve tried to do the “donut” approach at 37signals by using immediate child selectors to restrict how far down a style can trickle.
For example: div.tabs > div.hd > ul { … } limits the styles to immediate children of the container.
@Ryan Singer – the child selector is a great way to control scope if you don’t need to support IE6 and you are working with trusted code. I’ve used it for clients who are down with some visual differences in IE. The child selector is part of why I wondered if scope donuts are necessary… or even the scope attribute. I mean, CSS is all about scope, right? I’m still on the fence.