Cascade Layering of HTML Linked Style Sheets

Table of Contents


Miriam Suzanne


There are discussion threads on both the WHATWG and CSSWG github repos:


Cascade layers allow CSS authors to create explicit cascade priority tiers, and place style sheets into those tiers using either a block at-rule (@layer) or a condition on the @import rule (layer or layer(layer-name)). The details are defined in CSS Cascading and Inheritance Level 5.

One of the primary use-cases for cascade layering is to manage the priority of third-party CSS (libraries and design systems) in relation to site-specific styles. However, there are many situations where authors do not want to use @import for performance reasons, or cannot use @import because of build tooling. Providing this functionality on the HTML <link> tag would bring it in better alignment with the CSS import functionality.


The motivating use-case is quite specific: a syntax for assigning linked style sheets to a cascade layer.

This should:


This raises issues of compatibility. For some period of time, there will be browsers that do not support the new layering syntax. If those browsers apply the linked styles without applying the appropriate layer rules, the results will be unexpected and unreliable.

Ideally, browsers without support for a layering syntax should not load layered style sheets. With the appropriate tools for support-conditioned links, authors could then choose to load alternative style sheets.

However, that would currently require changes to the media or type attributes. Looking farther out, the WHATWG discussion seems to prefer a separate/new attribute for support conditions, since they are not technically treated as media queries in CSS. The details of that proposal seem to be blocking progress on the less controversial layer attribute proposed below. For that reason, I’ve set aside support conditions as a non-goal for this document.

Authors can polyfill conditional loading as desired, with a small amount of javascript to detect feature support, and alter the existing media attribute as needed.

Proposed solution: the layer attribute

The layer attribute applies to both link and style elements, with the following behaviors:

This is designed to match the behavior of the layer keyword and layer() function in the CSS import syntax.

Note that the empty attribute syntax sets the value implicitly to the empty string – so the layer attribute can be applied as though it is a boolean attribute, resulting in an anonymous layer.

Key scenarios

Existing style sheet links without the layer attribute should continue to work without any changes:

<!-- no layering is applied -->
<link rel="stylesheet" href="example.css" />
<link rel="stylesheet" href="screen.css" media="screen" />

When working with resets, third-party libraries, and design systems, authors may want to apply layers on-import. This is especially common since site authors may not have access to edit the style sheet, but still want to manage cascade priority of the resulting styles:

<!-- site.css can define layer order, and internal layering -->
<link rel="stylesheet" href="site.css" />

<!-- external styles can be assigned existing layers, or create new ones -->
<link rel="stylesheet" href="library/styles.css" layer="framework.library" />
<link rel="stylesheet" href="reset.css" layer="reset" />
<link rel="stylesheet" href="design/system.css" layer="framework.system" />

In some cases, authors might want to wrap styles in an anonymous layer that can’t be accessed later. This can be used to enforce code order, or simply move a reset to the lowest cascade position:

<!-- anonymously push a reset into a lower layer -->
<link rel="stylesheet" href="site.css" />
<link rel="stylesheet" href="reset.css" layer />

<!-- enforce layer rules are grouped in relevant files -->
<link rel="stylesheet" href="defaults.css" layer />
<link rel="stylesheet" href="patterns.css" layer />
<link rel="stylesheet" href="components.css" layer />

When given an invalid layer name the style sheets will not apply. That ensures a typo doesn’t apply un-layered style contents which would have the highest cascade-priority.

Detailed design discussion

While it would be nice to ship this along with a broader import-condition syntax, the details of that syntax have been contentious. However, the need for a new layer attribute, and the general shape of that attribute have not been controversial.

It might be worth noting that other CSS features are likely to need similar dedicated attributes. For example, the @scope block rule may warrant a scope() import rule and scope attribute. However, this is the exception for CSS – and only needed for larger architectural features. It is not likely to become a common request.

Stakeholder Feedback / Opposition

Authors have been asking when it will happen. Implementors have been positive in the linked discussions. Now that there is an explainer to reference directly, I will ask for more explicit standards positions:

References & acknowledgements

Many thanks for valuable feedback and advice from: