Styling the internals of custom elements
First published: 2022-11-27
Modified: 2024-11-3
In "Basic styling of custom elements" we learned that styling a custom element is much like styling any other HTML element. At its very root, this is always true, at least of the custom element itself. However, once the custom element begins to have an internal DOM structure of its own, this starts to change.
Here is the code from our previous example alter in a very arbitrary way to include internal DOM:
/* custom-element.js */
import styles from './custom-element.css' with { type: 'css' };
const template = document.createElement("template");
template.innerHTML = /*html*/`
<div>
<slot></slot>
</div>
`;
class CustomElement extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: "open"
});
this.shadowRoot.appendChild(
template.content.cloneNode(true)
);
this.shadowRoot.adoptedStyleSheets = [styles];
}
}
customElements.define('custom-element', CustomElement);
And, our custom element styles updated for this move to internal DOM.
/* custom-element.css */
:host {
display: block;
}
div {
color: red;
border: 1px solid;
padding: 30px;
font-size: 30px;
}
Out of the box, we get almost exactly the same custom element delivery as we had before.
All the way over here, I can hear you saying "But...that's not what we got before!?!" And, you're right, it isn't.
We had previously applied the below styles on top of the default styling of our custom element:
/* site.css */
custom-element {
color: green;
border: none;
padding: 1em 2em;
font-size: 2rem;
}
However, with the move to an internal DOM structure and applying our default custom element styles on those internal DOM elements, we're no longer overriding the defaults but adding styles to work in concert with them. The majority of the previously applied styles require direct or inherited access to an element to apply, so the only one we really see is the additional padding
. We can apply more impactful styles from the outside, however.
/* site.css */
custom-element {
color: green;
border: 3px dotted;
padding: 1em 2em;
}
Which looks like this:
Here we can immediately see how the two lists of style rules are now acting in partnership. What's not yet apparent is how to customize the delivery of the content within the custom element itself. In fact, it's not apparent because as currently structured the custom element that we've developed is specifically preventing the application of external decisions on its internal DOM.
Styling custom element internals (beyond inherited styles) requires some form of contract with the element's author. A number of paths by which this contract can be fulfilled exist, some of them are as follows:
- An agreement that the custom element is the Sistine Chapel of custom elements and should always be delivered 100% as is.
- A choice for you to become the custom element author via a class extension and reregistration.
- The developer surfacing a CSS Custom Property API.
- CSS Parts being made available in the shadow DOM.
- Container query-based "themes" being offered for consumption.
- Stacked slot-based DOM structure for customization with external DOM.
Any one of these could be used individually, and/or various approaches could be used in partnership to bring various levels of nuance to a custom element's style customization API. Look for more information on these, and more, soon.
Editors note: some of the APIs outlined in the examples above are not yet available cross-browser. While these are the target APIs that browsers have agreed to, in order to support cross-browser delivery of this article, live demo code is modified from the example to work fully cross-browser.