-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Working with custom elements brings challenges that don't exist in the React world with regards to designing our components. This issue uses our breadcrumbs component to illustrate this.
The challenge of shadowdom, slots and Fabric/Tailwind
Consider the following component structure that uses Shadow DOM and slots:
<nav class="flex space-x-8"><slot></slot></nav>Which is to be used like so:
<fabric-breadcrumbs>
<a href="#/url/1">Eiendom</a>
<a href="#/url/2">Bolig til salgs</a>
<a href="#/url/3" aria-current="page">Oslo</a>
</fabric-breadcrumbs>When all is said and done, the nav and slot element are in the shadowdom and therefore subject to any internal styles we use while the a tags are not in shadowdom and will be styled with the rest of the page as usual. The actual fabric-breadcrumbs element too, unlike react, IS an actual element on the page. IE. the component itself is not replaced with the root element (nav) as in the case with React. This means that to get the actual styled effect we want, the styles can't be on the nav element and instead must be on the fabric-breadcrumbs element like so:
<fabric-breadcrumbs class="flex space-x-8">
<a href="#/url/1">Eiendom</a>
<a href="#/url/2">Bolig til salgs</a>
<a href="#/url/3" aria-current="page">Oslo</a>
</fabric-breadcrumbs>Solutions
There are some design challenges implicit in trying to build such a web component to match the React/Vue implementations. There are a plethora of solutions to the challenge presented above, however, all feel like compromises.
Option 1 - Styling or class manipulation from within
One option is to try to style the component internally using special selectors ::slotted could be used to place styles on the a tags and :root could be used to place styles on the fabric-element itself.
It would also be possible to manipulate the classes on the fabric-element from within such that Tailwind could work.
Theres no shadow encapsulation available for the CSS in this approach and it would have to be either inlined style rules or reliance on an external Fabric style sheet. Far from what we are aiming for.
Option 2 - place the child elements within the shadowdom
It is also possible (P.O.C. example attached) to cut the child elements (the a tags) and paste them (using JS) into the shadowdom under the nav thereby subjecting the a tags to the same context as the nav and making internally loaded Tailwind/Fabric available. This makes the flicker effect of WCs worse but they need to be dealt with anyway.
Example implementation (without slots)
Content passed in is moved into a created Shadow DOM and / dividers are interleaved with the provided content.
export class FabricBreadcrumbs extends HTMLElement {
connectedCallback() {
const children = Array.from(this.children)
.map((child) => child.outerHTML)
.join('<span class="select-none" aria-hidden>/</span>');
this.innerHTML = '';
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<link
rel="stylesheet"
type="text/css"
href="https://assets.finn.no/pkg/@finn-no/fabric-css/v0/fabric.min.css"
/>
<nav class="flex space-x-8">${children}</nav>
`;
}
}Option 3. - no shadowdom
Don't use shadowdom at all and let styling happen externally. Straight forward though we'd lose the encapsulation and the Fabric stylesheet would have to be loaded alongside the component for it to work.
Example implementation
No shadow DOM is used at all. Provided children are interleaved with / spans.
export class FabricBreadcrumbs extends HTMLElement {
connectedCallback() {
const children = Array.from(this.children)
.map((child) => child.outerHTML)
.join('<span class="select-none" aria-hidden>/</span>');
this.innerHTML = `<nav class="flex space-x-8">${children}</nav>`;
}
}Option 4. - data passed in as attributes
We could pass data in as attributes. These must be passed a string due to how Web Components work which is very clunky but would I think solve all issues.
eg.
<f-breadcrumbs data='[["Eiendom","#/url/1"],["Bolig til salgs","#/url/2"],["Oslo","#/url/3"]]'></f-breadcrumbs>