Skip to content

Support fragment references in the <link> tag's href attribute #11019

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
KurtCattiSchmidt opened this issue Feb 11, 2025 · 55 comments
Open

Support fragment references in the <link> tag's href attribute #11019

KurtCattiSchmidt opened this issue Feb 11, 2025 · 55 comments
Labels
agenda+ To be discussed at a triage meeting needs incubation Reach out to WHATWG Chat or WICG for help stage: 1 Incubation topic: link topic: style

Comments

@KurtCattiSchmidt
Copy link

KurtCattiSchmidt commented Feb 11, 2025

What is the issue with the HTML Standard?

The problem

There are currently several options for sharing styles with Declarative Shadow DOM, but all of them rely on external files or dataURI's:

  1. <link rel="stylesheet" href="foo.css"> this requires an external file.
  2. <link rel="stylesheet" href="data:text/css;..."> this is not technically an inline style definition, but it doesn't generate a network request, so it's as close as you can get to an inline style today. This must be re-parsed and duplicated in memory for each instance, and the dataURI syntax has poor developer ergonomics.
  3. adoptedStyleSheets via Javascript - using Javascript somewhat defeats the purpose of Declarative Shadow DOM, and this approach still only supports entire files (or a dataURI).

Use cases

  • CSS @sheet - this will enable CSS @sheet to work with inline CSS. CSS @sheet will only work in external CSS files unless there's a mechanism for referencing inline style blocks as mentioned in this proposal. For more details (including examples), see this @sheet explainer.
  • Minimizing time to First Contentful Paint (FCP) metrics - by not relying on external files, inline styles can be parsed once and reused many times
  • Lowering style computation costs with Declarative Shadow DOM - by structuring styles so that a base set of styles can be selectively applied to Declarative Shadow DOM instances, developers can optimize their site's style computation performance and reduce duplicated CSS rules.
  • Custom Elements - Custom Elements often rely on Shadow DOM for ID scoping, but they lose access to most of the light DOM's style information and need to pick a least-bad option from the current solutions listed above.

Proposed Solution

Allow the <link> tag's href attribute to support same-document references to corresponding fragment identifiers for <style> tags.

<style id="inline_styles">
  p { color: blue; }
</style>
<p>Outside Shadow DOM</p>
<template shadowrootmode="open">
  <link rel="stylesheet" href="#inline_styles">
  <p>Inside Shadow DOM</p>
</template>

Prior Art

  • SVG xlink:href syntax is very similar, although it allows cross-document references. For HTML, this might not be desirable.
  • Reference Target expands behavior of Shadow DOM via node ID's
@KurtCattiSchmidt KurtCattiSchmidt changed the title Add sheet attribute to the <link> tag's for CSS @sheet support Support fragment references in the <link> tag's href attribute Feb 11, 2025
@dandclark dandclark added the agenda+ To be discussed at a triage meeting label Feb 12, 2025
@past past added stage: 1 Incubation and removed agenda+ To be discussed at a triage meeting labels Feb 13, 2025
@mayank99
Copy link

mayank99 commented Feb 15, 2025

This feature could tie neatly into "declarative adopted stylesheets" (as an alternative to #10673). Since the whole purpose of adopted stylesheets is to reference the original stylesheet instance, it makes sense to me that a <link> with a fragment reference would use the same mechanism.

Example code using a new rel value:

  <style id="inline_styles">
    p { color: blue; }
  </style>
  <p>Outside Shadow DOM</p>
  <template shadowrootmode="open">
-   <link rel="stylesheet" href="#inline_styles">
+   <link rel="adopted-stylesheet" href="#inline_styles">
    <p>Inside Shadow DOM</p>
  </template>

(This would prepopulate .shadowRoot.adoptedStyleSheets before JS runs.)

Keeping in mind that the "constructed" limitation on adopted stylesheets is likely going to be lifted (w3c/csswg-drafts#10013), does this sound feasible?

Using adopted stylesheets would be more performant and also avoid some of the harder questions such as "what happens when the original stylesheet contents change?" (changes propagate automatically).

This does not yet solve the problem of wanting to "disable" a stylesheet in light DOM, but that's a slightly different use-case, and @scope and @sheet or disabled="" can help with that.

@annevk
Copy link
Member

annevk commented Feb 17, 2025

Can someone remind me why the style element can't be used here?

I also don't think it's a good idea to mix URLs and same-document references due to base URLs and such. It's rather messy.

@KurtCattiSchmidt
Copy link
Author

KurtCattiSchmidt commented Feb 18, 2025

Can someone remind me why the style element can't be used here?

@annevk - do you mean duplicating style tags for every element in a Shadow DOM? This works, but it's not efficient or ergonomic for developers to copy-paste styles (or always rely on a bundler to do this for them), particularly in a Web Components scenario where each element on the page is a Custom Element in its own Shadow DOM. That is the main use case we're trying to solve with this proposed functionality.

I also don't think it's a good idea to mix URLs and same-document references due to base URLs and such. It's rather messy.

I agree that base URL's add some complexity here. This is a great call out. I can think of a few ways to solve this. One option is to use a different attribute than href, which would take base out of the picture and clarify that it's an ID reference and not a URL. Another option is to special-case the behavior of base tags for local references in this scenario, but that option also seems a bit messy.

@KurtCattiSchmidt
Copy link
Author

KurtCattiSchmidt commented Feb 18, 2025

This feature could tie neatly into "declarative adopted stylesheets" (as an alternative to #10673). Since the whole purpose of adopted stylesheets is to reference the original stylesheet instance, it makes sense to me that a <link> with a fragment reference would use the same mechanism.

@mayank99, this could be another good option. I have a few thoughts here:

  1. Is this actually how adoptedStyleSheets works without constructable objects? Or is it making a copy internally? @keithamus worked on this in Firefox and may have some insight.

  2. An adoptedStyleSheets attribute that takes (a list of?) ID's isn't symmetrical with the DOM API for adoptedStyleSheets, which takes an array of objects. I'm not sure how significant of an issue this is.

@robglidden
Copy link

I also don't think it's a good idea to mix URLs and same-document references due to base URLs and such. It's rather messy.

I agree that base URL's add some complexity here. This is a great call out. I can think of a few ways to solve this. One option is to use a different attribute than href, which would take base out of the picture and clarify that it's an ID reference and not a URL. Another option is to special-case the behavior of base tags for local references in this scenario, but that option also seems a bit messy.

When would a base element cause a need in the first place to disambiguate an existing URL use case from a (now-unsupported) reference to a document element?

<base href="http://a-url/" />
<style id="inline_styles">
    /* ... */
</style>
<!-- ... -->
<link rel="stylesheet" href="#inline_styles" />

now just produces an error:

Refused to apply style from 'http://a-url/#inline_styles' 
because its MIME type ('text/html') is not a supported
stylesheet MIME type, and strict MIME checking is enabled.

If there is a need to disambiguate an element reference, perhaps a new link type attribute, say like "element"?:

 <link rel="stylesheet element" href="#inline_styles" />

@noamr
Copy link
Collaborator

noamr commented Feb 20, 2025

I don't think this can work with a new rel, and also it won't solve the issue of @import in older browsers that don't support sheet. Also, this would only work for stylesheet, and this problem would not be solved when we want to use the "import from inline element" feature for scripts.

An alternate proposal for this could be to use a new URL scheme, similar to data: and blob:, that only targets same-document elements, where the path of the URL can walk up the shadow ancestry:

<style id="root-bundle">...</style>
<link rel=stylesheet href="element:root-bundle">

<my-element>
  <template shadowrootmode=open>
    <style id=inner-theme>...</style>
    <link rel=stylesheet="element:/root-bundle">
    <!-- or -->
    <link rel=stylesheet="element:../root-bundle">
    <link rel=stylesheet="element:inner-theme">
  </template>
</my-element>

Regarding mutability, I think this should work the same way as links and imports today and not change their semantics - once the URL is imported, it's immutable and doesn't track changes. To have an imported thing that tracks changes is something that needs to be done with JS, as it's done today.

@robglidden
Copy link

An alternate proposal for this could be to use a new URL scheme, similar to data: and blob:, that only targets same-document elements, where the path of the URL can walk up the shadow ancestry:

<style id="root-bundle">...</style>
<link rel=stylesheet href="element:root-bundle">

<my-element>
  <template shadowrootmode=open>
    <style id=inner-theme>...</style>
    <link rel=stylesheet="element:/root-bundle">
    <!-- or -->
    <link rel=stylesheet="element:../root-bundle">
    <link rel=stylesheet="element:inner-theme">
  </template>
</my-element>

I can see how walking up the shadow ancestry to find a style element in a parent shadow (or if not found there, in the light DOM) could be an often-requested capability.

Perhaps a CSS inheritance-like walk up the shadow ancestry by identically-named style ids would be simpler and less fragile to changes in the DOM layout:

<style id="inline_style">...</style>

<my-container>
  <template shadowrootmode=open>
    <style id="inline_style">...</style>
    <section>
      <my-container-item>
        <template shadowrootmode=open>
          <link rel=stylesheet
            href="#inline_style" />
        </template>
      </my-container-item>
    </section>
  </template>
</my-container>

The first style id of "#inline_style" found walking up the shadow ancestry would be used.

This would be analogous to how CSS inheritance works, but would not require the use of a special URL scheme.

Alternatively, an even simpler, but less capable, approach would be to look only in the current shadow, and if not found then look in the light DOM. After all, in server-side rendering, it might not be that difficult to emit all shared styles in the light DOM.

Any of these ways seem more useful to me than referencing styles in sibling shadows and their parents and ancestors or accessing sheets declared in shadow DOM from light DOM.

I assume shadows in slots would "inherit", i.e. look for, style element references in the light DOM (?).

@noamr
Copy link
Collaborator

noamr commented Feb 27, 2025

An alternate proposal for this could be to use a new URL scheme, similar to data: and blob:, that only targets same-document elements, where the path of the URL can walk up the shadow ancestry:

<style id="root-bundle">...</style> <style id=inner-theme>...</style> I can see how walking up the shadow ancestry to find a style element in a parent shadow (or if not found there, in the light DOM) could be an often-requested capability.

Perhaps a CSS inheritance-like walk up the shadow ancestry by identically-named style ids would be simpler and less fragile to changes in the DOM layout:

<style id="inline_style">...</style> <style id="inline_style">...</style>
The first style id of "#inline_style" found walking up the shadow ancestry would be used.

This would be analogous to how CSS inheritance works, but would not require the use of a special URL scheme.

This wouldn't solve the problem that the new URL scheme tries to address, as in older browsers it would load the entire document and treat it as the stylesheet.

@jakearchibald
Copy link
Contributor

It might be mixing two features in a way that doesn't quite work, but @layer allows blocks of CSS to be named. We could have some syntax to 'import' a particular named layer into a shadow root.

The bit that this and other proposals miss, is preventing the styles applying in the light DOM.

@jakearchibald
Copy link
Contributor

<style>
  @layer(detached) my-styles {
    /* styles */
  }
</style>
<my-container>
  <template shadowrootmode="open">
    <style>@adopt my-styles;</style>
    <!-- markup -->
  </template>
</my-container>

The (detached) part of @layer is just something I made up, so it doesn't apply to the light DOM. @adopt would attach it.

@noamr
Copy link
Collaborator

noamr commented Mar 12, 2025

<style> @layer(detached) my-styles { /* styles */ } </style> <style>@adopt my-styles;</style> The `(detached)` part of `@layer` is just something I made up, so it doesn't apply to the light DOM. `@adopt` would attach it.

There was a lot of CSSWG discussion about whether @sheet should be folded into @layer and it seems like it's going in the direction of having these as separate features. Without getting into the details here, layer is about specificity and has a very different purpose and semantics.

@jakearchibald
Copy link
Contributor

It's sad that @sheet doesn't have the flexibility of @layer that would allow for the above.

@justinfagnani
Copy link

I don't see how this addresses the SSR use case, since it relies on IDs which are scoped. Is there some change to idrefs being proposed too?

This is an example of the case that needs addressing: multiple instances of a component sharing a stylesheet emitted by the first instance appearing in the document:

<my-element>
  <template shadowrootmode="open">
    <style id="inline_styles">
      p { color: blue; }
    </style>
    <p>Inside Shadow DOM</p>
  </template>
</my-element>
<my-element>
  <template shadowrootmode="open">
    <link rel="stylesheet" href="#inline_styles">
    <p>Inside Shadow DOM</p>
  </template>
</my-element>

@justinfagnani
Copy link

@mayank99 very good point about potentially populating adopted stylesheets. It would be ideal for us if we could reconstruct the input to our SSR pipeline. Depending on how the author writes the component, they might use adoptedStyleSheets, inline <style> tags, or both. We would definitely want to declaratively populate adopted stylesheets from markup if the component uses them.

@aluhrs13
Copy link

@justinfagnani Wouldn't the SSR emit the style tag in the light DOM (inside an @sheet and inert to light DOM), then both components reference it through a link? Why would the first component instance be so fundamentally different from the 2nd?

@justinfagnani
Copy link

@aluhrs13 no, in streaming SSR systems we don't know what elements will be emitted ahead of time, since elements can render conditionally. We emit styles along with the element instances, and the change we want to make is just to not repeat styles after we've emitted them once. Emitting all the styles up front is not feasible. The only way we could emit styles in the top-level light DOM is to emit them at the end of the page, which could lead to a massive FOUC.

This is one of the requirements I listed in WICG/webcomponents#939 and I've tried to reiterate this need in every meeting and discussion I've been a part of on this issue. It would be really good to get feedback and validation from the various declarative shadow DOM using SSR system maintainers to see if this proposal would actually solve our use cases. Without cross-scope references to styles, this doesn't for us.

Also, would emitting to the light DOM even work? Presumably you mean the top-level document light DOM, not just any outer scope? Are these idrefs special in that they are always scoped to the document scope no matter if they're in a shadow root, or so that they inherit down the tree of scopes? That would be very different from idref resolution today, and I don't see any discussion in the explainer about changes to idref resolution, other than a section that says IDs are still scoped, which would seem to break the entire proposal.

@robglidden
Copy link

@justinfagnani, @sorvell, re streaming SSR, cross-shadow sharing, and xid:

... This would effectively be the xid solution I talked about in WICG/webcomponents#939.

Agreed.

(At least) 3 parallel, overlapping, potentially converging proposals (WICG #939, WhatWG/HTML #10673 and CSSWG #11509 / this proposal as enabling offshoot) have very similar style-sharing goals and all need a purpose-fit scoping mechanism for referencing a source element.

Questions:

  • Do you think an xid referencing mechanism would work for all three proposals, given that by-analogy existing referencing scoping methods of light-DOM anchor or shadow tree-scoped references might be adequate for some cases but not ideal?

  • Could an entirely new referencing scoping method (i.e. xid) just use the existing id attribute as (for this scoping algorithm only) the unique global id, given the already hard choices of the messy legacy of link, href and src discussed in the Web Platform Design Principles? I note that WhatWG/HTML 10673 already recommends adding a new cross-scope ID xid attribute, and @LeaVerou suggested that if we introduce new syntax, <style src> is overdue.

  • How would an xid reference resolution algorithm handle duplicate ids? GetElementById() "returns first element, in tree order"; shadow "tree-scoped names "inherit" into descendant shadow trees".

<style id="shared_styles">
  p { color: red; }
</style>
<my-container>
  <template shadowrootmode="open">
    <style id="shared_styles">
      p { color: blue; }
    </style>
    <my-element>
      <template shadowrootmode="open">
        <style id="shared_styles">
          p { color: green; }
        </style>
        <link rel="stylesheet" href="#shared_styles" >
        <p>what color am I/</p>
      </template>
    </my-element>
    <link rel="stylesheet" href="#shared_styles" >
    <p>what color am I/</p>
  </template>
</my-container>

@past past removed the agenda+ To be discussed at a triage meeting label Apr 24, 2025
@KurtCattiSchmidt
Copy link
Author

@annevk - I wanted to understand your issue regarding <base> and pushState from #11233

<base> works as expected with <link rel="expect" href="#foo"> in that the <base> URL is incorporated into the fragment, so with the following example:

<base href="https://www.example.com/">
<link rel="expect" href="#foo">

...the expect element being referenced is "https://www.example.com/#foo". You clarified that this works as expected in the discussion, so is the issue that the spec doesn't account for this? Or is the issue only when combined with history.pushState?

Re: pushState - I get why this impacts fragment identifiers for anchor navigation, but I don't understand how it impacts non-navigation scenarios like <link rel="expect" href="#foo"> or this proposal (<link rel="stylesheet" href="#foo">) - can you elaborate?

@noamr - you might have some context on this issue as well

@noamr
Copy link
Collaborator

noamr commented Apr 30, 2025

@annevk - I wanted to understand your issue regarding <base> and pushState from #11233

<base> works as expected with <link rel="expect" href="#foo"> in that the <base> URL is incorporated into the fragment, so with the following example:

<base href="https://www.example.com/">
<link rel="expect" href="#foo">

...the expect element being referenced is "https://www.example.com/#foo". You clarified that this works as expected in the discussion, so is the issue that the spec doesn't account for this? Or is the issue only when combined with history.pushState?

Re: pushState - I get why this impacts fragment identifiers for anchor navigation, but I don't understand how it impacts non-navigation scenarios like <link rel="expect" href="#foo"> or this proposal (<link rel="stylesheet" href="#foo">) - can you elaborate?

@noamr - you might have some context on this issue as well

<link rel=expect> is the first "internal link" type. It has two special properties that make it different from stylesheet:

  • It is used as part of the initial navigation, and is anyway not very useful after pushState
  • It's brand new, and thus doesn't have any behavior in existing browser.

However, I think that we can find a solution to these issues, e.g. by one of the following:

  1. using a new rel to circumvent the backwards compatibility issue
  2. Requiring a type attribute when using an in-document stylesheet
  3. Using a special URL scheme, e.g. inline:id rather than relying on fragment identifiers.

Alternatively we can determine that this backwards compat issue is OK and unsupported browsers would try to load a whole document as a style and then fail to apply it.

@robglidden
Copy link

With Chrome intent to prototype <link> fragment identifier references to inline <style> tags, here are wpt tests for variants of the lrlr-polyfill ("Link Rel Local Reference") in the corresponding HTLML spec pull request.

lrlr-polyfill wpt updates the polyfill to correctly handle multiple style rules.

lrlr-polyfill-nested-shadows wpt updates the polyfill to handle nested shadows.

lrlr-polyfill-find-style-elements-in-shadows wpt updates the polyfill to find style elements both in light DOM and in shadow DOMs, to potentially address if ultimately desireable the streaming ssr use case discussed here.

Also, the example streaming-server.js shows using the polyfill in the streaming SSR use case, i.e. in contrast to classic ssr, using HTTP Transfer-Encoding: chunked to streaming-emit shadow components without prior knowledge of the styles to be used in them.

@robglidden
Copy link

robglidden commented May 5, 2025

Considering the WHATNOT discussion in #11233 about links to links:

Luke: I remember we discussed before; might want to link to other <link> element, do not just inline.

Kurt: Can't do that now, but open to adding that. But what situation does it help with? Conceptually makes sense, but if style tag is in scope too, does that unlock new scenarios? Maybe if media elements don't match?

Luke: In my head, maybe perf stuff, but maybe that's not the case?

lrlr-polyfill-link-to-link wpt here updates the polyfill to use as a resource a link element in addition to a style element.

The benefit of also supporting a link reference to a link element in addition to a style element is that the styles can be loaded from a network resource in addition to a style element, while eliminating the FOUC caused by unspecified and unreliable cache behavior and the duplicated instances that result from multiple link elements all pointing to the same network resource.

For example:

styles.css:

.test-blue {
    color: blue;
}

lrlr-polyfill-link-to-link.html:

<head>
   <link rel="stylesheet" href="styles.css" id="style_tag" />
</head>
<body>
<div>
    <template shadowrootmode="open">
        <link rel="stylesheet" href="#style_tag" />
        <span class="test-blue">Blue text</span>
    </template>
</div>

@css-meeting-bot
Copy link

The CSS Working Group just discussed Support fragment references in the `<link>` tag's `href` attribute.

The full IRC log of that discussion <dandclark> kurt: Working on getting this to stage 2 in whatwg.
<dandclark> ...want to get some of the concerns worked out in joint call
<astearns> related: https://github.com//issues/11286
<dandclark> ...Noam filed https://github.com//issues/11286
<dandclark> ...seems like the biggest blocking issue. Seems to have been resolved in thread
<dandclark> ...spec already covers it
<dandclark> ...are there other concerns?
<dandclark> ...before stage 2?
<dandclark> annevk: Not sure it's resolved. Haven't seen Olli reply
<dandclark> ...I think Domenic thinks current situation is OK. Would be good to discuss with him
<dandclark> ...maybe Noam too
<dandclark> kurt: Makes sense to set up one-off session with them?
<dandclark> annevk: Yes, also good to have Tab, he created the local references thing
<dandclark> ...Treating URL starting with `#` differently
<dandclark> ...Doesn't have the issue of re-fetching the same document
<dandclark> ...Not sure it's actually implemented for CSS
<dandclark> ...seems like a useful thing
<dandclark> ...It's more intentonal. Very clear that it will always be treated as local reference, not affected by stuff like `<base>`
<dandclark> emilio: Gecko imlements local reference concept. I assume other engines do so in a more or less semi-consistent way
<dandclark> ...need to handle that kind of stuff for SVG
<dandclark> annevk: Do you end up fetching even if URL matches but doesn't start with fragment ID?
<dandclark> emilio: Need to double-check that.
<dandclark> annevk: I can make test case
<dandclark> emilio: Should be easy to test
<dandclark> astearns: Having specific session on this with the right people sounds good
<dandclark> kurt: I can set up

@sorvell
Copy link

sorvell commented May 15, 2025

Re-iterating the first concern here:

Doesn't this feature require the shared style to apply to the main document and therefore break Shadow DOM style encapsulation?

<style id="x_foo_styles">
  p { color: blue; }
</style>
<p>Outside Shadow DOM... should not be blue but is?!?</p>
<x-foo>
  <template shadowrootmode="open">
    <link rel="stylesheet" href="#x_foo_styles">
    <p>Inside Shadow DOM</p>
  </template>
</x-foo>

@KurtCattiSchmidt
Copy link
Author

@sorvell - as-is, yes, this feature requires styles to be applied in the main document as well.

However, the @sheet proposal (and associated sheet attribute on the <link> tag) will allow for styles to be defined in the main document, but not necessarily applied there. Shadow roots or the main document can selectively include named sheets.

See @janechu's example in #11019 (comment) for an example of what this would look like.

@trusktr
Copy link

trusktr commented May 15, 2025

The idea Justin has proposed is that sheets could be globally referenceable, therefore that problem could be avoided something like this contrived example (which is not doable for aforementioned reasons):

<p>Outside Shadow DOM... is not blue</p>

<x-foo>
  <template shadowrootmode="open">
    <!-- this style gets written on the first occurrence of x-foo during SSR streaming -->
    <style id="x_foo_styles">
      p { color: blue; }
    </style>

    <link rel="stylesheet" href="#x_foo_styles">
    <p>Inside Shadow DOM is blue</p>
  </template>
</x-foo>


<x-foo>
  <template shadowrootmode="open">
    <!-- SSR streaming does not write the style sheet here on following occurrences. -->
    <link rel="stylesheet" href="#x_foo_styles">
    <p>Inside Shadow DOM is blue</p>
  </template>
</x-foo>

But Justin proposed using a new attribute like gid= instead of link href= if the global behavior would be too different for link href.

TLDR

Unless style link href hash tag will search in ShadowRoots, it is not useful when the styles need to be inside ShadowDOM only, but is only useful when it is ok for the styles to be applied outside of ShadowDOM too.

@KurtCattiSchmidt
Copy link
Author

@smaug---- @tabatkins @noamr @annevk - the discussion from this morning suggested doing a one-off sync with all of us to discuss the remaining issues. Are you all available next week?

I realize the time zones will be a challenge, but I am flexible. I'll start with Tuesday, May 20th at 8AM - does that work for everyone?

@trusktr
Copy link

trusktr commented May 15, 2025

Why add multiple ways to do things? Why not just go with CSS Modules and specifiers as in

In other words, instead of giving end web developers two methods of writing shared styles, why not give them only one? That single method would be the one that cleanly solves streaming SSR at the same time (CSS Modules and specifiers).

Just curious why we need anything else more than that. Maybe someone can explain a good reason to do both.

Or can we update link href so that it can both:

  • reference encapsulated shadow DOM style
  • make the sheet be adopted

? This would make it then a target for serializing JS adoptedStyleSheets.

It could look something like this:

<p>Outside Shadow DOM... is not blue</p>

<x-foo>
  <template shadowrootmode="open">
    <!-- this style gets written on the first occurrence of x-foo during SSR streaming -->
    <style id="x_foo_styles">
      p { color: blue; }
    </style>

    <link rel="stylesheet" href="global:#x_foo_styles" adopted>
    <p>Inside Shadow DOM is blue</p>
  </template>
</x-foo>


<x-foo>
  <template shadowrootmode="open">
    <!-- SSR streaming does not write the style sheet here on following occurrences. -->
    <link rel="stylesheet" href="global:#x_foo_styles" adopted>
    <p>Inside Shadow DOM is blue</p>
  </template>
</x-foo>

However the ideas proposed in this comment seem much more useful overall:

It could solve the needs here and there plus also make other things possible, all via the URL standard (or other types of specifiers if importmaps are involved).

@sorvell
Copy link

sorvell commented May 16, 2025

@KurtCattiSchmidt re #11019 (comment)

However, the @sheet proposal (and associated sheet attribute on the tag) will allow for styles to be defined in the main document, but not necessarily applied there.

Ok that would satisfy the need. IMO, that seems a bit convoluted compared to some of the other options.

Couple more questions.

What happens to media on the referenced style? Is it honored or is the media of the link honored? And would the latter provide an alternative to using @sheet for creating non-global styles?

<style media="not all" id="x_foo_styles">
  p { color: blue; }
</style>
<p>Outside Shadow DOM... should not be blue but is?!?</p>
<x-foo>
  <template shadowrootmode="open">
    <link rel="stylesheet" href="#x_foo_styles">
    <p>Inside Shadow DOM</p>
  </template>
</x-foo>

Would this change apply and if so how? I believe the style element creates a new stylesheet when this happens. Ideally the adoptedstyle is replaced with it, maybe?

x_foo_styles.textContent = `p {color: red;}`;

@justinfagnani
Copy link

@sorvell

Ideally the adoptedstyle is replaced with it

There isn't an adopted stylesheet in this scenario, just a regular link/rel-stylesheet element that happens to reference an internal resource.

@mayank99
Copy link

mayank99 commented May 16, 2025

@justinfagnani

There isn't an adopted stylesheet in this scenario

But what if there was (using a new rel value)? #11019 (comment)

@sorvell
Copy link

sorvell commented May 16, 2025

@justinfagnani

There isn't an adopted stylesheet in this scenario

The question is still relevant in that case. What happens when the referenced style's sheet changes or is replaced?

@justinfagnani
Copy link

justinfagnani commented May 16, 2025

@mayank99

@justinfagnani

There isn't an adopted stylesheet in this scenario

But what if there was (using a new rel value)? #11019 (comment)

Of course, I very much advocate for a way to serialize and deserialize adoptedStyleSheets, but even if a <link> tag could do that, it wouldn't be sufficient if we can't emit a style the first time it's used, and reference it in any other scope it's used from. This feature doesn't do that, so just being able to add to adoptedStyleSheets wouldn't buy a lot.

This goes back to the original requirements I listed in WICG/webcomponents#939.

Requirements:

  1. Ability to serialize a constructed stylesheet to HTML
  2. Styles don't automatically apply to the document or any shadow root
  3. Styles are able to be associated with declarative shadow root instances
  4. To support streaming SSR, the styles must be able to be written with the first scope that uses it and referenced in later scopes.

@KurtCattiSchmidt
Copy link
Author

@sorvell raised an excellent point that I wanted to address:

The question is still relevant in that case. What happens when the referenced style's sheet changes or is replaced?

This is probably the most significant open question in the explainer, and I would definitely appreciate some discussion. As I see it, here are the pros/cons of reference vs copy:

Copy
Pros

  1. Closer to the existing file semantics for <link> tags
  2. Keeps the CSSOM objects isolated from other tree scopes, so shadow roots can modify the contents via CSSOM and not impact other tree scopes

Cons

  1. Potentially more overhead in memory (this can be mitigated with copy-on-write semantics, but if each <link> tag slightly modifies their stylesheets, there will need to be multiple copies in memory)

Reference
Pros

  1. Closer to SVG reference semantics (e.g. <use>)
  2. More efficient internally (always only one instance in memory)

Cons

  1. This is a big one - it would allow shadow roots to alter the styles of other tree scopes. This feels like a potential deal breaker for this approach
  2. Invalidation is more complex (but not drastically so)

@robglidden
Copy link

Concerning this con:

it would allow shadow roots to alter the styles of other tree scopes. This feels like a potential deal breaker for this approach

I don't think I understand this exactly.

Do you mean:

  1. Javascript in a Custom Element could change a source fragment <style> element outside the shadow root (putting the source element outside the shadow root is the point, after all), thereby changing the styles of other tree scopes that point to the same <style> element? So that is bad?

  2. Or when you change the <style> element that multiple <link> elements point to, the changes should or should not be reflected in all the <link> elements that point to that source element (like SVG <use> behaves)?

  3. Or that changing the <link> element in a shadow root that points to a <style> element fragment should or should not change the source <style> element?

As to 1), isn't that just how Javascript works? You can always reach outside the host's shadow tree.

If 2) I'd say yes, the changes should be reflected in all the <link> elements that point to that <style> element. That is how SVG <use> works, and it seems like a reasonable expectation for <link> elements that point to fragment elements as well.

Even though, as discussed here at least as to CSS import, "When the same style sheet is imported or linked to a document in multiple places, user agents must process (or act as though they do) each link as though the link were to an independent style sheet".

To me, that logic doesn't seem to apply to a source <style> fragment element.

As to 3), how would that work? Isn't the sheet property on <link> only readonly now anyway (through <link>'s DOM interface and LinkStyle interface)? And isn't svg <use> similarly readonly?

I'd note that xlink:href, the original inspiration of <use href>, is relevant. Xlink has long recognized that a "link" should have different meanings based on the context, not just whether it is an HTML thing or a CSS thing.

@sorvell
Copy link

sorvell commented May 21, 2025

I don't think I understand this exactly.

If the sheet is shared, then the link inside the shadowRoot's sheet would be the same as the one in the source style element. You can modify stylesheets so modifying the rules in link.sheet would modify the rules in style.sheet.

@justinfagnani
Copy link

justinfagnani commented May 21, 2025

I don't think I understand this exactly.

If the sheet is shared, then the link inside the shadowRoot's sheet would be the same as the one in the source style element. You can modify stylesheets so modifying the rules in link.sheet would modify the rules in style.sheet.

This is how constructible stylesheets work, but not how <link> currently works. Each <link>'s .sheet property is a separate CSSStyleSheet object. I'm not sure that <link> should get new behavior for this just because it uses an internal resource.

I think this feature makes more sense if we stop thinking about it as a way to share stylesheets between declarative shadow roots for SSR, since it does not work for serializing adoptedStyleSheets. We will still need WICG/webcomponents#939 for the SSR use case, and that can and should implement the shared stylesheet semantics. This feature is for some other use case.

@sorvell
Copy link

sorvell commented May 21, 2025

@KurtCattiSchmidt

This is probably the most significant open question in the explainer

To me, this suggests this feature may be the wrong solution for the stated problem.

In adoptedStyleSheets, Shadow DOM already has an explicit mechanism for consuming stylesheets, which can be shared.

It seems like we should endeavor to create a declarative mechanism for using that system. This helps give context for how to solve problems specific to the solution such that they support and mirror the imperative system.

Instead, we're designing a new system with similar, in spirit, capabilities. It has its own unique set of issues and problems, this one possibly thorniest among them. It's a more general system and therefore might warrant new semantics. But, afaict, no one is asking for anything more general than satisfying the declarative Shadow DOM use case.

This said, WICG/webcomponents#939 is perhaps a better starting point?

@KurtCattiSchmidt
Copy link
Author

I don't think I understand this exactly.

If the sheet is shared, then the link inside the shadowRoot's sheet would be the same as the one in the source style element. You can modify stylesheets so modifying the rules in link.sheet would modify the rules in style.sheet.

Yes, this is exactly the concern I was referring to.

@robglidden - some responses:

  1. Agreed, this is how shadow roots work, so I'm not concerned with this.
  2. This is one of my concerns. If the stylesheet is a reference to a shared sheet, we would want <link> tags to get updated whenever the referenced <style> object changes. However, if it's an indepdendent copy of the referenced state at parse time, an update to the referenced <style> object would not get updated. This is what I'm trying to align expectations on. With <link> tags referencing files, this isn't possible today, but this is very common in SVG.
  3. The sheet API is readonly, but that just means that you can't overwrite the sheet property (e.g. you can't set it to undefined or assign to it from another <link> element). It doesn't mean you can't modify the styles - CSSOM API's work on <link> tags referencing external CSS files today.

@robglidden
Copy link

Concerning this con:

it would allow shadow roots to alter the styles of other tree scopes. This feels like a potential deal breaker for this approach

I don't think I understand this exactly.

I guess I was not clear.

What I do not understand is why is this (conceivably allowing 2 ways to reach the <style> element, via the <style> and via the <link>) a potental deal breaker in the first place. Seems like at most a small convenience, but if actually a negative then why not just disallow the second way.

If the sheet is shared, then the link inside the shadowRoot's sheet would be the same as the one in the source style element. You can modify stylesheets so modifying the rules in link.sheet would modify the rules in style.sheet.

Sure, I think my comment assumed that could be an intuitive way for link.sheet to work when pointing to an singleton internal reference rather than an instanced network reference, but why would this be a negative.

I'm not sure that should get new behavior for this just because it uses an internal resource.

Using a <style> element as an internal resource would be a new behavior, so however referenced it would necessarily behave differently than an external resource.

The sheet API is readonly, but that just means that you can't overwrite the sheet property (e.g. you can't set it to undefined or assign to it from another element). It doesn't mean you can't modify the styles - CSSOM API's work on tags referencing external CSS files today.

Sure, as a general computing principle, a pointer is different from the thing pointed to.

But no, that is not how SVG <use> works today. By spec and implementation you can't change the source element from the <use> element that points to it (it is closed). You can only change the source element itself, and that will change all <use> elements that point to it. But I don't see that it would be a big deal if it worked differently and the source could also be reached from the reference, <use> is such a useful capability that Chris Coyier wrote a whole book on it. link.sheet could be null when pointing to an internal resource.

In adoptedStyleSheets, Shadow DOM already has an explicit mechanism for consuming stylesheets, which can be shared.
...
This said, WICG/webcomponents#939 is perhaps a better starting point?

But that adoptStyles approach would apparently also allow 2 ways to reach the <style> element, so why would that be positive there but negative here?

Perhaps a 939 explainer would help, fleshing out how @sheet would still need a referencing mechanism, unpolyfillable, light-DOM/shadow-DOM sharing, duplicate identifiers, and so on.

@aluhrs13
Copy link

@justinfagnani / @sorvell - I'm sending you an email right now to try to find time to make sure that we understand your concerns here. In the meantime - Can we keep this thread focused on additions or changes the proposal at hand, and keep the CSS modules conversations on the threads about it? If someone wants to pursue that while we look down this path, I'm happy to help where I can.

@KurtCattiSchmidt KurtCattiSchmidt added the agenda+ To be discussed at a triage meeting label Jun 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
agenda+ To be discussed at a triage meeting needs incubation Reach out to WHATWG Chat or WICG for help stage: 1 Incubation topic: link topic: style
Development

No branches or pull requests