Using Encapsulation for Semantic Markup

Published
June 2, 2014
by
Chris Coyier

The following is a guest article by Chris Scott. Chris takes us through a great use case for the Shadow DOM. As designers, we may want to style something in a certain way, but sometimes end up having to go to war with HTML, CSS, and JS to get it done. And even then, the results can be weighty, hacky, and unsemantic. Shadow DOM might be able to save us from that, giving us a fresh place to use whatever HTML we need (need 20 empty elements? No problem!) without exposing that mess to the actual DOM (which would suck for accessibility, semantics, etc.)

File inputs are notoriously hard to style. Let's say you want to use an SVG icon instead of the default button-and-filename styling.

That is not a trivial styling change. Input elements are "no content" elements, i.e. an element that doesn't have a closing tag. Thus, there is nowhere to put an SVG element "inside" of it. So how might you go about this?

Well, let's take a progressive enhancement approach and start with the basic functionality:

<form>
<input type="file"></input>
</form>

What do we get out of the box? Semantically, it's very descriptive: there's an element that allows the user to input a file. It's also functional: if you click on it a file system dialogue opens.

Now let's think about adding the icon. You could put the image near the input, perhaps using z-indexes and transparent inputs to retain the functionality of the input (like this). This approach is okay semantically (you have an input and an image) but it definitely feels hacky and is more difficult to test. Alternatively, you could use an <img> tag and drop the <input> altogether, but then you have all the issues of replicating the file input's functionality and you have lost a bunch of meaning from the markup.

(There are other ways you could go about this as well. For instance a label-with-hidden-input, but for the sake of getting to the point here, let's move on.)

Shadow DOM

A better approach, in my opinion, is using Shadow DOM. Shadow DOM is one of the ingredients to Web Components, and you can read up on all that here. I just want to talk about its semantic benefits, for now.

Shadow DOM is described in the W3C draft spec as functional encapsulation or functional boundaries. That means we can encapsulate some design and pin it to an existing node on the DOM tree (called the Shadow Root). Returning to our example:

Take a look at the result in the Pen above. This may look like a long-winded way of changing some HTML but that's not actually what happened. If you were to "inspect element" in your browser you would still see the original file input - no sign of a div at all. More to the point, if you click on the string the browser will pop up a file system dialogue. This is the crux of encapsulation: from the outside the input is still an input but if you traverse the functional boundary then it's not an input at all, it's a string. That means that we can make an input (or any other tag, for that matter) render in any way want. We can set the inner HTML of the Shadow Root to be any valid HTML and it will render as such. Including, of course, adding SVG.

Clearly that isn't a particularly good looking alternative, but it's a start. And the HTML is simple and semantically correct. To take this further let's look at another tool in the HTML5 Web Components toolbox that's useful for this kind of thing: template tags.

Templates

Template tags are basically a way of building HTML that is not rendered. The cool thing about templates is that you can use JavaScript to inject the DOM from a template into an element elsewhere in the document. In this case, we can define a template for our fancy vector image file input and use that to define the Shadow DOM for actual input tags. Here's a suitable template:

Bringing it all together

The template element can be placed into the head of a document, and I think that's a nice place for it. The following Pen has the template we just defined in the head. If you open it on CodePen.io you can see the template by clicking the cog in the top left of the HTML panel.

Pretty good, I reckon. Semantically, it's exactly the same as the vanilla HTML but it is visually completely bespoke.

Browser support, polyfills and graceful degradation

There's some bad news (then more bad news and then a little, tiny glimmer of good news) in terms of support for Shadow DOM in browsers.

The first piece of bad news is that editing Shadow DOM is, currently, only supported in WebKit/Blink browsers, using the prefixed element.webkitCreateShadowRoot() method. Firefox 30 should support Shadow DOM as well, though.

Bad news number two: Polyfils may not support this use case - certainly Polymer doesn't. In the case of the Polymer platform, to understand why the polyfill doesn't behave as the native implementation does one needs to understand the mechanics of the polyfill. Essentially, the Polymer platform performs it's encapsulation by replacing native DOM nodes with wrappers. These wrappers emulate the behaviour of actual DOM nodes; for example, you can set the innerHTML of the wrapper as you would a native node. The wrappers maintain the so-called light DOM and shadow DOM. In terms of encapsulation, this works fairly well as the wrapper can intercept calls on the light side (such as the children getter) and hide the shadow DOM.

In this example, the retieved inner HTML of the "light" DOM show the encapsulation working (there is no h1 tag). However, the polyfill has not used actual Shadow DOM (otherwise it wouldn't be a polyfill!) and you can verify that if you inspect the first div from the results. In the DOM, you will see, there is an h1 tag - not hidden in the Shadow DOM, just there. That's because, underneath the wrapper, the Polymer platform is just manipulating the regular DOM tree as there is no shadow root.

In terms of the file input SVG icon that is not good. If one were to run the demo of the file input with the Polymer polyfill, as with the previous example, it would merely insert the img tag as a child of the input. If you recall, that is not allowed. An input is a void element and has no permitted child elements, so the browser will just ignore the img and style elements as invalid children. Here is a fork of the 'pen illustrating the issue; again, inspect the element and you can verify that the polyfill has added the template as children of the input.

And so, to the promised "tiny glimmer of good news"... Even though support is limited at the moment, I still think that it is justifiable to start using Shadow DOM to do things like this, now. Why? If your user's browser doesn't support Shadow DOM it will just render a normal file input. That's not a bad thing and considering the complex (read: hacky) alternatives I think Shadow DOM should be considered a plausible choice.

Notes on Shadow DOM, Web Components, etc

There are a huge number of use-cases for Shadow DOM and the related Web Components. This article has completely overlooked the advantages of encapsulation in terms of developing frameworks and widgets. For those interested it's worth reading the specs and getting to grips with Polymer.

In respect to “why” at the very top, is it not actually as a result of it being a replaced element – coming from the OS level? The fact that it’s a self-closing tag shouldn’t really play into it too much, right?

Is the closing tag something that’s born out of the requirement for it to contain content? Maybe that should be mentioned to avoid confusion towards this non-standard markup.

Otherwise, looks awesome, can’t wait for greater shadowDOM support in the future.

I can’t understand why if you put more than one input, looks like apart from the first one the other inputs won’t get the template applied correctly.
Am I doing something wrong? I would expect multiple file input buttons. Here is my pen: http://codepen.io/napy84/pen/jsyIf

This comment thread is closed. If you have important information to share, you can always contact me.

Treehouse is where you go to learn HTML, CSS, and how to build iOS apps. It's a complete education in modern web and app technology, designed to get you ready for a hot new job or to kickstart your own business.

The Lodge is a member login only area with access to video training on how to build websites from scratch using the best modern tools.

What now? I have some ideas for you.

Go explore CodePen!

As a front end designer and developer, you should have an account on CodePen so you can save your snippets, present your ideas, and engage with other front end folk. I'd encourage you to go PRO as well, to unlock the full power of CodePen.

Get the newsletter!

You should sign up for the CSS-Tricks newsletter. It's a clean copy of all the blog posts each week, combined together, right to your inbox. If email isn't your thing, there is an RSS feed, iTunes, and lots of other ways to subscribe.

Listen to ShopTalk!

Subscribe to The Lodge!

The Lodge is a members-only, ad-free video learning area here on CSS-Tricks. Just like the free screencasts, but organized into four large complete series. Membership is also the #1 best way to support CSS-Tricks.

We can do the real footer now.

Site Links

Colophon

CSS-Tricks* is created, written by, and maintained by Chris Coyier. It is built on WordPress, hosted by Media Temple, and the assets are served by MaxCDN. The fonts are Source Sans and Source Code Pro. It is made possible by viewers like you who subscribe to The Lodge and through advertising for products and services I like.