1. The @import Method
A <style> element is inserted into the Shadow DOM, and that style imports the external stylesheet.
Code:
class ImportRange extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const node = document.createElement('style');
node.textContent = `@import url("${rangeCssUrl}");`;
shadow.append(node);
shadow.append(createRangeMarkup('@import'));
wireRange(shadow);
}
}
if (!customElements.get('import-range')) {
customElements.define('import-range', ImportRange);
}
Result:
This mirrors the article’s approach: the Shadow DOM receives a style tag whose only job is to import the CSS file. In this standalone page, the “external” file is represented by a generated Blob URL.
2. Fetching CSS with fetch
The component requests the CSS text, creates a style element, and appends that CSS into its Shadow DOM once the request resolves.
Code:
class FetchRange extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.append(createRangeMarkup('fetch'));
wireRange(shadow);
fetch(rangeCssUrl)
.then(response => response.text())
.then(css => {
const node = document.createElement('style');
node.textContent = css;
shadow.append(node);
});
}
}
if (!customElements.get('fetch-range')) {
customElements.define('fetch-range', FetchRange);
}
Result:
The markup can render immediately, then the fetched CSS is applied asynchronously. Move either thumb to see that the component behavior is the same as the first example.
3. Importing as a CSS Module
The modern target is to receive a stylesheet object and apply it to the Shadow DOM through adoptedStyleSheets.
Code:
// In a build or browser environment with CSS module support:
// import styles from './range.css' assert { type: 'css' };
const styles = new CSSStyleSheet();
styles.replaceSync(rangeCssText);
class ModuleRange extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.adoptedStyleSheets = [styles];
shadow.append(createRangeMarkup('CSS module'));
wireRange(shadow);
}
}
if (!customElements.get('module-range')) {
customElements.define('module-range', ModuleRange);
}
Result:
This self-contained demo creates the stylesheet object directly, which demonstrates the same Shadow DOM application step used by CSS module imports: assign a stylesheet to
adoptedStyleSheets.
4. Comparison Summary
Each approach has one main tradeoff: performance, ease of use, or browser compatibility.
Code:
const methods = [
{ name: '@import', performance: false, easy: true, compatible: true },
{ name: 'fetch', performance: true, easy: false, compatible: true },
{ name: 'import', performance: true, easy: true, compatible: false }
];
document.querySelectorAll('[data-filter]').forEach(button => {
button.addEventListener('click', () => {
const key = button.dataset.filter;
const rows = document.querySelectorAll('#comparison tbody tr');
rows.forEach((row, index) => {
row.hidden = key !== 'all' && !methods[index][key];
});
});
});
Result:
| Method | Good Performance | Easy to Use | Good Compatibility |
|---|---|---|---|
| @import | No | Yes | Yes |
| fetch | Yes | No | Yes |
| import | Yes | Yes | No |