Web Components

And the future of Modular CSS

Philip Walton / @philwalton

I have good news
and bad news

The good news

For a long time Web Components were primarily a Google effort, but now all browser vendors are on board.

Web Components will likely work natively in all browsers sometime in 2016.

The bad news

When I submitted this talk proposal, Chrome had a native implementation of all the Web Component features.

But in order to reach consensus, the spec had to change.

That means, unfortunately, no browsers currently implement the latest standards.

What has changed?

Why should CSS developers care about Web Components?

I actually think CSS Developers have the most reason to care!

To understand why, let's first look at what makes CSS hard today.

  • Managing global names
  • Scoping/isolating styles
  • Specificity conflicts
  • Unpredictable matching
  • Managing style dependencies
  • Removing unused code

What do all these have in common?

Hint, it's not these…

~~#sidebar ul li a {~~ color: blue; display: block; padding: 1em; ~~}~~

…it's these

#sidebar ul li a { ~~color: blue; display: block; padding: 1em;~~ }

In CSS, your selectors are the biggest determining factor in how scalable your code is.

Selectors are globals. It's hard to write predictable code when any rule you write could potentially conflict with another rule you didn't know existed.

Writing bad selectors today will likely cripple your ability to makes changes to the front-end in the future.

What are bad selectors?

Selectors that cast a wide net increase the chances of unintended matches.

And for whatever reason, people who write CSS like to live dangerously!

#main #content div div div { float: left; width: 50%; }

This is not a joke. This actually happened.

But my selectors aren't that bad.

Maybe you're using a naming convention, or a linter, or something else that ensure your specificity is low and you're only targeting classes…

But even that has problems

Are all these classes needed? What do they do? If I remove one of them will everything break? WTF is going on?

#implementationdetails

So what is the answer?

If selectors that depend the markup structure is bad, and if markup full of implementation details is bad, how should we be writing CSS?

The problem isn't that our CSS is bad, the problem is it's operating on far too much DOM.

The real question isn't "how do I write good CSS?" It's:

How should we change CSS to make it harder to screw up?

What is CSS missing?

If I could change anything about HTML and CSS to make these problems easier to solve, what would it be?

As it turns out, Web Components gives us all of these.

But wait!

Why should we change CSS? Aren't these problems already solved by:

NO!

Ok, maybe a little bit…

But there are some key differences between the current solutions and Web Components:

What are the benefits of a platform-based solution?

Sold yet? Ok, let's dive in!

The Anatomy of a Web Component

Shadow DOM

What is it, and how can I get me some?

Shadow DOM is a subtree of DOM nodes that you can create on any HTML element.

The shadow DOM subtree gets merged into the main DOM, but unlike the main DOM tree, shadow nodes can't be styled from outside.

In short, shadow nodes are private.

A simple example:

// Creates a new paragraph element and adds it to the DOM. var p = document.createElement('p'); document.body.appendChild(p); // Creates a shadow root on an the paragraph. **p.createShadowRoot()**; // Sets the paragraph's shadow root's HTML contents. **p.shadowRoot.innerHTML** = 'Sweet sweet contents...'; Shadow DOM demo →

Custom Elements

Custom elements are initially defined as JavaScript classes.

They extend HTMLElement and then get registered on the document for use as a specific HTML tag.

Custom elements typically create a shadow root in their createdCallback lifecycle method.

A simple example:

// Creates a MediaObjectElement class that extends HTMLElement. class MediaObjectElement extends HTMLElement { createdCallback() { var shadowRoot = this.createShadowRoot(); shadowRoot.innerHTML = 'Shadow DOM contents...'; } } // Registers the `<media-object>` element for use. document.registerElement('media-object', MediaObjectElement); Note: the other lifecycle methods are attachedCallback, detachedCallback, and attributeChangedCallback.

HTML Imports

HTML Imports are a way to package up a custom element and any of its dependencies into a single file.

You no longer need to manage a component's HTML, CSS, and JavaScript dependencies separately.

Other custom elements (or the main document) can then "import" the imports before using them.

Templates

Template are inert bits of DOM. By "inert" I mean their contents aren't processed by the browser.

Templates are typically used to define a reusable bit of DOM that can be cloned and assign to another DOM node.

A template and import example:

super-element.html component definition:

<link rel="import" href="dependent-element.html"> <script> class SuperElement extends HTMLElement { createdCallback() { this.createShadowRoot().innerHTML = document.querySelector('template').innerHTML; } } document.registerElement('super-element', SuperElement); </script>

How does Shadow DOM interact with the main DOM?

Shadow DOM uses the <content> element to define insertion points. The insertion points house the main DOM.

The main DOM:

Shadow DOM:

The final, composed DOM tree

~~
~~ ~~
~~ ~~
~~

~~
~~

What does modular CSS with Shadow DOM look like?

Most software development techniques for modularity and code reuse center around two principles.

Inheritance

Inheritance generally establishes an is-a relationship.

// Inheritance in Sass .profile-card { @extends .media-object; /* ... */ } // Usage:

Inheritance with Web Components

The extending component will typically:

<profile-card> extends <media-object>:

<link rel="import" href="media-object.html"> <script> class ProfileCardElement extends MediaObjectElement { createdCallback() { super.createdCallback(); this.shadowRoot.innerHTML += document.querySelector('template').innerHTML; } } document.registerElement('profile-card', ProfileCardElement); </script> Media object demo  |  Profile card demo

Composition

Composition generally establishes a has-a relationship.

Composition with Web Components

The composing component will typically:

Composition usually involves building a complex element from simpler elements, or "base" elements.

What is a base element?

The media object is a classic OOCSS base element.

Base elements typically define layout and positioning while leaving color and ornamentation unspecified.

Example layout base elements

The <flex-grid> element:

Flex grid demo →

The <flex-line> element:

Flex line demo →

Building layouts from composable primitives.

<flex-line> and <flex-grid> are very presentational, so you might not want to have them appear in your main document source.

Luckily, you don't have to.

The next example composes <flex-line> in its shadow DOM to build the classic "Holy Grail" layout.

The main DOM:

Holy grid demo →

The shadow DOM:

The final, composed DOM tree

~~~~ ~~
~~
~~
~~ ~~~~ ~~
~~
~~
~~ ~~
~~ ~~
~~ ~~
~~ ~~
~~ ~~
~~ ~~
~~
~~
~~ ~~
~~

Composition + inheritance

And example <profile-card-gallery> element, built using:

The profile card structure

The main DOM:

~~…~~ ~~…~~ ~~…~~ ~~…~~

The shadow DOM:

Profile card gallery demo →

The final, composed DOM tree

~~~~ ~~[media-object]~~ ~~[media-object]~~ ~~[media-object]~~ ~~[media-object]~~ ~~~~

Theming with custom properties

The <profile-card-gallery> component uses CSS custom properties to pass style data to the <flex-grid> element inside its shadow DOM.

flex-grid { --flex-grid-basis: 26em; --flex-grid-gutter: 1.5em; }

Custom properties replace the shadow piercing combinators as the de facto way to style third-party components.

In some cases, it's unreasonable to create custom properties for all the possible CSS properties someone may want to style.

The @apply rule allows component authors to opt-in to letting users of their component add any styles they want.

/* In the main document CSS */ :root { --flex-grid-styles: { border: 1px dotted; margin: 1em; } } /* In the component's shadow DOM */ flex-grid { @apply --flex-grid-styles; }

Custom pseudo-elements

Custom properties work very well as a way to pass data through the shadow boundary in a way that a component can opt-in to receiving.

In addition to custom properties, a future spec will likely define custom pseudo-elements for more direct third-party styling.

Defining custom pseudo-elements in the Shadow DOM:

Referencing custom pseudo-elements in external CSS:

date-range-picker**::part(start-date)**, date-range-picker**::part(end-date)** { font-size: 1.5em; }

Mixing in common styles:

In many cases, a component will need to depend on a set of styles that don't make sense as individual Web Components.

To include those styles, add their contents to a style tag in the element's Shadow DOM.

Wrapping up

Writing scalable CSS today is challenging

All CSS rules are global and they often conflict with other rules in unexpected ways.

Managing the global nature of CSS at scale usually involves littering the HTML with implementation details.

Abstracting away these implementation details often requires complex build processes or templating systems that raise the barrier to entry for new developers.

Web Components address all of these problems

And they do so at the platform level, so the barrier to entry remains low.

Web Components will change how we think about modularizing our CSS.

Verbose naming conventions will go away; they'll no longer be necessary.

CSS files will become smaller, self-contained style declarations that do one thing and one thing only.

Components will be styled by composing and extending existing styles.

Third-party component theming will be opt-in.

Web Components are happening and they're happening soon.

You can already use most Web Components features (with the exception of Shadow DOM) via the webcomponents.js polyfill.

I predict Shadow DOM will be available in all browsers within one year from today.

#shadowdom2016

You can prepare for Web Components today

Start (or continue) thinking about your site design in terms of reusable components.

Use a methodology like BEM, SMACSS, or OOCSS, which will make the transition very natural.

Final thoughts

The web platform will continue to work as it does today, long into the future. If Web Components aren't the best solution to your problem, feel free to use another solution.

Speaking for myself, I can't wait for a world in which Web Components, ES6 modules, and HTTP2 are all fully supported everywhere.

The future is bright!

The End

Twitter
@philwalton
Website
philipwalton.com
Github
github.com/philipwalton
Slides
github.com/philipwalton/talks