#1195: Question: should `shadowrootadoptedstylesheets` perform a fetch?

Visit on Github

Opened Feb 13, 2026

Relevant background: Explainer: https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md TAG Review: https://github.com/w3ctag/design-reviews/issues/1000 Mozilla Standards Position Discussion: https://github.com/mozilla/standards-positions/issues/1081

As currently spec'd, shadowrootadoptedstylesheets on the <template> tag does not perform a fetch. We have received feedback that this is inconsistent and unintuitive for developers. We would like the TAG to consider this proposal and provide guidance on whether this should remain as-is, or be changed to perform a fetch in some manner. If it does perform a fetch, there are several open questions that need to be resolved.

shadowrootadoptedstylesheets accepts a space-separated list of specifiers. Currently, for each specifier, the module map is queried, and if a style module is present with that specifier, it is added to the adoptedStyleSheets array for the declarative shadow DOM that it is associated with.

There are several reasons why it is currently spec'd to not perform a fetch:

  1. The imperative adoptedStyleSheets API does not perform a fetch, and we wanted the attribute to be consistent with the API as much as possible.

  2. If it did perform a fetch, there would be no mechanism to catch errors via onerror (or success via onload), as the <template> tag is discarded after parsing. Also, <template> lacks attributes like CORS, blocking, and more that are necessary for a robust fetching mechanism.

  3. The primary use case we've seen for shadowrootadoptedstylesheets is for declarative modules, which don't need a fetch. Alternatively, we could scope shadowrootadoptedstylesheets to only declarative modules, but that also seems inconsistent.

  4. adoptedStyleSheets is an array, and the order matters for CSS rule application. Each fetch response would trigger an FOUC, and style computation must be rerun for each load completion. Any mutations to adoptedStyleSheets while the fetch is loading would trigger more style changes, but this would match the existing behavior of adoptedStyleSheets.

There are many ways to address this scenario, each with various tradeoffs. A few options:

  1. Leave as-is, and do not perform a fetch.

  2. If not already in the module map, perform a fetch, but with no CORS, error handling or load events, and with an FOUC for each fetched module completion. This can be mitigated with <link rel=modulepreload> ahead of time (and could be warned via the console).

  3. Only allow shadowrootadoptedstylesheets to accept declarative modules. This would avoid this issue, but is limiting and inconsistent. This could also be a temporary limitation.

  4. Create a new type of <link> tag that populates adoptedStyleSheets. This would handle CORS and load/error events, but still has ordering/FOUC issues due to adoptedStyleSheets ordering/mutation and adds DOM mutations to the mix. It will also be very similar to the existing <link rel="modulepreload">, which could be confusing.

TAG guidance on this scenario is greatly appreciated. I would also like to thank @jakearchibald and @LeaVerou for bringing up this concern.

<!-- Content below this is maintained by @w3c-tag-bot -->

Track conversations at https://tag-github-bot.w3.org/gh/w3ctag/design-reviews/1195

Discussions

Log in to see TAG-private discussions.

Comment by @LeaVerou Feb 14, 2026 (See Github)

(Disclaimer: Not TAG currently, but have been for 4y)

On a high-level, I have quite a few reservations about these efforts to extend specifiers to CSS in such a specific, ad hoc way without considering the broader platform implications. I think extending specifiers to other resources and other import sites is a fantastic idea, but we need to at least have a plan for how it will all fit together in the end, otherwise a decade later we will end up with 10 different ways to define and import specifiers, all with subtly different implications and no mental model that justifies their differences in a user-centered way. This is not a bottom up problem that we can solve incrementally and hope the pieces will just happen to fit together in the end.

  1. The imperative adoptedStyleSheets API does not perform a fetch, and we wanted the attribute to be consistent with the API as much as possible.

The imperative API accepts CSSStyleSheet objects, not URLs, and any import statements do trigger a fetch.

Also, while consistency between corresponding JS and HTML/CSS APIs is useful where it makes sense, HTML and CSS are generally reactive languages and thus author expectations are different. In reactive languages, ordering effects are generally avoided: If A && B produce C, which comes first shouldn't matter. This part violates that expectation:

shadowrootadoptedstylesheets accepts a space-separated list of specifiers. Currently, for each specifier, the module map is queried, and if a style module is present with that specifier, it is added to the adoptedStyleSheets array for the declarative shadow DOM that it is associated with.

If a style module with that specifier (or even a specifier mapping) is not present at the time, what happens? Is the module map ever revisited or does the stylesheet just fail to get adopted? Now authors have to learn at what exact point the module map is looked at, and how to make sure their modules have all loaded before that point.

It’s exactly these types of ordering effects that we try to avoid as much as possible when designing HTML and CSS features. I would swear we introduced a design principle on this when I was in the TAG but I can't find it, not even in the issues list. Perhaps @jyasskin or @martinthomson has seen something around it. If I'm hallucinating it, I'm happy to file an issue.

Ordering effects aside, I’m not aware of any precedent of a web platform API that lets authors use an imported module in some way without providing any means to import it. The closest I can think of is import.meta.resolve(), but that just resolves a specifier, rather than doing something with a resource. The examples of <blockquote cite> or Microdata attributes that was mentioned in the other thread are adding metadata, not importing for usage. This is an important difference. Nothing breaks if <blockquote cite> is not fetched. Stuff breaks — badly — if adopted stylesheets are not fetched.

Also, reading the explainer it appears that the attribute accepts both URLs and specifiers, not just specifiers, and this behavior is even weirder with URLs.

  1. If it did perform a fetch, there would be no mechanism to catch errors via onerror (or success via onload), as the <template> tag is discarded after parsing. Also, <template> lacks attributes like CORS, blocking, and more that are necessary for a robust fetching mechanism.

All good points! But to me, these seem like API smells indicating that perhaps the current syntactic approach may not be the best, not reasons that justify the current behavior. Yes, it's good to have error handling. Yes, it's good to be able to set parameters on the request. Ideally, we should try to find a way that allows for these too (even if not MVP). But I would still consider it a top priority to avoid missing stylesheets entirely.

I would note that the goal here is not to avoid a separate element. A separate element may well be warranted! The problem is that the API design of the current feature implies that the stylesheets will be fetched. modulepreload is supposed to be a performance optimization, not something that breaks functionality if not present.

Also, going into the weeds here briefly, but the error event could well fire on the element itself, or even on the <template> before it's removed, so that capturing still catches it.

  1. The primary use case we've seen for shadowrootadoptedstylesheets is for declarative modules, which don't need a fetch. Alternatively, we could scope shadowrootadoptedstylesheets to only declarative modules, but that also seems inconsistent.

It seems inconsistent for the same reason it's problematic to overfit to declarative modules: There is no author-facing reason to scope the feature to declarative modules. It is a perfectly normal user need to want to specify adopted stylesheets for declarative shadow roots. Declarative modules may be a louder use case, but I can see many authors seeing the attribute in documentation and thinking it's a good idea to use it, and fundamentally there is no reason why it shouldn't work. As an exercise, try to explain to the average web author (who — like 99+% of authors — is not involved in standards or browser vendors) why this attribute doesn't work.

  1. adoptedStyleSheets is an array, and the order matters for CSS rule application. Each fetch response would trigger an FOUC, and style computation must be rerun for each load completion. Any mutations to adoptedStyleSheets while the fetch is loading would trigger more style changes, but this would match the existing behavior of adoptedStyleSheets.

It being an array doesn't seem relevant, since the ordering in the array is specified via the URL order, regardless of how long resources take to fetch. Power users would want to avoid too much style recalc, and should have reasonable means to do so. However, we should not conflate performance/rendering speed with basic functionality.

As discussed in the thread, A FOUC is much better than the stylesheet not loading at all. There is no reason to fetch anything or mutate anything if the stylesheet is already fetched, so there seems to be no downside to fetching as a fallback behavior. Authors can always pre-fetch, but it now becomes an optimization, not something that is required to make the basic functionality of the feature work.

  1. Leave as-is, and do not perform a fetch.
  2. If not already in the module map, perform a fetch, but with no CORS, error handling or load events, and with an FOUC for each fetched module completion. This can be mitigated with <link rel=modulepreload> ahead of time (and could be warned via the console).
  3. Only allow shadowrootadoptedstylesheets to accept declarative modules. This would avoid this issue, but is limiting and inconsistent. This could also be a temporary limitation.
  4. Create a new type of <link> tag that populates adoptedStyleSheets. This would handle CORS and load/error events, but still has ordering/FOUC issues due to adoptedStyleSheets ordering/mutation and adds DOM mutations to the mix. It will also be very similar to the existing <link rel="modulepreload">, which could be confusing.

I think 2 is the right way forwards: things still work and modulepreload makes them performant. Everything else seems very surprising. IMO if it looks like it should fetch, it should fetch.

Comment by @KurtCattiSchmidt Feb 17, 2026 (See Github)

Thanks for the input @LeaVerou! A few responses/questions/clarifications:

If a style module with that specifier (or even a specifier mapping) is not present at the time, what happens? Is the module map ever revisited or does the stylesheet just fail to get adopted?

Currently, it fails to get adopted. There is no step to revisit previously parsed items in HTML, and I would prefer not to add one for this feature. I agree that this adds resistance to using this with external files, but that's sort of the point. It doesn't explicitly block external files, but there is extra work required to use them.

There are several other less problematic mechanisms for importing stylesheets in external files, and none currently to import declarative modules, so this does fill a capability gap as-is. I am not opposed to add a less-than-ideal mechanism for importing external CSS files for consistency, but I think it needs to have a good reason. This is where the TAG can really help.

Also, reading the explainer it appears that the attribute accepts both URLs and specifiers, not just specifiers, and this behavior is even weirder with URLs.

It only accepts specifiers, which get mapped to URL's after import map processing (see https://html.spec.whatwg.org/#resolve-a-module-specifier). There isn't anything new with this proposal with regards to URL processing - it behaves exactly as an import would. Can you clarify what's weird here?

Also, going into the weeds here briefly, but the error event could well fire on the element itself, or even on the template before it's removed, so that capturing still catches it.

The issue is that the template element never exists in the DOM with declarative shadow DOM, so there's no element itself, or time frame before it's removed. So this suggestion is not possible with the way declarative shadow DOM behaves.

If we warn that specifier references without an entry in the module map at shadowrootadoptedsytlesheets parse time should always have a corresponding <link rel=modulepreload>, that would mostly address these issues, as it would 1) provide an element to fire load/error elements on (the <link>) and 2) prevent an FOUC via preloading.

It being an array doesn't seem relevant, since the ordering in the array is specified via the URL order, regardless of how long resources take to fetch.

I brought up the array to point out that it's not just a single FOUC like a <link> tag, it's a list of fetches. If you specify a.css, b.css, and c.css, does each fetch complete update adoptedStyleSheets upon each fetch completion or when all are completed? I don't believe there's an existing precedent for an attribute triggering multiple fetches, so I'm not sure how the resolution should behave.

Comment by @jyasskin Feb 19, 2026 (See Github)

I want to preface this by emphasizing that the core use case for this feature is in server-side rendering of web components. @justinfagnani has had to re-explain this multiple times, e.g. at https://github.com/mozilla/standards-positions/issues/1081#issuecomment-3560266264, and I don't want to make him do it again here. The question about how a specifier-based mechanism handles "URLs that aren't already in the module map" is about ensuring that the new mechanism is consistent with the rest of the platform and avoids sharp edges, but it's unlikely that the core use case will run into this part of the feature.

I was chatting with @bkardell, and we both have the sense that "yes, this should fetch", but that raises @KurtCattiSchmidt's questions about how it should fetch. So:

The proposal currently uses <template shadowrootadoptedstylesheets="specifier1 specifier2 ...">, where if one of the specifiers is an unfetched URL, there's no way for developers to control "render-blocking"1 or error handling. If the proposal used the https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/ShadowDOM/explainer.md#using-a-link-tag-to-adopt-stylesheets alternative from the explainer:

<template shadowrootmode="...">
  <link rel="adoptedstylesheet" specifier="specifier1">
  <link rel="adoptedstylesheet" specifier="specifier2">
  ...
</template>

Then developers could use normal <link> features to control render-blocking1 and error handling.

@domenic was worried about this possibliity in https://github.com/whatwg/html/issues/10673#issuecomment-3222362601:

the explainer suggests maybe using a <link rel="adoptedstylesheet" specifier="foo"> syntax. Then you could use sheet="" or media="", and we'd have room for future extensibility, e.g. maybe respecting blocking="".

... I don't like reusing <link>, because right now <link> is always about a hyperlink to the URL specified in the href="" attribute. (There's the slightly strange case of rel="expect", but even for that we tried hard to give it hyperlink-ish semantics, and it uses #foo URLs in the href="" attribute.) This would be the first case where we're basically using <link> to mean "a generic reference to something", and departing from href="".

I feel like the specifier attribute keeps <link>'s quality as a hyperlink to a URL, even though it indirects that URL through any import map. It means the URL isn't always in the href attribute, but what concrete problems is that going to cause?

The rel="adopedstylesheet" or rel="adopt stylesheet" is somewhat orthogonal to the specifier attribute. I couldn't justify the need to put these into the adoptedStylesheets list if sheets can be shared in another way, but I also see that it's useful to let libraries keep the same object structure they're currently using.

@jakearchibald, @keithamus, and others, do you have any sense of whether it would help to define <link specifier> for importing/adopting this kind of sheet?

Footnotes

  1. In this situation, "render-blocking" might mean that the shadow DOM waits to be attached until all dependencies are ready, as opposed to the traditional kind that can't add render-blocking elements after the body has started. 2

Comment by @sorvell Feb 19, 2026 (See Github)

@jakearchibald, @keithamus, and others, do you have any sense of whether it would help to define <link specifier> for importing/adopting this kind of sheet?

I, for one, was about to suggest the exact same thing. It makes sense to me and paves the way to more mappings of html resources to the module map (e.g. /drool inline module scripts).

I think I'd be ok if the shadowrootadoptedstylesheets attribute only took specifiers and using a remote resource meant putting a specifier on <link> before the <template>.

At least Firefox and Chrome have, I believe, the ideal rendering performance with respect to loading and rendering of <link>'s. Placing the link before the template should ensure it's loaded (maybe not in webkit) before the shadowRoot is created and therefore is available. The page can stream and render as needed before that.

One obvious objection to organizing Shadow DOM styles for remote fetching is the granularity mismatch. Ideally you'd bundle a bunch of styles to justify the cost of the request but only apply a subset to any given Shadow DOM. Modern frameworks seem to be landing on a hybrid approach for critical CSS where it's inlined to a certain size threshold before creating a bundle to load/cache. To do this with link we will likely want @sheet, but until then, we can, at the least do this with :host(element-name) { ... }.