 Hi. Welcome to Design at Scale with Web Components and maybe a few docs. 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. My name is 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. 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. 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 and 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, Ms. Rowdy, 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. 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 devela ducks and dozens of teens making innovative and useful products. A group of designer ducks came together and made a strong brand for Quaggle called Bred 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 Bred 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. Bred 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 some 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. Some pieces of the design have really strict requirements such as the label where they can only change the text. Other parts have 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 structure in its own sandbox DOM tree. Styles outside the shadow root won't affect children inside it, nor will styles inside the shadow root leak to other elements on the outside. So with Shadow DOM, both our styles and our client 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, TLDR, if your component acts like a native element, it'll work in scale really well in the frameworks and libraries out there. 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. 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 bread 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 QuackInput. 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 displayed label, so let's add a property for that. We'll need a label element to display it. 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 mockup. 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. 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 that bubbles and 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 an interaction set up. Last thing we need to do is turn our component from drab to quacktastic. Let's add some styles with let elements static styles property. Quacket helped us here already with a bunch of styles from our mockup, 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. 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. Some props, like class name and on change, are handled differently for web components. The wrapper will make using them seamless for Mallard Map develop decks. Other props, including value, on click and more, don't need any special handling, so they can be passed directly to quack input. For props like on change 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 quack input 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 quack input styling. And our team got a thoughtful bread loaf, as a thank you. 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 in the wild jungles of MPM node module trees. Lion web components. These are white label components built by ING and a design system that can be used by anyone. 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. All right, Liz, what's next with Shadow DOM? Well, 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. Oh, those are great points, Rody. We can use element internals in the accessibility object model, or AOM, to help address these needs. We can call attached internals on a custom element and get an element internals 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 element internals interface. Element internals and the described features of the accessibility object model are ready to use on Chrome and Edge today. AOM ARIA attribute reflection is also ready to use on Safari. All right, let's take a look at how we can add element internals and the accessibility object model to the web component we just made. Let's start with form integration. To register our component as form associated, we need to add a static form associated 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. We can do that in the element update lifecycle. Whenever our value property changes, we can update the form value as well, using element internals. That was easy. Well, buckle up, Declings. 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 labeled by attribute on our component and everything should work. So let's start there and work backwards. 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. Attribute changed callback. Whew, they really picked long names for these, huh? We only care about the attribute that is our ARIA labeled by attribute. Everything else can be passed off to lit elements implementation in the super class. Okay, finally, let's use the accessibility object model. Since AOM reflects reference IDs to elements themselves, we can use the ARIA labeled by elements 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 shadow roots 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. Otherwise, the screen reader will announce the labels twice and that would just confuse a bunch of ducks. Wait, what is it quackette? 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 inputs labels after just adding them. Good catch. Let's see this in action. Quackette, who's next on our list of teams to visit? Oh, of course, that's right. 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 view and they have booking forms that bread designed 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. 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 view 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. When using the aria labeled by attribute and an external labels ID, screen readers announce the external label even though the browser is focused on an input element deeply nested inside the shadow root 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 typography. Some tokens inherit from each other. For example, a label primary color inherits from the amber color. To start off, Rody gives the tokens to implement the light theme and also the dark theme with the correct tokens for each. 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 fall-by 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. 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 and scope custom properties to specific sections of a DOM tree using simple CSS selectors for incredible flexibility in 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. In a custom element shadow root, pieces of the shadow DOM can be exposed using the part attribute. Clients can then use the part sudo 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. 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 Quagginfits 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. Without it, we need to add another custom property so users could change the font weight. So TLDR, part means fewer custom properties, but potentially more maintenance. All right, let's dive in and bring design tokens to QuagInput with the help of custom properties. We'll begin with color. That's a good place to start. Our label and 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 token's fallback value. Let's work on typography next. We have QuagInput 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. 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 define the primary design token as the color provided in our dark theme mock-ups. There are a couple of reasons to use the prefers color scheme which include 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, Srody. Okay, we've got the colors for our component and the means to change them. I know the next team that desperately wants to use these new features. QuackTube, making duck 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. First things first, in Angular, don't forget to add the custom element schema to the NG modules 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 QuackInput. 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 developed ducks have added a bunch of custom property declarations. 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? True, but it 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 a SP theme helper element rather than being set manually by the user. It's a great resource if you wanna see a pre-built design system like Spectrum or just reference it for inspiration. So is this one to 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 and the proper token inheritance. True, luckily there's a new technology called register 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 lengths, numbers, URLs, images, and of course, colors. 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. The browser will use this even if we have not provided a value declaration or fallback for our property. Registered 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 Quacken puts 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 and maintain. So let's clean it up. For bread designs 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. 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 went to override our initial definitions. And let's not forget about dark mode. 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. Additionally, they receive improved performance from browser optimizations that registered properties bring. That's pretty impressive, Liz. So where does that leave quacket in our own story? Come on, quacket. Let's do a quick recap. Quackle 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 developed Ducks customization needs. And quacket, she gets a well-deserved vacation. Good job. Nice work, quacket. Well, if you hadn't figured it out, quaggle and bread design is a story of Google and material design. We faced 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. 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.