451: JavaScript and Web Components with Nolan Lawson
Nolan Lawson talks with Dave & Chris about his emoji picker element and how styling a Web Component works, why documentation is important, constructable stylesheets, how to build with Svelte, React or other frameworks, and the accessibility story with Web Components.
Guests
Time Jump Links
- 02:12 Emoji Picker Element
- 08:52 How do you style a Web Component?
- 22:32 Documentation is important
- 25:02 Sponsor: Linode
- 26:52 Adding a class to style
- 32:32 What about constructable stylesheets?
- 35:42 What about Svelte or React or other frameworks?
- 40:52 Sponsor: CodePen PRO
- 42:22 Writing in Vue and use it in another framework?
- 49:52 Accessibility and Web Components
- 54:32 Should there be a no JavaScript fall back?
Transcript
[Banjo music]
MANTRA: Just Build Websites!
Dave Rupert: Hey there, Shop-o-maniacs. You're listening to another episode of the ShopTalk Show. I'm Dave--live in the shed from snowy Austin, Texas--Rupert, and with me is Chris--warm and definitely has power--Coyier. [Laughter]
Chris Coyier: Yeah.
Dave: Hey, Chris. How are you doing?
Chris: Man, Dave. I'm so sorry; 70+ hours of no power. That's wild. A little snow in Texas really goes south, too.
Dave: A little snow in Texas, and I'll just apologize ahead of time here if I'm bringing any extra rage to the show [laughter] and old man complaining energy. It's been a long week, but I'm glad to be here because we are talking about Web components and other things. Chris, who do we have in the studio?
Chris: Yeah. We've been hoping to do a mini-series here on what JavaScript is like in 2021 because there's a lot going on. It feels very different than the last time we did a series like this in 2019. We talked to the bundler fellas a couple of episodes ago about how that's changing and how ES Modules is changing so much in the JavaScript ecosystem. That's just the tip of the iceberg. There's so much other stuff to talk about.
Web components is always on the mind a little bit because the journey of them has just been so fascinating to follow. I came across this blog post by Nolan Lawson about styling Web components. That's just a part of the greater story of Web components, but at least we could talk about that and then see where it goes.
We got Nolan on the show. How are you doing, Nolan?
Nolan Lawson: I'm doing good. Happy to be on the show.
Chris: Great. Thanks so much for being here. That's just one of them. Maybe we'll start there because I think this will help permeate the whole conversation is that you have a Web component that you make publicly available to the world that is an emoji picker, right?
Nolan: Yeah, that's right. I wrote this thing last year. It's called emoji-picker-element - kind of presumptuously, almost as if it were built into the browser. That's kind of what Web components are aiming for, right? It's to kind of give us these customizable elements that sort of feel like the HTML5 days when we got video and audio and we felt great about that. Anyone can build it and you can just drop this little emoji picker tag onto your HTML and you've got an emoji picker.
Chris: Yeah. I've used it as a demo just to show exactly that because you can write one line in your JavaScript that imports this emoji-picker-element because it does need JavaScript. I guess we'll get to that, but that's the nature of the beast for Web components, at the moment.
Then you use the element. It's literally just angle bracket emoji picker, right? Then all of a sudden, guess what, an emoji picker shows up. You click it, it opens, and it's like that thing in Slack or that thing on everybody's different -- you know, on Mac OSX. I press--what--option, command, space bar and get it. I'm sure there's one on Linux and Windows as well that give you this component. But that's the native one.
This is one that you built on the Web and it's just obviously tremendously useful. You can imagine Web apps needing to add an emoji picker and them being like, "Why would I spend two, three days doing this? Here's one." I guess that's kind of the goal.
Is it in something? Did you need it?
Nolan: Yeah, so the genesis of this thing was, I maintain a Mastodon client Pinafore, which is a PWA. Mastodon is basically just an open-source Twitter, social media platform.
Chris: Mm-hmm.
Nolan: I knew I needed an emoji picker. In the beginning, I didn't have an emoji picker built into this thing. You could compose statuses, but I would just tell people, like, "Hey, just use the one," like you said, "built into your OS," right? I like to not build things myself unless I really have to. I like to rely on the browser or the OS for these kinds of things.
I just kept hearing from people, "Hey, on certain versions of Linux, they don't have an emoji picker," or certain keyboards. A lot of people don't even know that these things exist. Us technologists--
Chris: They don't.
Nolan: Yeah. Us technologists, we've got those keyboard shortcuts memorized to launch the emoji picker, but I challenge you to ask a non-techie if they know that there even is an emoji picker built into Mac or Windows.
Chris: Oh, yeah. if I tweeted right now, "Oh, my God! I just found out there's an emoji picker on Mac OS," and put the keyboard command and posted a screenshot of what shows up when I press the key command, it would get hundreds of likes. They'd be like, "Oh, my God! I never realized." I promise. You think people know stuff and they don't.
If you used this, you wouldn't need to know anything because there's a little thing and you click it. The learning curve is low.
Nolan: Yeah, that's exactly it, yeah. People are just kind of trained now to look for the emoji picker that's built into Slack or built into Twitter, Facebook, or whatever. They each bring their own. I was always really hesitant to build my own.
Actually, the first thing I did was used a React emoji picker called Emoji Mart. I bundled it as a Web component because my Web app was written in Svelte and it already uses some Web components. I just find it to be a really nice kind of mechanism for including third-party code.
I actually ended up contributing to Emoji Mart a bit. I fixed up some issues with accessibility. But at some point, I just got a little frustrated. I wanted -- I was contributing to it so much and there were a bunch of things I wanted to do.
At some point, I was just like, "You know what? I'd rather just write my own emoji picker." Actually, the original motivation had nothing to do with Web components. It was performance.
I'm a performance engineer, actually. I was dissatisfied with the way all these different emoji pickers out there worked. If you look at basically any emoji picker on NPM or GitHub or wherever, what they're doing is they're downloading the entire emoji set, like every emoji ever and all the little metadata about each emoji, all the skin tones, descriptions, shortcodes, and all that kind of stuff, and just keeping it in memory.
Chris: Right, so they're like two megabytes of text data.
Nolan: Yes.
Chris: Yeah.
Nolan: In some forms, it gets up to almost a megabyte. It's not like that's ever going to go down. The Unicode consortium is just adding more and more emoji every year.
My thought process -- well, my first thought process was, "Why don't browsers just do this?" I really wish there were just an emoji picker built into browsers. But module that, what's the next best thing?
What I did was, I implemented it in such a way that it just downloads the emoji once. It stuffs it in IndexedDB, which I still love IndexedDB. I used to contribute to PouchDB, which is a JavaScript database, so I learned a few tricks about IndexedDB in those days. I still think it's fabulously underused on the Web.
Chris: It's like a cache, in a way, or whatever, right?
Nolan: Yeah. Yeah, well, it's a cache sort of like similar to local storage or these kinds of things, but it's also a full database. It has everything you would expect in there for indexing and secondary indexes and combined indexes. You can do all this fancy database stuff you might do with something like SQLite. You can just do it right here in the browser.
The emoji picker that I wrote just downloads the emoji once, caches it in IndexedDB, and then uses IndexedDB as the source of truth, so it's extremely low memory. I did a test in Chrome where I found that emoji-picker-element actually uses less memory than just loading the JSON object from another emoji picker.
Dave: Wow.
Chris: It's pretty cool. Again, it's just a couple of lines of code to use this thing. You'd maybe write a couple extra lines of code for what happens when an emoji gets picked or something, but if I'm writing my own personal notetaking app and I want to incorporate an emoji picker, this would be a tremendously good option because it doesn't matter what framework I'm writing in (if any at all). In a couple of lines of code, now I have an emoji picker.
Let's say my notetaking app is called Forest. Okay? It's all greens. Everything is some kind of shade of green, like you open Discord or whatever and it's just Graysville - everything is gray. Mine is different. It's green.
Then I use the emoji-picker-element, I open it up, and it's got this white background. It's like, oh, jarring! Oh, no! I want to be able to style this thing.
Enter how do you (the author of emoji-picker-element) offer styling to me (author of Forest, notetaking app) to make this thing green so it doesn't clash in my app? I guess that's kind of where this -- that's a different setup than your blog post is, but it's similar, right?
Nolan: Yeah. Yeah, so that's where we get into styling a custom element. Throwing aside all the technical implementation details of this thing, I decided to expose it as a Web component, as a custom element. Yeah, it wasn't obvious to me how to let people style that thing. I knew that people would probably want to make the emoji a little bit bigger or change how many columns there are in the grid or all these different things. That led me to writing this blog post where I kind of enumerate all these different ways that you can style a Web component and sort of the strengths and weaknesses of each one.
Chris: Yeah, so this is tough. People out there, this is interesting too. It's extremely interesting and you should just read the post, but this is a podcast. We're going to have to do some mouth blogging here, I'm afraid. [Laughter]
There are a bunch of options, so it wasn't obvious to you. I don't think it's obvious to anybody. That's as far as I'll go with Web components.
Dave: I'm actually in the process of turning the podcast player we have on the ShopTalk site into a Web component, like open-source. This was very timely because I opened a PR and I was basically like, "I don't know how to offer styling or custom icons or things like that." Your post is just spot on because it just is like, "Here are kind of the four options and none of them are great."
Chris: [Laughter]
Dave: [Laughter] Maybe I'm spoiling that, but you're just kind of like, "Here are the four options," which, I like Web components. I want to use them. I use them, but the styling story is just brutal, I feel.
Chris: We should set up the idea that Web components, generally -- I guess it's always -- maybe that's a strong word but have a Shadow DOM in them. I think it might be optional but, generally, the point of using a Web component is that you get the Shadow DOM. I think that's a strong reason why you would reach for them at all. That's because no styling in, no styling out; a little bit like an iframe, if you can picture that. It's uniquely cool.
The Shadow DOM is a good thing. It makes it so that Nolan can make an emoji-picker-element. You can use it on your app and not have it be subject to the wilderness of CSS on some other site. It's going to look right. if you use Nolan's thing, it's going to look right. But you still need to be able to reach in there and make changes.
Option number one, Nolan (while styling this thing) could have just used CSS custom properties all over the place. Could have just been like, how many columns? It's going to be eight, but I'm going to make that custom property that eight; number of columns, eight.
What's the background color? I'm going to make it background color white because I'm going to set that. Then just through doing that, there's kind of a styling API, right?
Nolan: Yeah. The four main options I came up with, I'll just quickly go through them and then I want to go deeper into what you're talking about with custom properties.
Chris: We'll circle back. Yeah. Okay.
Nolan: The first one is custom properties or CSS variables - whatever you want to call them. The second is classes or, really, attributes - either one. The third is this new CSS shadow parts spec that has now landed in most modern browsers. Then the fourth one is, just do whatever you want (DIY) where it's kind of like a hack or a loophole. But it's kind of a nice last resort.
The first one, custom properties, that was the one I kind of settled on. At the time I first wrote this emoji picker, shadow parts hadn't landed in all the browsers and I just kind of looked around the landscape and I saw that custom properties worked really well, and so that was kind of the main thing I landed on.
The basic insight is exactly what you said that even though Shadow DOM is sort of like an iframe, like you have your little self-contained element and, in general, no styles bleed in, no styles get out. Custom properties are kind of this big exception where you can just declare --background white at the root and that will eventually get down into the Web components.
Chris: Why that works is anybody's guess to me. Why did they decide that CSS custom properties could just penetrate the Shadow DOM? [Laughter] I could have seen it the exact opposite way, but whatever.
Nolan: My guess is because they're inheritable. There's this other sort of loophole that it sometimes surprises people about Shadow DOM, which is that any inheritable properties, so in CSS this would be font size, color, or pitch.
Chris: Color definitely is inherited.
Nolan: Yeah, and anything that automatically passes from the parent to the child element, those also pierce into the Shadow DOM, which I actually find really useful. Some people don't like it and maybe they'd want to reset it, but I actually kind of like that. I guess custom properties work the same way.
Chris: Yeah, kinda. Now I'm feeling like a lack of knowledge here a little bit. Font family is also inherited, but it's a tricky story, right? You'd think, well, it's inherited; then why does my button not inherit it? Well, it's inherited, but the user agent style sheet has the font family on it, so that's winning. And so, that's just confusing to know.
But if I had a Shadow DOM and all inside of it was just a P-element, like a paragraph of text, and I set the font family on the body, are you saying that would inherit through the Shadow DOM and that paragraph would be the font family? I said outside of--
Nolan: Yes, absolutely. That is the way it works. That was actually something I discovered. It kind of delighted me because I was thinking to myself, what if emoji picker is in a page that has serif versus san serif fonts? Do I have to give people a way to style that?
Dave: Fantasy.
Chris: Yeah.
Nolan: Yeah. You don't.
Dave: Pyrus.
Nolan: Yeah.
Dave: Yeah.
Chris: Well, that's good. That's a little news to me. I've got to bone up on this stuff. But that's good. When I say no styles leak in or out, that's not true. Things that are inherited do leak in, just like anything else does, I guess. But if you were to write, like, body background red--inside your Web component in the Shadow DOM--that's not going to make the body background red outside of it. That is prevented, that particular type of leak.
Nolan: Yeah, definitely. Nothing can reach out of the Shadow DOM at all. Inside your Web component, whatever you do in there, you know it's not going to affect the page outside - period.
Chris: You went custom properties and, just through usage of them, you know that those are inherited and, thus, kind of pierce the Shadow DOM, as it were. Then it's on you as an author. Anything that somebody who is using this emoji picker might want to set, you better make sure to make it use a custom property because, if it doesn't then they're stuck. It's on you to craft that API.
Nolan: Yeah, that's true, but I actually found that I like that because I like custom properties and I tend to use them anyway. I like to use variables and define something like --emoji size, --emoji padding. Then maybe I'll do some fancy calc or something to actually make that real.
Chris: Yeah. Yeah. But the other day, I just got a whole bunch of variables. Then just exposing those to someone else outside of my Web component, it just felt really natural.
Now, the one thing that I ran into that's kind of interesting is, you might think to yourself, "Okay, well, that's great, Nolan. If I've only got one custom element in my entire Web app, I can just put --background in the root of my entire Web app and know that that will affect your emoji picker," but I don't want every Web component in the world to be doing that, so I was trying to think--
Chris: Namespace them or something. Yeah.
Nolan: That's where it gets interesting, yeah, so what some people do is they will prefix it. I could have prefixed --emoji picker-whatever, but I actually chose a different option that I thought was really nice that I discovered from, actually, the Ionic folks are doing this, which is that there's this kind of weird mind-bending hack, which is that if you tell your consumers of your Web components, "Hey, don't put your --background at the root of your entire Web app. Just select my element. Just be like emoji picker, open curly bracket, and then put in --background white or whatever you want," then that will only affect that Web component and you don't need any prefixes or namespacing or anything.
The way it works is this weird kind of hack where, inside of the Shadow DOM, you can target the host with :host, that pseudo-selector. That is kind of reaching out a little bit to its container inside of the Light DOM.
Chris: Hmm. Yep.
Nolan: But that has lower specificity in CSS than the user of that Web component, defining whatever they want on the component, and so it overrides. As an author of a Web component, you can set a default inside the Shadow DOM and then let people override it by targeting your element.
Dave: Wow! Okay. Interesting.
Chris: I like that. Don't you? It seems like the way to go. These variables have no meaning at all outside of the component, so to teach people, "Don't set them too high up. Set them on the component."
Dave: No, I like that because you're like, "Here, laser into the component if you need to change it." It's all if you need to change it. Hopefully, your default styles are decent, but yeah. This is interesting. How far do you go, though? How many custom levers do you offer in emoji?
Chris: Well, that's the thing, isn't it? To me, I think this is perfectly valid. Super cool. If Nolan likes this for the emoji, for this particular component, that's great. More power to everybody that wants to craft this API.
You remember custom properties are properties, or they're the values to properties, really, because you can't even use it for a selector, right? It's limited in what you can do. It's not just giving somebody a hook like, "Hey, style this part of it however you want to." It's offering a very specific, like, "I have opened up this whole for you because you can style this but nothing else." That's a choice that every component has to make, but I don't think that it's fair that that choice is good enough for every Web component in the world.
Nolan: Yeah, well, and that's where we get to maybe the third option, which you can contrast with it, which is the shadow parts, which the idea of shadow parts is basically that when you expose a Web component, you can designate parts of it and then let people style that somewhat directly. For instance, take if you have a custom select element or something. You might define the parts of those as being like the button that triggers the dropdown of a list box.
Chris: Sure.
Nolan: Another part would be the list box, the list of things you can click on. Another would be the options inside of that thing. You could give a part for each one of those and then someone could just do fancy-select::part, I think it is, open parenthesis, list box.
Chris: The name....
Nolan: Yeah.
Chris: Yeah. That's fascinating because it opens up a little bit more than custom properties do because now you're not limited to just one value of one property. It's like you're styling one selector and you can do anything you want to it because it just opens up any key values you want to throw at it, which is pretty cool. It's still on you then, the author, to say, "This part is something I want you to style. This part I want you to style. This part I want you to style, but not this one." You have to name every single part that you want to be stylable, which seems okay, but it feels--
The part that feels weird to me about this is, like, you're reaching in. You're styling that one thing. But you can't -- like, let's say you had a header with an H1 in it. Header space H1 is how you'd target that in CSS. You can't give the name. You'd have to give a name to the header and to the H1. You can't reach for the part header and then say, "space H1," and target the H1 inside of it.
There's no descendants traversal at all, which seems so bizarre to me. It's like if you're reaching in, just reach in. [Laughter] I feel I may be alone in this. This is just me talking. I don't think....
Dave: No, I'm with you. Hey, you used the magic word "part," and now you have total control over styling the component. I don't really care. You know that would be nice. I don't know. Yeah, it doesn't always work like that.
Chris: Yeah, I think, in both of these options -- Nolan, you set it up perfectly. I think that's -- I don't know -- just a good blog post. [Laughter] I'll say that. In both of these options, as the author, it's your job. You'll probably use your readme.md file in your repo to explain what the styling scoop is on this particular Web component. It's like on you not only to author it and ship it and maintain it that way, but to explain it to people, right? You have to document it.
Nolan: Yeah. That's sort of the strength and weaknesses of all of these, I think, in general. I think, inherently here, there's a tradeoff between how much customizability and expressiveness you offer to the consumers of your Web component versus how much you kind of lock things down and offer a well-defined API where things don't change from release to release. That was one of the issues I had with shadow parts and where I kind of shied away from it is that I felt like it kind of exposed some implementation details in a way that kind of made me uncomfortable.
As soon as you start attaching the parts to those elements, people can go hog wild and can add anything to those. It can mess with the display or position or ... the margin.
Chris: Mm-hmm. Yeah.
Nolan: What if I decide the next release I want to put a wrapper element around that because I want to do something--?
Chris: Too bad. Can't do it. [Laughter]
Nolan: Yeah, well, I can do it, but then I'd break people.
Chris: Yeah.
Nolan: But then on the other hand, for a long time, before Shadow DOM, people would kind of make Web components or component-esque things, and it was kind of expected you could reach in and do whatever you wanted with them, right? It was kind of like, "You broke it, you bought it." If you did an upgrade and your custom styles didn't work anymore, it kind of came with the territory. It's tradeoffs, I think.
[Banjo music starts]
Chris: This episode of ShopTalk Show is brought to you in part by Linode. Linode is great. They've been around forever. One of the first companies in cloud computing, three years before even AWS was a thing. They are their own beast, in a way.
I think this is a pertinent way to describe it. If it runs on Linux, it runs on Linode. You can do anything. These are like cloud computers that you buy to do anything you want.
For example, let's say you're a gamer. They have all kinds of one-click apps that are gaming-related like, do you want to deploy your own Minecraft server, Counterstrike, or whatever? You just click a button and spin it up on your Linode server. That's kind of cool. It's probably mostly for Web stuff.
It's also notable that Linode is a step past entry-level hosting, I'd say. If you want to be, like, super in control of everything, actually own every detail of your hosting, Linode is a step up to totally customizable cloud computing.
It's VPN friendly, all that stuff. Obviously, they have great human beings that are going to support you in what you're trying to do. They've got GPU hosting if what you're trying to do is machine-learning-based, that kind of thing.
Again, if cloud computing is what you need, if it runs on Linux, it runs on Linode. Go to the link in the show notes. They have a special page just for ShopTalk Show listeners where you get $100 of free credit, which is pretty generous of them, I think. Try that out. Thanks for the support.
[Banjo music stops]
Chris: We skipped past option number two. What's that one? It's kind of like, add a class, basically. Right?
Nolan: Oh, yeah, so that one is super interesting to me. It's a little bit different from the other ones because I think it's better for doing kind of theming or variants. I ended up using it for dark mode. The idea is that when you're using the emoji picker, you can do emoji picker class equals dark or class equals light. Then it will just go into dark mode or light mode.
The way this works is that, inside of the component, you can target that colon host pseudo selector that corresponds to your custom element out there on the Light DOM. You can also pass in stuff to that, so you can pass in classes, you can pass in attributes, and you can key off of that and change your styles internally. I really like that one. I think either attributes or classes are fine there. I think I've seen some frameworks out there using attributes for this, but I think it's nice for theming, in particular.
Dave: Oh, I think of video players. Light and dark theme are pretty obvious. There's small, medium, big. But video players, you know, sometimes it's like, "New, radical, retro, YouTube."
Chris: Mm-hmm.
Dave: You have these kind of fixed set of themes that it supports. If those don't work for you, well, cool. You just need to roll your own or something like that. Yeah. No, I think that's an interesting way to approach it.
Chris: It's in the same boat. It's like you've got to document that. The reason I'm dwelling on that a little bit is because it feels a little new that you'd have to do that. I feel like, in the day you could ship a jQuery slider or something, and you don't have to be like, "Here's the HTML I used," because you just look and you see the HTML that you used. If you want to style it, you just style it.
I don't know if that was good or bad because they weren't protected in the Shadow DOM and were maybe less generally useful because you couldn't just plop them in anywhere you wanted. It had these very specific dependencies.
I just mean that it feels like HTML and CSS and the DOM are kind of the self-documenting styling API already. That's what CSS is. IT's a styling API. Shouldn't I be able to just look in the Shadow DOM and be like, "Look. This is what this author did." I'm choosing to intentionally write some syntax that reaches in there to style it. I feel like that should be one of the options. I don't think any of the other options are bad, but an option, to me, should be, "Reach in. Do work."
Dave: You feel like with Shadow DOM specifically or Web components kind of break the contract or the expectation?
Chris: Yeah, I feel--
Dave: Your styling.
Chris: I feel slighted. I feel like I should be able to do that, but Nolan writes option four kind of is that, in a way.
Nolan: Yeah.
Chris: Just go in there. [Laughter]
Nolan: Yeah, it's option four. I call it the escape hatch. I like having it there because it's kind of like if none of these styling APIs that I expose as component author satisfy you, you can always just use this one, which is that, in open Shadow DOM, at least, there are two types of Shadow DOMs. There is open and closed. As far as I can tell, most people are using open.
But, in open Shadow DOM, there is nothing to stop you from just grabbing that element with JavaScript and doing element.shadowroot. Then, once you get that, you just have the keys to kingdom, and you can inject style tags and do whatever you want. You could just inject a style tag in there that then applies inside of the Shadow DOM.
Chris: Right. I chuck a style tag in there and now, like you said, I have keys to the kingdom. I really can put any CSS in there and it will apply as I expect it to. I kind of like it. Ergonomically, it's not beautiful, but at least I'm not hampered by anything else. I have what I want. I can write styling until my heart is content.
I don't know. As a CSS person like me, that feels right. It feels like that's what I want to do. [Laughter] But that's funny, so that's the case. That's four options. I don't even know. I'm sure, if we really stretched, we could find more ways to style Web components.
Nolan: [Laughter]
Dave: Couldn't you have slot ID style, or something, and pass a style block in a slot or something, because that's another part of Web components?
Chris: Oh, a slot. Yeah, you could do that.
Dave: You're writing Light DOM inside the emoji picker, so open emoji picker brackets and then style tag. Go buck wild. Change things.
Chris: It feels a little dangerous because that slot would apply, like, before the Java. It would apply that CSS then to the parent page, too, temporarily.
Dave: Oh, well, yeah, maybe. Yeah, I guess you could host or something.
Nolan: I think that actually doesn't work because I think slots render to the Light DOM.
Dave: Ah! Nards.
Nolan: They already thought of that. They cut you off at the pass.
Dave: I'm going to blame Alex Russel.
Nolan: [Laughter]
Chris: Funky monkey, but you could still enter HTML, grab the contents of it, and apply it inside if you wanted to somehow, you know. It'd probably three lines of code.
Nolan: Yeah. Yeah.
Chris: This is fun, but I wouldn't blame anybody who is looking at Web components as, like, where is the docs on how to style it? [Laughter]
Nolan: [Laughter]
Chris: Like, well, there isn't any because it's weird. There's something that I totally don't understand. Maybe you do. If you do or don't, let me know, Nolan. This thing called constructible style sheets, have you followed that little saga at all?
Nolan: Yeah, I have looked into that. I think it doesn't -- it neither solves the problems that we've been talking about nor does it make them worse. It's just a different way to work with style sheets in Web components and the DOM as a whole.
Chris: That makes me sad.
Dave: Are you telling me--? Are you telling me they're standardizing something that doesn't quite solve the problem?
Nolan: [Laughter]
Dave: Just one more question. It's me being Web standards Columbo.
Chris: You actually went with the CSS custom properties, right? That's what you actually shipped?
Nolan: Yeah, that's the main one I went with because me, as a component author, really just as a library author, a package author, I always tend towards wanting to define a very strict API that does one thing and has one happy path and doesn't let you go hog wild. I don't like to expose a lot of the private implementation details.
For me, having the custom properties was really the sweet spot because I could just have these custom properties that do exactly what I want it to do (--emoji-padding--numb-columns) - whatever. Internally, I can express that however I want with CSS Grid or whatever. Yeah, but it kind of fits my mode of thinking.
Chris: I agree. I think it's nice. I think that's what I would pick, too, generally, or if I was in your case here specifically.
Dave: You had mentioned in your post; this is what you all are using on Lightning Web components, the Salesforce Web component library. Is that right?
Nolan: Yeah. Yeah, yeah, so I work at Salesforce. I'm actually on the performance team, but I'm on the performance team of what we call UI Platform, which is basically, yeah, we build JavaScript framework that runs on Salesforce. It's called Lightning. We build this thing called Lightning Web components.
Yes, one of the main strategies, if you go and inspect a Lightning page, is that there are custom properties that are like --lwc-something.
Dave: Spacing small. I'm looking at one. Spacing small.
Nolan: Yeah, and so I think, in that case, the prefixing makes a lot of sense because it's a design system. At the core, there are these design tokens, things that say, like, what the width of icons should be or what the font sizes should be - all these kinds of things. You want it to be consistent across all the components in an app, right? In that case, you want to define it at the root and have it go and trickle down into every single component everywhere and be consistent. In that case, you can't use the ionic trick I was talking about where you define or you just target that exact Web component. I think, in that case, having a prefix makes a lot of sense.
Chris: Mm-hmm.
Dave: Hmm. Very interesting.
Chris: You're saying your Mastodon client is in Svelte?
Nolan: Yes.
Chris: If you want to use this emoji picker in Svelte, is there any complication at all? Let's say my thing is in React. Will I have any? What's the story of these kind of raw Web components and outside frameworks?
Nolan: That's a really interesting question. The way I designed it, I used Svelte because it compiles down to be really, really tiny, and I didn't want most people to even know that implementation detail or care about it. If you use the emoji picker, you can use it inside of a Vue app, a React app, or whatever you want.
If you use it inside of a Svelte app, actually, you're just going to be using it as if it were a custom element. There's no Svelte-y API surface that it's exposing. I actually kind of like that way of thinking.
Litelement is doing this as well where you can have Lit components inside of your React app or your Vue app. Even for things like React, like I mentioned earlier, you can wrap React components in a Web component. There's a tool. They're called Remount that'll do this.
I like to think of Web components as being this sort of glue layer, sort of like a container structure, sort of like docker containers, or just shipping containers. You want to give this one universal format where you can just package something up, hand it off to someone, and they can use it. It works basically the same everywhere and you don't have to know about the gnarly implementation details. That's kind of the way I think about it.
Chris: There's almost nothing you'd have to think about, or there isn't in Svelte. I don't know if there is in React. I feel like there was that was a talking point for a while is that "Your Web component might not work how you expect it does in your React app. It's not that we don't support it. It's just that it's weird." I kind of forget why it was weird. But it should be okay now. I don't know. I can't imagine what's so wrong about it, right? It renders emoji picker on the page. I don't know what could go wrong.
Nolan: Well, yeah. I've actually seen there was a React app out there that was using my emoji picker, so it is totally doable. Yeah.
Dave: Oh, well, I don't mean to -- does it work the other way? I know you can use the Web component in a Svelte app. I know Svelte can output Web components, technically - or whatever. But is that a process? Does that actually work? That's kind of my question. Can you then output a Svelte thing as a Web component and then just use it in regular HTML like an 11ty site or something?
Nolan: Yeah, that's exactly what I'm doing, actually.
Chris: Oh, that's cool.
Nolan: Yeah.
Dave: Okay.
Nolan: Now, I think what Chris was getting at is that there are some complications when you're using a Web component inside of a framework. There's a great site--I think someone at Google put it together--called Custom Elements Everywhere, which is basically kind of a set of benchmarks. It's kind like the HTML5 test of JavaScript frameworks. It basically tells you, "Hey, if you're using this JavaScript framework, can you just grab a Web component, just plop on your page, use it in a framework compatible way, and does it feel ergonomic with that framework?"
Chris: Right.
Nolan: What it's not saying is -- by the way, React gets kind of a bad score on this test, but that doesn't mean that you shouldn't use Web components with React. It just means you might have to get your hands dirty with using refs in React rather than doing things in kind of an ergonomic, React-y way. For instance, with handling events or passing props - things like that.
Chris: When I think of React, I think, well, it's in charge of re-rendering things and such. I have no control over this. React might just decide that it's going to re-render some area of the app because some prop changed, or something. That means my Web component is going to re-render too, which means its own little unmount (or whatever it is) is going to run and all that. It may not just be -- maybe a Web component isn't designed or prepared to just be re-rendered as much as React might do it to it. Is that one of the things you have to think about?
Nolan: I'm not sure about the details about React in particular, but I think what you're talking about is something like, let's say you have a custom element inside of a React app and you have an attribute on it. You're switching that attribute based on some React state, right? From the Web component author's point of view, what's going to happen is you're going to get this callback called Attribute Changed. You can handle that inside of your Web component however you want. You don't have to fully re-render the entire thing unless React (under the hood) is going to remove it from the DOM and add it back into the DOM entirely, which I don't think they would do.
[Banjo music starts]
Chris: I want to remind you that CodePen has pro plans, CodePen being the company that I'm the co-founder of. We don't get a chance to shout it out enough on this show, perhaps, but if you go to codepen.io/pro, it'll take you to a page that explains all the stuff that you get if you're just an individual developer and decide to go pro.
Some of the big ones are privacy. If you're building a Pen, you can flip it to private or turn on a setting that everything you save is just private by default - if that's just how you want to roll.
Something to know about that is that let's say you're working on something. You don't intend for it to be private forever but just for some time. The moment then that you flip it back to public, that's the moment that it enters the streams of socialness on CodePen. It's not based on the creation date. It's the first moment that it went public. That's kind of just a nice thing to know that even if you work on something for a month or something, it's that first flip to public that gets it into those social feeds.
Then there's other stuff like uploading files. We have a very fancy asset hosting system that not just hosts your files, but let's say they're images. You can resize them on the fly and serve them in the automatically correct format for the browser, crop it, and re-upload it. It has all kinds of abilities for dealing with that, not to mention that they're CDN hosted, super-fast, and all that.
That's just two of the features of CodePen PRO, so we'd love your support. It's also mandatory, so sign up right now.
[Banjo music stops]
Chris: Okay, so that's interesting. There is stuff to think about and there are resources to look at when you're doing it. Kind of good to know.
Then does it feel like an okay--? You were saying it works -- when I say the other way around, I mean you could, if you wanted to, write a Web component in Vue and then ship it and package up Vue inside of that Web component and use it in a different framework because it's so kind of like isolated internally. That seems weird because--I don't know--I think we're engrained to think, like, you should definitely not ship multiple frameworks on one website. But it's isolated, so is that okay now or something?
Nolan: That's an excellent point. Definitely, I'm a performance guy, so if I didn't address that, I wouldn't be doing my job.
I think that it's totally fine to bundle it up inside. Effectively, that's what I'm doing. Svelte has a runtime, and so I'm basically bundling that runtime inside of the clone and distributing it that way.
But I also offer a mode where you can have a shared Svelte dependency, so you don't have to include Svelte twice. You can do require emoji-picker-element/svelte, and it just gives you the one that doesn't duplicate Svelte, in case you're already using Svelte. In my case, it saves ten kilobytes, so it's not very much.
Chris: I don't know. That seems significant, especially because it's ten kilobytes that has to execute.
Nolan: Oh, yeah. It's on nothing and I could see the strategy becoming more widespread in a world -- you know what I would like to see is, I'd like to see a world where if I want to use an emoji picker, if I want to use a this or a that, I'm not going to NPM and typing in emoji picker Vue or emoji picker Angular or emoji picker React. I think it's silly to rebuild these things over and over and over and over again in every single framework. I think there should just be one good one that exposes a Web component interface and I can use it. I don't care if it's implemented in Vue, React, or whatever.
Chris: Mm-hmm.
Nolan: Now, in this magical future I'm describing, you could imagine a bad performance situation where I have 100 Web components and some of them are using Vue, some are using Angular, and some are using React. It's duplicated every single time.
But if they use this strategy that I mentioned where you have a mode where it can share the framework, then you don't have all this duplication. Yeah, sure, you're loading React, Angular, and Vue together, but not every single Web app--
I'm going to get lambasted for saying this is a performance guy, but not every single Web app out there has to be as fast as possible to get a perfect 100 score on Lighthouse or whatever, like some people are building internal apps.
Dave: You heard it here first, folks. Tell your bosses you don't need 100 on Lighthouse.
Chris: Mm-hmm. Nolan's not--
Dave: A performance engineer said that.
Chris: You're not a stranger to controversy, so. Okay. That's kind of interesting to know. You're also talking to Dave, who is here. Dave, you've been working on this tab stuff. That's the same spirit, isn't it? There should be one good one for tabs.
Dave: Let's just make one good one and then we all rally around it and say, "Please use this one. Go ahead and make your other one, but just use this one." That's what I'm trying, and I've settled on generic components by Pascal.
I have this concern, too, about, okay, if somebody writes this in Lit and somebody writes this in Stencil and somebody writes this in Vaadin or whatever, I'm going to have 15 sneaky frameworks. They're all small because they're all like Web component frameworks (one, two, five K, or something). But if I'm using three elements, that's not a big deal. But if I'm using 15 elements, that's going to add up because it's all kind of bundling or minified into its own little doodad.
Hopefully, it shares code or I can bundle it myself, but it seems like a problem for me. Yeah, I think it gets tricky, too, because if I'm using, let's say, Lightning button or something from Lightning Web components, right? Sure, it's going to come with a little bit of stuff. It may not be nothing. It may be small, but it's going to come with some stuff from the overall Lightning system, whether it's some styles or some behaviors or something from Lightning that I'm just kind of, bit by bit, bloating up my site. That's what I kind of worry about, I guess, in the grand scheme of things.
Granted, it's still one-tenth of a React app, so maybe I'm doing okay. But I don't know. Those are the stuff I kind of worry about. I wish we had less diversity, but I don't know. Web component people are like, "Uh, the more the better," so I don't know. I don't know what to do think.
Nolan: You make a good point. I think it's a little bit situation-dependent. For instance, with the emoji picker, I would expect most people to be lazy loading this thing. It's typically the use case is like you have a text box or something and there's a little smiley face button. You click the smiley face button and that launches the emoji picker, right? You can just wait until that moment to import the emoji picker.
Dave: Mm-hmm. Yeah.
Nolan: You would think of something like WordPress has Gutenberg. Isn't that built on top of React? You can use it on WordPress?
Chris: Kind of. It is, but there's like a native app version of it, too. I think the story is a little complicated but, yeah, React is part of that story.
Nolan: Okay. Well, let's just say a rich text editor or something like that. That's another thing where you can just lazy load it on the client-side. The frameworks are getting better about being smaller. If you don't like React's size, you can use Preact. I think Vue 3 is even smaller than Vue 2. It's probably getting to the point where your biggest bloat is going to be coming from something like MomentJS or your Polyfills or something along those lines. Like I said, situation-dependent.
Dave: Even just your own code, right?
Nolan: [Laughter]
Dave: You just will block the main thread by doing big computation too soon or something.
Nolan: Yeah.
Chris: I like how easy it's getting in JavaScript, too. You could have the smiley face, add in event listener to it of click. That event listener's function can be async. Then inside that, you can do an await import and pull the import of this thing off a CDN. It's just a couple of lines of code. When Nolan says, "Oh, just lazy load it," it's not hard anymore. JavaScript itself kind of helps with that in a big way.
Chris: Are we stoked about top-level 08 now, too? Is everybody excited about that?
Dave: I'm sure I'll break something doing it.
[Laughter]
Dave: A wait while--
[Laughter]
Chris: Oh, wow. Yeah, that might get tricky. It doesn't return a promise, though. I think that would throw.
Dave: Oh, okay.
Chris: Uh... [Laughter] That's cool. Okay, so we talked about the styling of Web components to some degree, but that's just one part of the overall story. There's this whole story of how you mount and use them that Dynode has been interested in for a long time. Then there's like, what about accessibility? You have an even more recent blog post about how focus is handled in Web components or, specifically, the Shadow DOM. What's that all about? Is it just as messy as styling?
Nolan: Yeah, that's another case where I seem to run up against all these hard edges in Web components in the Shadow DOM. I don't know if it's just because--
Dave: Hard edges like styling and focus management. Yeah.
Chris: Yeah. Then you blog it, but other people just throw their hands up and don't do it. Anyway, go on.
Nolan: Yeah. Well, I think it's maybe because I maybe looked too deeply into this kind of stuff. If you stay on the happy path and, like, here's how to do a Hello World, everything is great. But when you look into the details, yeah, it's always harrier than it seems at first. It's always been that way with the Web, right?
Chris: Indeed.
Nolan: But, yeah, in this case, this is a very specific problem, which is that -- I care a lot about accessibility, so another thing I wanted to do with this emoji picker was make it accessible from the start. I actually kind of thought about the semantic structure of the thing before even deciding what DOM elements to use and things like that. It gets really tricky when you have Shadow DOM because of certain cases, especially with keyword management and focus traps.
If you've ever worked with a modal dialog before, an accessible modal dialog, you know that it launches a modal dialog. Now, the focus is trapped inside of that dialog. If I was a user, I'd keep pressing tab. It'll kind of cycle around all the stuff inside of that modal dialog.
Chris: It's very hard to pull off nicely but, yes, that is a requirement of accessibility to do that.
Nolan: Yes, and I don't know how much detail exactly you want to go into this but, basically, there is a bunch of libraries out there on NPM or GitHub that you can find for doing focus traps and focus management and accessible dialogs. Unfortunately, a lot of them just don't take the Shadow DOM into consideration, which means that, in the case of something like emoji picker, you just wouldn't be able to tab into any of the elements inside of the picker. You wouldn't be able to tab through the emoji. You wouldn't be able to tab to the search box or the skin tone dropdown or anything like that.
Basically, I wrote a blog post where I said this is how you should be doing it. You've really got to roll up your sleeves and do this. It's not fun. I hope that browsers fix it someday.
Chris: Yeah. Ideally, we just get inert and we just don't have to deal with that. I don't know if you follow that saga a little bit, but it seems like if you could just put inert on the body and put not inert on the dialog, you've got yourself a focus trap in two lines of code and that is a good thing. But I know what you mean.
Okay, so that's interesting. Of course, they don't. That's funny that the Shadow DOM introduces these challenges. Focus surely not being the only one. I’m sure there are all kinds of libraries that didn't even consider the existence of the Shadow DOM.
Nolan: Oh, yeah. The Shadow DOM just breaks so many Web developer assumptions. It breaks -- I say "breaks" but I'll maybe be a little more gentle about it. It redefines. [Laughter] It redefines some aspects of the DOM.
Dave: Ah!
Nolan: For instance, traversal. Doing queries, document.queryselectorall button, right? You would expect that's going to give you every button on the page. With Shadow DOM it doesn't because that's an internal implementation detail of that component and it's hidden inside the Shadow DOM so you can't get it. There are ways to traverse it when you're using open Shadow DOM, but it is definitely a lot more getting your hands dirty kind of thing. I mean you can just imagine lots of libraries and frameworks written for the pre-Shadow DOM era that didn't take that into consideration and thought they could just use a query selector all and have it work, and it doesn't, which is just unfortunate.
Chris: Mm-hmm. This, I think most people can imagine, an emoji picker where you click a thing and it opens up an emoji picker and then you select an emoji and then an event fires, which you listen for in which to use that emoji somewhere is a pretty JavaScript specific thing, right? The no JavaScript story for an emoji picker is nothing. [Laughter] Basically, right? Is that okay? I'm alluding to your -- I already alluded to the controversy from 2016 where you were like, "It's 2016! Websites. It's okay to build a website where JavaScript is required."
Nolan: [Laughter]
Chris: We don't have to dig all into that, but you lived through it once. Do you think about that type of stuff with this emoji picker, too? Should it have a no JavaScript fallback where it says, "There is supposed to be an emoji picker here," or "Use your browser's emoji picker," or something? Or is like, "Whatever. Just don't show anything"?
Nolan: It's funny. When you said I was no stranger to controversy, I wasn't even thinking of that. I was thinking of when I wrote "Safari is the New IE" back in 2015.
Chris: Wow!
Dave: Hmm.
Nolan: Which-- [laughter]
Dave: Still accurate.
Chris: [Laughter]
Nolan: Maybe a little strongly worded. Yeah, I might have walked it back a little bit since then.
Chris: Where's Flexbox gap, Safari? Okay. Wow.
Nolan: Let's not even get into that because--
Chris: Okay. Let's not.
Nolan: ...the W3C now. Actually, I worked at Microsoft Edge before my current job, so I got more involved in browser standards. I work with folks at Apple and they're lovely people, so that was a long time ago. But, yes, I did also talk about whether websites should work without JavaScript or not and that whole controversy.
I think where I'd land nowadays on that with Web components is, I think it's very situation dependent. Again, like you mentioned, with the emoji picker there really isn't a story of server-side rendering and works with JS. It kind of doesn't really make sense for something like an emoji picker, nor would it for something like a rich text editor or fancy date picker or things like that.
Rich Harris wrote a post a year ago, I think, called "Why I Don't Use Web Components," and he mentioned this problem of not having server-side rendering, not having a way to express Web components completely declarative. He gave the example of, I think, a Twitter, "Share this link," right? Where if you were doing it without Web components, you'd have just a bog-standard anchor element, which you'd then enhance with JavaScript. If the JavaScript didn't load for whatever reason, that anchor element is going to be fine. With Web components, you would have my fancy thing tag, which if the JavaScript didn't load, it wouldn't work.
Chris: Can you just put the anchor tag inside that, though?
Nolan: Well, that's where it gets tricky is that you can put the anchor tag inside if you're using Light DOM, but not Shadow DOM. But that is actually getting fixed now, so there is now a new proposal from folks on the Chrome team for declarative Shadow DOM where you would actually be able to do server-side rendering of the Shadow DOM. That would potentially solve that problem.
Dave: Yeah, but--
Chris: Isn't the template like that, the template tag? It's like you can use that and put a bunch of HTML inside and it doesn't render. There is some precedent for chucking a bunch of HTML on the page that isn't rendered.
Nolan: Yes.
Chris: It's just used later by something else.
Nolan: Yeah, exactly. In fact, this proposal uses the template tag. It reuses it.
Chris: Well, there you go.
Dave: But if you're using the element ten times, like a list or something, you have to use the template tag ten times.
Chris: I mean that's what server-side rendering is.
Dave: It's not my favorite syntax. Yeah, yeah, yeah. I don't know. I think they're not going to win the hearts and minds with that proposal, but that's just me, so I don't know. They solve a problem, but not elegantly. That's my opinion. Hot take. Spicy.
[Laughter]
Dave: I'll fall on the grenade knowing you don't have to.
Nolan: That's fair. Here's the thing. I totally understand all the developer hate towards Web components and all the controversy about it because browsers move slow and Web standards move slow. As soon as something ships, you can already think of ten things wrong with it that you wish they would fix. You look at the discussion for fixing it and the Apple people are arguing with Mozilla people are arguing with Google people, and you're like, "Why can't you just ship something?"
I think where I land on this is I would still bet on Web components. The reason for that is that Web components have a sort of unfair advantage, which is that they're built into the browser.
Chris: Yes.
Nolan: Anything else they're competing with, you're going to have to go through that extra step of loading the thing, reading some documentation. Oh, this was version 2 and now they're on version 3, and everything is broken. You just don't have that with Web standards.
I'm pretty sure that a lot of these problems with Web components in terms of styling, in terms of accessibility, all this kind of stuff, it's all going to shake out eventually. I think it's going to be great.
Chris: I agree. Even if they're bad, which I think there's plenty of bad stuff about them, they're still going to win. They're going to win the long game because they're built in, so they win.
Dave: Just like object. [Laughter] Just kidding.
Chris: [Laughter]
Dave: No. Hey, all right, well, we should probably wrap it up here. Nolan, thank you for coming on the show. For people who aren't following you and giving you money, how can they do that?
Nolan: They don't need to give me any money, but my blog is nolanlawson.com. I'm also on GitHub and Mastodon. There are links to that on my blog. Yeah, subscribe to the RSS. Read my blog.
Dave: You've got a good blog and a good Mastodon instance toot.cafe, so it's good.
Nolan: [Laughter]
Dave: I should spend more time there, to be honest. Yeah, well, thank you so much for coming on the show. I think this has been educational. I guess, if you were to say, is Web components a 2021 JavaScript technology I should be using? Is that a yes or a no?
Nolan: I'd say yes. If you're not using it, you probably are and just don't realize it. Open up the element inspector and you'll probably find a shadow root in there somewhere that you didn't know about.
Dave: Okay. Well, that sums it up. All right. I'm going to say thank you, dear listener, for downloading this in your podcatcher of choice. Be sure to star, heart, favorite it up. That's how people find out about the show. Follow us on Twitter, @ShopTalkShow, for tons of tweets a month.
Check out our Discord, patreon.com/shoptalkshow, I believe. It's still so new, but people are in there, and so it's getting very exciting. Yeah, come on, join in. We'd love to have you.
Chris, do you got anything else you'd like to say?
Chris: Patreon.com/shoptalkshow.