Design at scale with Web Components (and ducks)

Dec 22, 2020 23:00 · 4509 words · 22 minute read projected content 36 bunch adaptable

(upbeat music) - Hi, welcome to Design at scale with Web Components and maybe a few ducks. My name is Liz, I’m a Software Engineer on the Material Design Team at Google. Here on the Material Team we believe in a simple philosophy to create design systems at scale. Design once, build once, use everywhere. If you don’t know us already you may be wondering who or what is Material? Luckily, I’ve got a great partner with me here who knows a lot about that. Take it away, Rody - Thanks, Liz.

00:42 - My name’s Rody and I’m also on the Material Design Team. I’m a Developer Advocate at Google. Material is a design system created by Google to help teams build high quality digital experiences on Android, iOS, Flutter, and the web. It is an adaptable system of guidelines and components and tools that support the best practices of user interface design backed by open-source code. Material streamlines collaboration between designers and developers to help teams quickly build beautiful products. Theming makes it easy to customize Material Design to match the look and feel of your brand with built-in support and guidance for customizing colors, typography, and corner shapes.

01:20 - The goal with Material is to have a single unified design. When building Material Design at Google there are multiple things we need to keep a priority, supporting multiple frameworks, ensuring spec compliance, and defining a design token API. - So what’s the solution? - Well, Web Components. They allow us to build once which is part of our philosophy. How do Web Components solve this? Well they’re supported in all major browsers and work in every framework.

01:48 - And with the use of Shadow DOM it encapsulates styles from the application and stops the component styles from leaking through the existing application CSS and affecting the component styles. You can use CSS custom properties to provide an API for design tokens. You have properties that reference other properties in creating cascading tokens and effects. With that we can bring Material Design using Web Components to all of our platforms and teams. - I’m sure someone’s thinking this, Rody. What’s this got to do with our lovely viewers? - Well, Material has shared the same problems as other design systems when trying to scale.

02:23 - We want to help you build better systems and share a little bit about what we’ve learned along the way. To design at scale with Web Components we need to design once, build once, and use everywhere. So Liz, how did Material build scalable Web Components for our own design system? - Let’s tell a story for our audience about a company called Quaggle. Once upon a time, there was a quirky quacky gaggle of ducks with a mission to make great products. They grew into a large company with a flock of thousands of develo-ducks and dozens of teams making innovative and useful products.

03:05 - A group of designer ducks came together and made a strong brand for Quaggle called Bread Design. The ducks loved it and wanted to bring it to all of their products but they didn’t know how to do so, until one day a clever duck named Quackett suggested to use Web Components. Let’s go on a journey with her and look at how she and the Bread Design team built one of these components. Ready, Quackett? Ready, Rody? - I’m so ready, Liz. - All right, let’s build a component. The first step is getting a design mock-up with all the pieces and interactions we’ll need.

03:47 - - Bread Design wants Quaggle products to use the same delightful text input everywhere. At the start of the process designers provide us with mock-ups that give us the pieces of our component including a label, an input, and an optional icon. They also instruct us how the user should interact with the component. They should be able to quack text into the input. Blue lines give us dimensions of each piece of the design and how they should be laid out.

04:11 - Some pieces of the design have really strict requirements such as the label where they can only change the text. Other parts of loose requirements such as the optional icon that can be any image icon. So Liz, what Web Component API can help us build all this. - Well, Shadow DOM would be perfect for these complex styles. You see Shadow DOM gives us a shadow root with that we can encapsulate our Web Component’s structure in its own sandbox DOM tree. Styles outside the shadow root won’t affect children inside it. Normal styles inside the shadow root link to other elements on the outside. So with Shadow DOM both our styles and our clients’ styles are protected. On top of that within our encapsulation we can start to scale by following native element patterns such as using the same attribute, property, and event naming conventions that we see from existing HTML elements. So tl;dr if your component acts like a native element it’ll work in scale really well in the frameworks and libraries out there.

05:15 - - That’s really great but what about the slot? - It’s a great question. Slots are special elements inside a shadow root that allow you to define where the light DOM is projected. Slots can either be unnamed or named to project content with the slot attribute. The tricky thing with slots is that they can break design compliance. The projected content can be styled by the user arbitrarily so we lose control over defining what can and can’t be styled.

05:47 - However, they’re a vital part of Web Components and should be used to compose children. For design compliance you can use the slotted star selector along with display none to control what can and can’t be used inside of a slot. All right, let’s use shadow DOM and show the audience how Quaggle turned Bred Design from a mock-up into a Web Component. First let’s set up a basic Web Component. We’ll use lit-element to help us write a fast, lightweight component with efficient rendering but you can use anything to write your own Web Components from Stencil to native. We’re registering our component with the custom element API that allows us to define new HTML tags. We’ll call this one quack-input.

06:33 - Our render method is looking a little empty right now. Let’s add some attributes and properties. This is an input, right? Let’s start there and add an input element. It wouldn’t be much of one without a value to set though so let’s follow native patterns and add a property called value. We’ll bind it to our input that we just created. Now design has stated that users can change the display label so let’s add a property for that. We’ll need a label element to display it.

07:06 - We can bind our property to the text content in our render method. So the label can be read by screen readers for accessibility we’ll add an ARIA attribute to the input and connect it to the label. And let’s not forget about the icon from the mock-up. Design told us the icon could be provided by the user though so let’s make it a slot instead. Okay, looking pretty good. Lastly, let’s add some interaction following the gold standard of native element patterns.

07:41 - Let’s listen to the change event on the input by adding a change event listener. Whenever the user types into our input, the value property should update. Finally, let’s dispatch our own change event the bubbles en composes outside of our shadow root so that other elements can listen to it. That way it’ll be intuitive for developers to use and compatible with frameworks. Great, we have interaction setup. Last thing we need to do is turn our component from drab to quacktastic.

08:16 - Let’s add some styles with lit-element static styles property. Quackett helped us here already with a bunch of styles from our mock-up but she asked us to finish up with the slotted icon. First let’s hide all content by default. We can use the slotted pseudo element to select projected light DOM content. Next, we can add another rule to display SVG tags as well as set an explicit width and height for them so they don’t pop out of our quacky little container. With this our clients can provide their icons and we’ll be able to reduce how much maintenance our team takes on with what type of content is supported in our icon slot.

09:00 - Now that we have our component built let’s meet our first team that wants to use it. Mallard Maps, helping ducks everywhere find the closest pond to migrate to. Mallard Maps is built with React. They want to use Bread Design’s input for their migration search feature but they’re worried about their app’s styling being an issue. Lucky for us, Shadow DOM ensures that isn’t a problem. To improve their experience, Mallard Maps is creating a wrapper React component.

09:34 - Some props like className and onChange are handled differently for web components. The wrapper will make using them seamless for Mallard Map develo-ducks. otherProps including value, onClick and more, don’t need any special handling. So they can be passed directly to Quack-input. For props like onChange that require a reference to the element we can use a ref and callback hook. Let’s zoom in, React’s callback hook can be used to retrieve the value of a DOM element whenever it changes. We’ll use the ref hook to store the current value of the DOM element. We can add and clean up logic on the element for the props that we’re wrapping such as event listeners. Now Mallard Maps can easily use QuackInput in the app with the best developer experience for their team. With Shadow DOM they don’t need to worry about the app’s complex styles interfering with QuackInput styling and our team got a thoughtful Bread loaf as a thank you.

10:41 - - You know, Liz, this reminds me of something else. - Material Design? - I see what you did there, but no. I mean, using the Shadow DOM encapsulation attributes and properties and slots as a powerful API to scale and manage your design system. It reminds me of a web component set that I’ve seen the wild jungles of NPM node module trees, Lion Web Components. These are white label components built by ING in a design system that can be used by anyone.

11:10 - These components use slots and attributes to set up pieces that can be customized in an extended design system like ING web. On the left we have the white label components and on the right we have the first-party components. These components can be used as a starting point for your own design system. When extending Lion Web Components you can use both attributes and/or slots to customize what is shown. You can even check them out at the QR code right here.

11:36 - All right, Liz, what’s next with Shadow DOM? - What do you mean ? - Isn’t accessibility a problem with reference IDs? Also I tried using a Web Component in a form and it didn’t submit like the other inputs. - Those are great points, Rody. We can use ElementInternals in the Accessibility Object Model or AOM to help address these needs. We can call attachInternals on a custom element and get an ElementInternals instance which provides many internal features to help integrate our web component with platform features such as forms and Accessibility Object Model. The AOM itself is an exciting new set of features which include reflecting ARIA properties and attributes, reflecting element references, and providing default semantics for custom elements through the ElementInternals’ interface. ElementInternals and the describe features of the Accessibility Object Model are ready to use on Chrome and Edge today.

12:37 - AOM ARIA attribute reflection is also ready to use on Safari. All right, let’s take a look at how we can add ElementInternals and the Accessibility Object Model to the Web Component we just made. Let’s start with form integration. To register our component as formAssociated, we need to add a static formAssociated property. This tells the browser that our custom element is ready to participate in forms. The last thing we need to do is set the form’s value.

13:08 - We can do that in the element’s update lifecycle. Whenever our value property changes we can update the form value as well using ElementInternals. That was easy; well, buckle up ducklings. ARIA and accessibility can get a bit tricky. Always remember the golden rule act like native HTML elements. That means a user should be able to set the aria-labelledby attribute on our component and everything should work. So let’s start there and work backwards.

13:43 - We’ll need to know when that ARIA attribute changes. Custom elements have a static property called observed attributes that allow us to list which attributes we want to listen to. Normally lit-element manages this for us. So we’ll need to call super to keep that behavior while adding the extra attribute we want. Next let’s override the custom element callback for that static property: attributeChangedCallback. Whew, they really picked long names for these, huh? We only care about the attribute that is aria-labelledby attribute.

14:18 - Everything else can be passed off to lit-element’s implementation in the superclass. Okay finally, let’s use the Accessibility Object Model. Since AOM reflects reference IDs to elements themselves we can use the ariaLabelledByElements property to get the actual elements that the ID or IDs in the attribute refer to and we didn’t have to do anything for them. Pretty neat, huh? With that, we can use the same property and pass those elements off to our shadowRoot’s input since that’s what the screen reader will be focusing on and what the elements should be labeling. Hmm, but what should we do about our host Web Components attribute? We should probably remove it.

15:00 - Otherwise the screen reader will announce the labels twice and that would just confuse a bunch of ducks. Wait, what is it, Quackett? Oh, good point. If we remove our attribute our change callback is going to be called again. We’ll need to add a flag and gate our logic so that we don’t accidentally remove our input’s labels after just adding them. Good catch. Let’s see this in action. Quackett, who’s next on our list of teams to visit? Oh, of course, that’s right.

15:36 - I remember who needs these features, POND. A Quaggle Travel company helping ducklings find an Island with a pond to call their own. POND is built with VUE and they have booking forms that Bread Design’s input would be perfect for. They want to make sure it will properly submit with their forms. Additionally, they want to use their own external labels with the inputs.

16:02 - Those labels will need to be hooked into our inputs so they can be read by screen readers for ducks with accessibility requirements. POND works on a loaf exchange system. Here’s one of their VUE components that is using our quack-input to pay for a travel trip. Our golden rule seems to be paying off. Our quack-input is nearly indistinguishable from a native input. To use it inside of a form a name attribute must be provided, just like other form elements. When the form submits quack-input’s value is associated with that name key.

16:36 - When using the aria-labelledby attribute and an external label’s ID screen readers announce the external label even though the browser is focused on an input element deeply nested inside the shadowRoot of the host element. That was pretty successful. Well, Rody, we have our component structure but I think it could use a splash of color. What’s next from design? - Well, Bread Design has provided us with design tokens to specify colors, shape, and topography. Some tokens inherit from each other. For example, a label primary color inherits from the amber color. To start off, they gave us the tokens to implement the light theme and also the dark theme with the correct tokens for each.

17:16 - So Liz, since you had such a good answer last time, what is the Web Component API that can help us out here? - Of course we can use CSS Custom Properties. Custom Properties are user-defined variables. We can define a Custom Property with a value and then use that value in a CSS declaration. Custom Properties can also provide fallback values to use for when they have not been defined. The best part is that Custom Properties can be overridden so that you can dynamically change the value of a declaration at runtime.

17:51 - This is why CSS Custom Properties are perfect for design tokens. Each Custom Property can match one to one with individual design tokens. Properties can inherit from other properties as well, just like our tokens do. And finally, you can apply in scoped Custom Properties to specific sections of a DOM tree using simple CSS selectors for incredible flexibility and a satisfying runtime customization experience. - So Liz, what about the CSS part API? Can we just use that instead of all the Custom Properties to expose our theming? - It’s another great question.

18:28 - In a custom element shadow root, pieces of the shadow DOM can be exposed using the part attribute. Clients can then use the part suitor element to add any CSS declarations to the part. As you can guess, parts are similar to slots. They can potentially break design compliance and expose arbitrary styling on pieces of a component. This can be good if your design system allows one-off customizations since it can mean fewer custom properties.

19:00 - However, it can also be difficult to maintain if your design system wants strict compliance. Especially with larger teams where there’s less direct control over what clients are doing, like Quaggle. For example, could a user make quack-input’s label bold? If so, we could expose a part and allow them to set the font weight. However, they’re also able to change the font size, add padding. So it could be dangerous exposing a part if we can’t enforce what declarations are used.

19:32 - Without it, we need to add another Custom Property so users could change the font weight. So tl;dr part means pure custom properties but potentially more maintenance. All right, let’s dive in and bring design tokens to quack-input with the help of Custom Properties. We’ll begin with color, that’s a good place to start. Our labeling input should be the primary design token color. According to our design token inheritance the primary color should be Amber 700. So we can add that in as a primary tokens fallback value. Let’s work on topography next, We have quack-input specific tokens for that. The label and input share the same font family but they each have their own token for their respective font sizes. All right, pretty good. Now what should we do about dark theme? - I think I can help with that, Liz.

20:30 - Here we can use the media query prefers-color-scheme to detect if the user has turned on a dark mode on their device. Now all we need to do is to find the primary design token as the color provided in our dark theme mock-ups. There’re a couple of reasons to use the prefers-color-scheme which includes better accessibility, automatic dark mode switching and power savings with OLED displays. You can check out the web.dev link here to read the blog post to learn more. - Wow, thanks Rody. Okay, we’ve got the colors for our component and the means to change them.

21:04 - I know the next team that desperately wants to use these new features, QuackTube, making ducks stars or just helping them find fish memes. QuackTube is built with Angular. They want to use Bread Design’s input for their comments section but they have unique brand colors that are different from Quaggle’s default brand. See they’re a sister company under the larger Waterfowl Corporation. Lucky for us though, design tokens were specifically designed for this. Custom Properties will let QuackTube override their default colors to match what they need.

21:46 - First things first, in Angular, don’t forget to add the custom element SCHEMA to the NgModules that are using custom elements that tells Angular to expect element tag names it wouldn’t immediately recognize. All right, let’s get started. First, let’s look at our comment component using quack-input. Everything looks pretty standard for an Angular component and its template, as well as what we’ve seen so far. So let’s move on to the styles. That’s where QuackTube wants to change things up. QuackTube develo-ducks have added a bunch of custom property declarations.

22:23 - With a simple CSS selector they’re able to scope the changes they wanted to make, including color and typography. - Using Custom Properties is actually pretty straightforward, way to go QuackTube. You know Liz this is starting to ring a bell for me too. - It’s kind of like Material Web Components, huh? - (laughs) True. But I was meaning the Custom Properties for design tokens. There’s some great teams out there doing the same thing with their design systems like Adobe Spectrum Web Components. Spectrum is Adobe’s design system that defines many tokens including those that inherit from each other. Spectrum Web Components uses Custom Properties to implement their design tokens. Tokens are set and provided via sp-theme helper element rather than being set manually by the user. It’s a great resource if you want to see a pre-built design system like Spectrum or just reference it for inspiration.

23:16 - So is this when I ask what’s coming next, right? - You mean when you mention all the problems with Custom Properties? - Yeah, users can provide any value to a Custom Property and it may not even be the right one. It’s also hard to maintain all the extra CSS and make sure all the default values are set in a proper token inheritance. - True, luckily there’s a new technology called Registered Properties that can help us with these problems. Custom Properties can be registered with the browser using the app property rule in CSS or CSS.registerProperty in JavaScript. We can set the property syntax to many types such as links, numbers, URLs, images, and of course colors.

23:59 - The syntax will make our Custom Property behave like a native user agent property. If the user provides an invalid value the declaration will be ignored. Whereas unregistered custom properties would completely drop the declaration and result in an unstyled element. We can also set whether or not their property is inherited between a child element and its parent, like the color property or if it’s not, like the border property. Finally, we can give our custom property an initial value.

24:31 - The browser will use this even if we have not provided a value declaration or fallback for our property. Register Properties are ready to be used in Chrome and Edge today. Let’s take a look at how we can add Registered Properties to our component to clean things up. This is what quack-input’s CSS currently looks like. We have a lot of fallback values for inheritance between design tokens and their initial values. It’s pretty hard to read or maintain, so let’s clean it up. For Bread Design’s Components we’ll provide a global style sheet that clients can import. It will register our design token Custom Properties as well as define the inheritance between them. Let’s register our static Amber 700 token first with its initial value. We’re setting it to inherits false because we don’t anticipate users changing the value of this token.

25:25 - So there’s no need for the browser to spend extra effort calculating the inheritance. Next we’ll register our primary token. It will be set to inherit because it’s a color token and we want users to set it once on a parent element and let the value inherit through all of the children of that element. Finally, let’s establish the relationship between these two design tokens. We’re using the root selector because it’s less specific than the HTML tag selector. Just in case our clients want to override our initial definitions, and let’s not forget about dark mode.

26:03 - We can move our dark mode styles from inside our Web Component to this global style sheet since all of our components will be dark mode compatible. Let’s look at how this changed quack-input CSS, much better. This will be a lot easier to maintain and result in a smaller file size, especially as the number of design tokens and web components increase. So let’s revisit QuackTube and see what has changed. Actually not much, they’re still using the same design tokens except now they have a smaller bundle size, which means faster loading for their users.

26:40 - Additionally, they receive improved performance from browser optimizations that Registered Properties bring. - That’s pretty impressive, Liz. So where does that leave Quackett in her own story? - Come on, Quackett, let’s do a quick recap. Quaggle now has a solid foundation of Web Components for Bread Design that were built once and can be used everywhere. Shadow DOM ensures compliance with a unified brand and Custom Properties provide a design token API for all develo-ducks customization needs and, Quackett, she gets a well-deserved vacation, good job. - Nice work, Quackett. Well, if you hadn’t figured it out Quaggle and Bread Design is a story of Google and Material Design.

27:28 - We face with the problems of bringing Material Design to Google’s Teams, Maps, Travel, YouTube, and more. We encounter many of the same problems that other design systems face when scaling, supporting multiple frameworks, ensuring spec compliance, and providing a robust API for customizing our design tokens. Web Components allow our design systems to scale on the web and we hope our discoveries can help your design system scale too. - All of the code from this talk, including quack-input and examples for setting up Web Components in each of the frameworks mentioned is available to view and play around with. You can also find out more about what’s coming next with the future of Material Web Components.

28:06 - We’re really excited about the direction Material on the web is going and we hope you check it out. Thanks for tuning in, I’m Liz. - And I’m Rody, and remember - Design once. - Build once. - Use everywhere. (upbeat music) .