I'm learning web components. When designing a custom element, I have to decide what is going to be hidden in the the shadow DOM. The remainder will then be exposed in the light DOM.
As far as I understand, the APIs allow two extreme use cases with different tradeoffs:
I currently lean toward hiding everything in the shadow DOM for the following reasons:
Am I missing something?
Thank you, I am answered that it depends on the use case which partially answers my question. But I'm still missing an answer regarding the case I'm in: I'd rather not support slots for some of my components.
I'll add an example for each extreme of the spectrum:
<template id=light-email-view>
<div>
<div><slot name=from></slot></div>
<ul><slot name=to></slot></ul>
<h1><slot name=subject></slot></h1>
<div><slot name=content></slot></div>
<ul><slot name=attachements></slot></ul>
<div class=zero-attachment-fallback>no attachments</div>
</div>
</template>
<template id=shadow-email-view>
<div></div>
</template>
<script>
...
let view = document.createElement('shadow-email-view');
// this method renders the email in the shadow DOM entirely
view.renderFromOject(email);
container.appendChild(view);
</script>
In the first example, the component author has more work to do since they need to "parse" the DOM: they have to count attachments to toggle the fallback; basically, any transformation of input that isn't the browser copying an element from the light DOM into the matching shadow DOM slot. Then they need to listen for attribute changes and whatnot. The component user also has more work, they have to insert the right elements into the right slots, some of them non-trivial (the email content may have to be linkified).
In the second example, the component author doesn't need to implement support for instantiating from HTML with slots. But the component user has to instantiate from JS. All the rendering is done in the .renderFromObject
method by the component author. Some additional methods provide hooks to update the view if needed.
One may advocate for a middle ground by having the component offer both slots and JS helpers to fill those. But I don't see the point if the component isn't to be used by HTML authors and that's still more work.
So, is putting everything with the shadow DOM viable or should I provide slots because not doing so isn't standard compliant and my code is going to break on some user agent expecting them (ignoring older UAs that are not at all aware of custom elements)?
@supersharp has nailed it.
One thing I see with Web Components is that people tend to have their component do way too much instead of breaking into smaller components.
Let's consider some native elements:
<form>
there is no shadow DOM and the only thing it does is read values out of its children form elements to be able to do an HTTP GET, POST, etc.
<video>
100% shadowDOM and the only thing it uses the app supplied children for is to define what video will be playing. The user can not adjust any CSS for the shadow children of the <video>
tag. Nor should they be allowed to. The only thing the <video>
tag allows is the ability to hide or show those shadow children. The <audio>
tag does the same thing.
<h1>
to <h6>
No shadow. All this does is set a default font-size and display the children.
The <img>
tag uses shadow children to display the image and the Alt-Text.
Like @supersharp has said the use of shadowDOM is based on the element. I would go further to say that shadowDOM should be a well thought out choice. I will add that you need to remember that these are supposed to be components and not apps.
Yes, you can encapsulate your entire app into one component, but the browsers didn't attempt to do that with Native components. The more specialized you can make your components to more reusable they become.
Avoid adding anything into your Web Components that is not vanilla JS, in other words, do not add any framework code into your components unless you never want to share them with someone that does not use that framework. The components I write are 100% Vanilla JS and no CSS frameworks. And they are used in Angular, React and vue with no changes to the code.
But chose the use of shadowDOM for each component written. And, if you must work in a browser that does not natively support Web Components that you may not want to use shadowDOM at all.
One last thing. If you write a component that does not use shadowDOM but it has CSS then you have to be careful where you place the CSS since your component might be placed into someone else's shadowDOM. If your CSS was placed in the <head>
tag then it will fail inside the other shadowDOM. I use this code to prevent that problem:
function setCss(el, styleEl) {
let comp = (styleEl instanceof DocumentFragment ? styleEl.querySelector('style') : styleEl).getAttribute('component');
if (!comp) {
throw new Error('Your `<style>` tag must set the attribute `component` to the component name. (Like: `<style component="my-element">`)');
}
let doc = document.head; // If shadow DOM isn't supported place the CSS in `<head>`
// istanbul ignore else
if (el.getRootNode) {
doc = el.getRootNode();
// istanbul ignore else
if (doc === document) {
doc = document.head;
}
}
// istanbul ignore else
if (!doc.querySelector(`style[component="${comp}"]`)) {
doc.appendChild(styleEl.cloneNode(true));
}
}
export default setCss;
The choice is 100% dependent on the use case.
Also:
if you want the user to be able to format your custom element with global CSS style attributes, you may opt for the normal, light DOM.
you're right: in the Shadow DOM, "this may be less searchable": the document.querySelector()
method won't inspect the Shadow DOM content.
as a consequence, some third-pary JS library may fail to integrate easily with Shadow DOM
if you intend to use a Custom Element polyfill for legacy browsers, you may avoid Shadow DOM because some of its features cannot be really polyfilled.
in many cases, the answer is to provide a mix of Light DOM and Shadow DOM. As suggested by @JaredSmith:
<slot>
.As a conclusion, you should consider the context in which your Web Component will be used to decide whether Shadow DOM is required or not.
Answer to the Edit
Considering your use case, I would create a custom element and:
<div class="mail-to">
or custom sub-components <mail-to>
as suggested by @Intervalia, this.querySelectorAll('.mail-to')
or this.querySelectorAll('mail-to')
instead of <slot>
to extract data from the light DOM and copy (or move) them to the Shadow DOM.This way users won't have to learn the <slot>
working, and the developer will be able to format the web component rendering with more freedom.
<email-view>
<mail-to>[email protected]</mail-to>
<mail-to>[email protected]</mail-to>
<mail-from>[email protected]</mail-from>
<mail-body>hello world!</mail-body>
<email-view>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With