Tab Completion

I'm Tab Atkins Jr, and I wear many hats. I work for Google on the Chrome browser as a Web Standards Hacker. I'm also a member of the CSS Working Group, and am either a member or contributor to several other working groups in the W3C. You can contact me here.
Listing of All Posts

Cascading Attribute Sheets

Last updated:

I recently participated in an internal thread at Google where it was proposed to move a (webkit-specific) feature from an attribute to a CSS property, because applying it via a property is much more convenient.

Similarly, some of the a11y folks have recently been talking about applying aria-* attributes via CSS, again, because it's just so much more convenient.

I think there are probably a lot of examples of this, where something definitely belongs in an attribute, but it would just be so nice to set it with something like CSS, where you declare it once and everything just works. For example, inline event handlers!

Given all this, I have a proposal for a method of doing that. It's very similar to CSS and gives most of the benefits, but should avoid some pitfalls that have sunk similar proposals in the past.

This idea was originally suggested to me by fantasai, and several others have come up with similar ideas in the past. I've simply worked out the details such that I think it should be palatable, and actually put the proposal forward to the WebApps working group.

The Proposal

Cascading Attribute Sheets (CAS) is a language using the same basic syntax of CSS, meant for easily applying attributes to an HTML page.

To use it, include a CAS file using <script type="text/cas">. (I chose <script> over <style>, even though it resembles CSS, because it acts more like a script - it's run once, no dynamic mutations to the file, etc.) CAS scripts are automatically async.

The basic grammar of CAS is identical to CSS. Your attribute sheet contains rules, composed of selectors, curly braces, and declarations, like so:

video {
  preload: metadata;
}
#content video {
  preload: auto;
}

In the place where CSS normally has a property name, CAS allows any attribute name. All of the properties have the same syntax:

<string> | <number> | <dimension> | <ident> | <hash> | <special-values>. If the value is a string, it's used as the attribute's value. Otherwise, the value is serialized using CSS serialization rules, and that is used as the attribute's value.

There are three special values you may use instead to set the value: !on sets an attribute to its name (shorthand for boolean attributes), !off removes an attribute (boolean or not), and !initial does nothing (it's used to cancel any changes that other, less specific, rules may be attempting to make, and is the initial value of all properties).

CAS files are not as dynamic as CSS. They do not respond to arbitrary document changes. (They can't, otherwise you have dependency cycles with an attribute selector rule removing the attribute, etc.) My thought right now is that your CAS is only applied to elements when they are inserted into the DOM (this also applies to any parser-created elements in the page). This allows us to keep information tracking to a bare minimum - we don't need to track what attributes came from CAS vs the markup or setAttribute() calls, we don't need to define precedence between CAS and other sources, and we don't need to do any of the fancy coalescing and whatnot that CSS changes require.

Semantically, a CAS file should be exactly equivalent to a <script> that does document.querySelectorAll(<selector>).forEach(<do attribute mutations>); (but with specificity determining which mutations are actually done), plus a mutation observer that reruns the mutations on any nodes added to the document.
It's just a much more convenient way to express this.

I think we should allow the CSS Conditional rules as well http://dev.w3.org/csswg/css3-conditional/ - at least Media Queries, but @document seems useful as well, and @supports may even be justifiable (it would need some definition work to make it usable for CAS, though). Again, these aren't responsive to live changes, like MQ are in CSS, but they let you respond to the initial document condition and apply attributes accordingly.

Feature Suggestions

I've already gotten a few suggestions for more advanced features.

Have a way to attach event listeners properly

In other words, create some special syntax that desugars to addEventListener instead of setAttribute.

My first idea for this is to add a special function that triggers this. If you're setting a property that is recognized by the host language as an event listener (in HTML, all the on* properties), you can instead pass a listen(<string>) function as the property value.

This desugars into passing the string through JS's Function constructor in the global scope, to transform it into a function object, then invoking addEventListener.

Have a way to add to the existing property

Simple way is to allow multiple values instead of just one, as currently specced, and then introduce CSS's attr() function. To create the attribute value, just serialize each value as already described, with a single space between them.

That way you could do class: attr(class) foo; to add the "foo" class to the element.

Feedback?

There's already a thread started on public-webapps@w3.org, so comment there if you want your feedback to be public.

I'll also accept feedback here in the post comments.

(a limited set of Markdown is supported)

While this is nice for authoring a little more terse HTML outright, most HTML is generated via templates in apps, so via Handlebars or Jade or soon the <template> tag. As such, we kill the repetition there.

Inline event handlers are bad practice, though this effectively translates to a first-class event delegation registry, which is an interesting thought. Still, registering handlers makes more sense in a JS file.

Already I'm hopping between templates, CSS, and Javascript to keep all my elements in sync via selectors. Adding in CAS into the mix raises the complexity some.

Reply?

(a limited set of Markdown is supported)

I like it. Thanks for suggesting it.

Regarding it being equivalent to finding and mutating elements using JavaScript, do you intend for CAS to be disabled when scripting is disabled?

It would only be useful as a replacement for tediously repeatedly specifying attributes in the HTML source if CAS were always enabled, even when JavaScript isn't.

That shouldn't be problematic: the only CAS rules that will trigger with JavaScript will be those that could've been specified directly as static attributes in the HTML source; anything dynamic would still need JavaScript to effect the dynamic situation.

Reply?

(a limited set of Markdown is supported)

Re #1:

I dispute that "most" HTML is generated via templates in apps. Quite a lot is just generated server-side, but even there there's a lot of places where you can't easily kill the redundancy.

Anyway, I think there's value in providing solutions for all the people that aren't using templating as well.

Yes, when used with events it's basically a built-in delegation mechanism. Browsers could even implement it as such, I think. They'd have to fix up the event.target, but they can do that, because it's magic. You can't detect where an event listener that you didn't register exists in the DOM (with normal DOM methods, at least).


Re #2:

No, CAS should run when scripting is disabled. As you say, it doesn't do anything dynamic; everything it does could be done by just amending your static markup. As such, there's no reason to ever turn it off.

Reply?

(a limited set of Markdown is supported)

Why can't one just use XHTML (HTML5 doesn't forbid xml well-formedness, right) and XSLT?

Reply?

(a limited set of Markdown is supported)

I'm skeptical about the utility of this proposal. To begin, I can honestly say I've never run into a situation where I wanted to be able to do this. On most web apps, as Paul said, the markup is created using templates that provide logical defaults. If I decide that I want the same attribute on some element multiple places, I would create some sort of template to do that.

This would also seem to break progressive enhancement quite horribly. Taking the attributes out of HTML and putting them into an external file means that vital information would be missed if that external file isn't available for some reason. In theory, the attributes exist on HTML elements because they are important I therefore document structure or semantics and therefore shouldn't be extracted into another location.

I definitely don't think that event handlers should be bound outside of JavaScript. Having any reference to JavaScript functions outside of JavaScript is a recipe for disaster and a tightly coupled mess that will only cause problems in the future.

So, I'm just not sure that there's a real problem being solved the solution. The discussion of whether or not something should be a CSS property or HTML attribute should be limited to, "is the data necessary to properly understand or render the element when CSS and JavaScript is turned off?" If the answer is yes, that it must be an attribute and it should be included directly in the HTML.

Reply?

(a limited set of Markdown is supported)

Looking at some of the responsive sites coming through, I'm seeing a lot of HTML attributes being manipulated via js with their values dependent on the viewport size.

I like Tab's proposal (but then I would as I suggested this http://andydavies.me/blog/2012/08/13/what-if-we-could-use-css-to-manipulate-html-attributes/) as it moves the setting of these attributes to a more declarative approach, making the code mor succinct, and separates the attributes and their values out of the js.

Reply?

(a limited set of Markdown is supported)

Re #5:

That's not what "progressive enhancement" means. :/ PE is about creating a simple design for down-level clients and upgrading it for better browsers.

You seem to be complaining about just moving information around, because an external script might load. I don't think this is something to worry about (you don't defensively code against one of your scripts not loading), but even if it was, CAS is perfectly fine to be used inline, just like inline scripts.

I don't understand your objection to attaching events. Obviously, attaching events with the CAS syntax should only be used for simple things (more complex things are probably hard to read), but otherwise using CAS is basically the same as an additional script attaching events (and then not hanging onto the functions, so you can't remove them yourself).

I suspect some of the objection here may be a mistaken idea that I'm trying to "recreate" a new scripting language. These are just strings of JS - either assigned to an on* attribute a la setAttribute(), or attached via addEventListener() and the Function constructor in JS. The events attached this way interact with the rest of the page's JS in the normal way.

Finally, don't think of this as "CSS versus HTML". It borrows the syntax of CSS for convenience and familiarity, but it has nothing to do with CSS otherwise. This is just a new, more convenient way to designate HTML attributes across a page, so that you can declare attributes on multiple elements with one declaration, rather than having to repeat the attribute on every element. It's semantically equivalent to just putting the attributes in manually, just like using a templating system (particularly when we add a native templating mechanism) instead of manually writing out every instance of every element.

Reply?

(a limited set of Markdown is supported)

Correct me if I'm wrong but the main interest of CAS is to manipulate HTML content via media queries. The other advantage of DRYing up HTML is already currently resolved using templates (client or server side).

If this is the case, it would be interesting to also consider adding/removing entire HTML tags (and their child nodes) from the DOM based on MQs. That would enable responsive designs to add/remove parts of the page based on MQs, and if the predictive parser would play along as well, avoid loading their related resources.

That can help responsive designs avoid delivering unnecessary content to mobile devices.

Reply?

(a limited set of Markdown is supported)

Re #8:

I don't think it's accurate to say that applying attributes under a MQ is the "main" purpose. Templating solves some of the vanilla purposes, when you have repetitive markup, but not all - for example, if you want to throw some ARIA at a bunch of tags scattered across your document, or to add rel="noreferer" to all the <a>s in your comments section, templating won't help you.

CAS won't help with removing sections of your document before the preload scanner gets to them, though. For sanity reasons, CAS is automatically an async script, and is applied at the same time as mutation observers. However, a combination of CSS and CAS can be useful - use CSS to show/hide the section based on MQ, and use CAS to add the resource-loading attributes based on MQ.

Hell, you can use just CAS for this - have the CAS apply a hidden: !on; attribute when the MQ doesn't match. The hidden attribute just applies display:none via the UA stylesheet.

Reply?

(a limited set of Markdown is supported)

Could CAS run synchronously when inlined? If so, a CAS script at the document head can be taken into account by the preload scanner. OTOH, it is true that adding/removing "src" attributes might be enough to download a resource or avoid downloading it, so there may not be a real need to add/remove DOM nodes for that purpose. The main advantage I see from removing unnecessary DOM nodes is smaller in-memory DOM tree on mobile.

Just thinking out loud here...

Reply?

(a limited set of Markdown is supported)

That's an interesting idea (applying inlined CAS "synchronously" for parser-created elements). It was just brought up as a problem by zcorpan from Opera, as well.

The only problem I can see with it is that if you put your CAS late in the document, it'll only be "synchronous" for the elements that follow it; preceding elements will get caught at the next microtask I guess, and might have already started loading things.

However, there's literally zero reason to put your CAS late in the document. Linked CAS is automatically async, which kills the JS "late placement as ghetto async" technique, and inline CAS doesn't need to wait for elements to be parsed to work properly, which kills the JS "late placement as ghetto DOMContentLoaded" technique.

Reply?

(a limited set of Markdown is supported)

Oh, and removing DOM nodes isn't a big deal. When it is a big deal (like an infinite-scroll list), you need something smarter than CAS can be. In normal circumstances, display:none is enough - it prevents the elements from generating any boxes, so they don't slow down layout. The elements' effect on the rest of DOM is negligible.

Reply?

(a limited set of Markdown is supported)

I like the idea, and can definitely see how it can "clean up" a page. Even without the querying aspect, there are many cases where the default browser behavior is not the right "default" for a specific page, and this can be a way to specify that.

However, I feel it can lead to some terrible performance issues. If you expect CAS to support attributes like async scripts, dictate which images to fetch or trigger onload events, you're creating some serious blockers for the speculative loaders.

It may be useful to require CAS to be inline. Here are some reasons to do so:

  1. It'll address most of the performance concerns (that I can think of), as well as interoperability concerns when pulling in CAS from 3rd party sites.
  2. It shouldn't get too big. It's meant to replace querying scripts, and there aren't a ton of those. Besides, if it got very big it can get extremely hard to understand how the browser will process a page when reading the code.
  3. It can be thought of as a sophisticated version of Meta tags, since it guides the browser on how to process the page, but in a more powerful way.
  4. It has a very broad effect on the page, both visually and functionally. As such, it's probably not going to be reused across pages any more than HTML snippets are shared across pages today.
Reply?

(a limited set of Markdown is supported)

I still try to wrap my head around this… My first concern was violation of Separation of Concerns as a development principle; then, whether we’re looking at a bigger problem in terms of how to centrally manage structure, or structural information (just as we do with presentation, via CSS).

In what stage are we here at all, is that just idle speculation right now?

Reply?

(a limited set of Markdown is supported)

Re #14:

I still don't get how "separation of concerns" comes into play. I must assume it's a kneejerk reaction to the "similar to CSS" grammar, causing people to assume that this is moving parts of your page's semantics to CSS.

It's really not! It's just an alternate syntax for specifying attributes on your elements, which provides some nice convenience features, like letting you apply attributes contingent on Media Queries or whatnot.

As for progress, I've put this as a proposal toward the WebApps WG, where I got some decent support but also some significant detractors. I suppose I'm waiting for a champion on the implementation side. (I'll probably be blocked from trying to do it myself in WebKit by Maciej's stern disapproval of the idea.)

Recent discussions with coworkers have pointed me toward addressing this in a slightly more general manner, by adding the ability to register scripting hooks against <style> and <script> elements with different type='' attributes (similar to just running a script after pageload, but somewhat more direct). That would let us experiment pretty widely with adding new things into the platform, and let off some steam - imagine embedding SASS directly in a <style type='text/sass'> element, and having a smallish JS library parse it into regular CSS and apply it, without the flash of unstyled content that you get today with LESS-js.

Reply?

(a limited set of Markdown is supported)

@tab

Do you really don want to make the loading of a page conditional on the 'CSS' e.g. SASS being parsed by JS the performance will be horrible - witness the waterfall for LESS

One question I do have is...

If I used the following at one viewport breakpoint

''' class: attr(class) foo '''

and

''' class: attr(class) bar '''

at another what happens when the viewport size changes from one to the other i.e. how do I get rid of foo or bar?

Reply?

(a limited set of Markdown is supported)

I fear many people will say there isn't really a use case/need for manipulating any and all attributes.

But I think people who practice the object oriented css approach to styling their sites and are aware of how very close to "classitis" it is will love CAS as a way to apply their OOCSS classes to their html elements without actually needing to edit the html.

I think enabling class: attr(class) foo; should be the primary focus of this proposal. It would enable the multiple style inheritance we love about OOCSS without the dozens of manually entered class attributes in the html.

It would be better if that was feature in css itself but I haven't heard about anything like that being proposed so CAS is the next best thing.

See:

http://www.vanseodesign.com/css/flawed-practices/#comment-310985

http://www.vanseodesign.com/css/flawed-practices/

http://thinsoldier.com/txt/comments/2012-09-25-classitis.txt

Reply?

(a limited set of Markdown is supported)