Skip to content

Async css #3983

Open
Open

Description

@andershol

Allow css files to be marked as async meaning that they will not block rendering. The syntax could be a new attribute on the link element or a new value for "rel" attribute (that already have link types such as "dns-prefetch", "preconnect", "prefetch", and "preload" that also seems to serve a technical, how-to-load purpose), say "<link rel='stylesheet async' type='text/css' href='theme.css'>".

This functionality is similar to the async attribute on the script element, and the font-display descriptor for the @font-face CSS at-rule.

A web search will find quite a few articles about how to do this in a more or less hackish way, so there seems to be a demand for it. It seems that the browsers could rather easily add this in a much more reliable way as the browser e.g. knows if the resource is already in the cache and can control the priority of requests.

(I did read the contributing guidelines, but as I read them, they only talk about submitting pull requests and not about submitting issues)

Activity

  1. sashafirsov commented on Aug 31, 2018

    @sashafirsov

    While the JS-based packaging does not have an issue with loading CSS on demand of JS module( lazy or in run time), IMO having coherent phased load behavior over all resources including script, images, fonts, css, etc has sense to be unified and be a part of HTML standard. So I would extend this feature request to "unify the load behavior for web page resources" instead of just CSS lazy load.

    Also current separation of synchronous/async/delayed behavior does not reflect the need for modern web app. In complex apps the order of load is phased and each phase accounts dependency graph. Extending loading parameters with "depend on" and "load order" is a good addition to proposal.

    All seems to be easy polyfilled for backward compatibility.

  2. andershol commented on Sep 6, 2018

    @andershol
    Author

    There are two related but orthogonal issues here:

    1. What should the browser do before a resource is downloaded? For fonts this might be wait for the font to download before showing anything, show invisible text, or show a fallback font some of which can be selected by font-display (note it does not seem to be possible using font-display to opt-out of the initial wait), for scripts it might be to wait for the script to load before proceeding or just to execute it when it is loaded, for images there is "lowsrc" and one might imagine an option to select how the image should animate on to the image when it is loaded. In short, it depends on the resource type what possibilities it makes sense to choose between.
    2. What priority should the resource have in the load order? This could tie in with the priority and dependency system that both http2 (section 5.3) and quic (section 3.2) seems to already have. It would probably make sense to give page authors a way to indicate a desired load-order instead of relying on the browsers heuristic.

    As an example of why they are orthogonal, consider that it currently seems, that marking a script "async" will give it the lowest priority. But even though a script might not be needed for the first render, it might be desirable to have it load before, say, large decorative images on the page.

    This issue is about (1) for the stylesheet resource type.

  3. noamr commented on Aug 1, 2024

    @noamr
    Contributor

    Perhaps we can add a semantic that <link rel=stylesheet blocking="none"> would make parser-inserted scripts not render-blocking by default

  4. LeaVerou commented on Aug 1, 2024

    @LeaVerou
    Member

    Maybe we can revive this issue?

    Async CSS is pretty essential. It's the reason libraries like Font Awesome have to resort to <script> for their embed code.
    Tons of questions online about CSS async loading, all resorting to JS in one way or another, with the prevailing solution being this:

    <link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">
  5. scottjehl commented on Jan 9, 2025

    @scottjehl
    Contributor

    I still frequently find reasons for loading CSS asynchronously in my work. As @LeaVerou mentioned, the "print media hack" is commonly referenced as a workaround, but its reliance on JavaScript to apply CSS is a major downside.

    The addition of an async attribute for the link element would allow developers to load CSS in a non-render blocking manner without relying on JavaScript. By default, I think this should cause a stylesheet to load at a low fetch priority, but the already standard fetch-priority attribute gives us control to change that when desired.

    I'd love to volunteer my time to consult on specification or implementation work if that's helpful in any way. Thank you!

  6. rajsite commented on Jan 9, 2025

    @rajsite

    <link rel="stylesheet" href="/path/to/my.css" media="print" onload="this.media='all'">

    The addition of an async attribute for the link element would allow developers to load CSS in a non-render blocking manner without relying on JavaScript.

    CSP settings avoiding inline JS also complicate this common workaround. Another win for a native implementation.

  7. noamr commented on Jan 9, 2025

    @noamr
    Contributor

    I'm happy to submit a proposal for blocking=none as per #3983 (comment), but one q:

    Why has putting those link tags at the beginning body not become a practice for this? It seems simple enough and potentially solves the problem. Is that a matter of popularizing it via DevRel/education? Or is it a material issue with this alternative?

  8. scottjehl commented on Jan 10, 2025

    @scottjehl
    Contributor

    @noamr I like how that sounds but I think async feels more intuitive.

    As for the question of in-body links, my understanding is they are sync and block rendering for HTML that follows them. Useful for different contexts but not the same. [update: browser behavior varies a lot here!]

  9. noamr commented on Jan 10, 2025

    @noamr
    Contributor

    @noamr I like how that sounds but I think an attribute that's already in use would be much more intuitive (async).

    blocking is also already in use, you use if to put blocking=render on scripts, or on dynamically inserted link elements.
    But currently it can only be used to add render-blocking and not remove it.

    As for the question of in-body links, my understanding is they are sync and block rendering for HTML that follows them. Useful for different contexts but not the same.

    That's incorrect AFAICT. <link> elements inside the body are always async as per spec and don't have the script-like semantics of blocking parsing.

    We should be clear here with the use of the word sync - fetching of external resources can either block parsing or block rendering.

    • Only classic scripts block parsing.
    • Only elements in the head can block rendering (script, link).

    So the following should just work:

    <head>
       <title>...</title>
      <link rel=stylesheet href="blocking.css">
      <!-- stuff -->
    </head>
    <body>
      <link rel=stylesheet href="non-blocking.css">
      <!-- stuff -->
    </body>
  10. bramus commented on Jan 10, 2025

    @bramus

    That's incorrect AFAICT. <link> elements inside the body are always async as per spec and don't have the script-like semantics of blocking parsing.

    These too are blocking. See https://codepen.io/bramus/pen/NPKYWrX/23a03a80e29b92fa80a41701959d1dba for a demo.

    UPDATE: In Chrome and Safari. Not in Firefox.

  11. noamr commented on Jan 10, 2025

    @noamr
    Contributor

    That's incorrect AFAICT. <link> elements inside the body are always async as per spec and don't have the script-like semantics of blocking parsing.

    These too are blocking. See https://codepen.io/bramus/pen/NPKYWrX/23a03a80e29b92fa80a41701959d1dba for a demo.

    UPDATE: In Chrome and Safari. Not in Firefox.

    Yea I believe this is not per spec. It's a browser heuristic, not sure I like it :)
    Thanks for the clarification @bramus, I got this wrong indeed and was looking too much at the spec.

  12. rik commented on Jan 10, 2025

    @rik

    Yeah, browsers all behave differently. 8 years ago, Jake wrote about it.

  13. noamr commented on Jan 10, 2025

    @noamr
    Contributor

    Anyway, putting the <link> after the elements that don't rely on it and before the ones who do, with a corresponding preload if you must, sounds like an OK-ish practice?

    I'm afraid that <link rel=stylesheet blocking=none> (or async or what not) would lead people in the direction of creating hard to detect FoUCs.

  14. bramus commented on Jan 10, 2025

    @bramus
  15. 43 remaining items

  16. noamr commented on Jan 16, 2025

    @noamr
    Contributor

    Having spoken with @scottjehl offline, I recognize that a valid use case for this is when the elements being styled asynchronously have some kind of default CSS that makes them hidden until the CSS is loaded, which would make them "styled into being visible". This is a legitimate use case because the resulting UX is not a FoUC.

    I wonder if the web platform should enable this use case directly, or if that's enough to justify the simpler-but-footgunny "async" attribute.

  17. added
    agenda+To be discussed at a triage meeting
    on Jan 16, 2025
  18. hsivonen commented on Jan 17, 2025

    @hsivonen
    Member
    <!-- This would load (like preload), but would only apply & block parsing when an element with ID="some-id" is seen -->
    <!-- If the target element is something like an async script, we can keep parsing but block the script execution on the stylesheet --> 
    <link rel=stylesheet href="widget.css" for="some-id">

    How do you intend this to work when the <foo id="some-id"> comes from document.write? (Previously, we've thought it bad for the point at which document.write returns to depend on whether an external script is already in the cache or not.)

    For markup coming from the network, it does not seems as problematic to allow arbitrary elements to block the parser, though it doesn't seem particularly great for performance to have to perform a "does the parser need to be blocked on ID match" on every element", but perhaps it can be optimized well enough by making documents carry a flag indicating whether this feature is in use at all, which would provide for a quick check for all the pages that don't use the proposed feature.

  19. itsdouges commented on Jan 18, 2025

    @itsdouges

    Lazy loading themes would be useful.

  20. noamr commented on Jan 18, 2025

    @noamr
    Contributor

    Lazy loading themes would be useful.

    Sure! How would you envision this working when the user changes theme before the theme's css is loaded?

  21. noamr commented on Jan 27, 2025

    @noamr
    Contributor

    As per the WHATNOT discussion, created a WICG proposal: WICG/proposals#195

  22. removed
    agenda+To be discussed at a triage meeting
    on Jan 29, 2025
  23. scottjehl commented on Jan 29, 2025

    @scottjehl
    Contributor

    A comment that expands on many of the use cases in this issue, as well as why the existing "print media" workaround already does exactly what we need, and only has a few drawbacks that a standard async attribute would address:

    WICG/proposals#195 (comment)

  24. westonruter commented on Jul 9, 2025

    @westonruter

    putting the sheet at the "end of the body" (a) already works (in Chromium and mostly Gecko so far) and (b) hopefully makes them think more about the implications in a more intuitive way.

    It may often work, but there's a problem with moving stylesheets to the footer: it can break the expected CSS cascade. I haven't seen this discussed so far. I'm working on improving performance in WordPress core (e.g. WordPress/performance#2076 (comment)), and one area I've been looking at is deprioritizing styles for blocks that aren't in any initial viewport. For example, there is a "Social Links" block and its stylesheet is quite large, so having it as a blocking stylesheet in the head is problematic when the block only appears in the page footer. Moving it to the end of the body fixes this issue, but it causes the order of CSS rules to change.

    In WordPress, as surely in many CMSes, a site owner can add their own CSS overrides which appear at after other stylesheets have been printed in the head. So they may something like:

    .wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor {
        color: lime;
    }

    This will have the desired style applied when the Social Links stylesheet is printed previously in the head, but this user CSS rule gets unexpectedly overridden when the stylesheet is moved to the body by this core CSS rule:

    .wp-block-social-links .wp-block-social-link.wp-social-link .wp-block-social-link-anchor {
        color: currentColor;
        fill: currentColor;
    }
    Original Stylesheet Moved to body
    Image Image

    This makes it unreliable to move a stylesheet from the default head placement to the end of the body, even though a page analysis may indicate this is better for performance since the bulk of the CSS is not needed until the footer is scrolled into view. It means optimizations cannot be applied automatically without manual testing to ensure the cascade of CSS rules doesn't break. The print media hack does not have this problem since the order of the cascade is preserved (h/t @eric-michel).

  25. noamr commented on Jul 10, 2025

    @noamr
    Contributor

    Note that I'm working on a solution for out-of-order HTML streaming that could solve this problem without introducing a style-specific solution. See https://github.com/WICG/declarative-partial-updates

  26. domenic commented on Aug 21, 2025

    @domenic
    Member

    @nyaxt and I discussed a related topic today, which is:

    • The Chromium implementation has stylesheets block the parser
    • The spec has stylesheets block sync scripts. (And sync scripts block the parser.)
      • So in most real-world cases, where you have code like <link rel="stylesheet"><script src="non-async-non-defer-non-module.js"></script> somewhere at the top of your page, the spec's behavior will be about as blocking as Chromium's.
      • But it's possible to construct pages where the spec blocks less than Chromium, e.g. a page with no blocking scripts, or a page where all the blocking scripts are before the <link rel="stylesheet">s.
    • What web developers seem to care about, and what everyone in this issue is talking about, seems to be render-blocking.

    For the difference between Chromium and the spec, see #1349.

    The difference between blocking scripts and blocking rendering is that if you only block rendering, scripts might execute and see incorrect values from getComputedStyle() or similar. The spec discusses this in the note at https://html.spec.whatwg.org/#interactions-of-styling-and-scripting .

    However, I feel like the spec's current choice is a very conservative default. It might be worthwhile letting pages opt out certain stylesheets from script-blocking, while still keeping them as render-blocking.

  27. scottjehl commented on Aug 21, 2025

    @scottjehl
    Contributor

    What web developers seem to care about, and what everyone in this issue is talking about, seems to be render-blocking.

    Since I’ve been involved in the discussion, I can say that render blocking is the aspect I care most to opt-out of (and I think most folks are looking for the same when they mention async css). Opting out of script blocking as well seems expected, but the former is the bottleneck something like the print-media workaround seeks to avoid.

  28. sashafirsov commented on Aug 26, 2025

    @sashafirsov

    Theproposal of loading CSS lazily would not be complete without

    Freeze css for DOM sub-tree.

    meaning any runtime changes to css via load or otherwise would not be applied to particular section of DOM. In addition to performance gain( I hope there is no need to explain ), it would give assurance that 3rd-party css loaded later would not interfere.

    It sounds quite similar to shadow root insulation except of scope defined by the load time.
    The moment of freeze is when all dependencies resolved.

  29. Velocifyer commented on Oct 11, 2026

    @Velocifyer

    I would suggest one of these attribtues, in order of preference (rather than a new rel, as that will break compatability)

    • async (copied from <script>)
    • blocking=false (reuses existing attribtue)
    • blcoking=never (reuses existing attirbute)

    (yes I do know that I'm breaking spec by using <ul> for a list with meaningful order; I just didn't want for there to be numbers and github blocks CSS in issue comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions