CSS @import is cool, actually


Using @import is generally thought of as a bad practice. While there are some performance considerations to be aware of, @import provides some useful functionality that a <link> isn’t capable of.

Cascade layers

You can @import all the styles of a CSS file into a cascade layer.

@import 'utility-classes.css' layer(utilities);

@import statements must be included at the top of a stylesheet but an @layer declaration can come before your imports:

@layer base, components, utilities;
@import 'base.css' layer(base);
@import 'components.css' layer(components);
@import 'utility-classes.css' layer(utilities);

Feature queries

An @import can include a supports() condition. If you want a stylesheet to be applied only if some newfangled CSS feature is supported, this is a clean way to achieve that.

@import '/background-clip.css' supports(background-clip: border-area);
@import '/field-sizing.css' supports(field-sizing: content);

You can also import a stylesheet only if a CSS feature is not supported:

@import '/fallback1.css' supports(not (animation-timeline: scroll()));
@import '/fallback2.css' supports(not (top: anchor(top)));

Unfortunately, in Safari and Firefox, all stylesheets get requested, but only those that meet the supports condition actually get applied. Chrome is more efficient: only stylesheets that meet the supports condition get requested.

The supports condition for @import was added in Safari 17.5, Chrome 122, and Firefox 115.

Media queries

If you seperate styles into seperate files for mobile and desktop or light and dark mode, for instance, you can conditionally apply the correct stylesheet:

@import "/dark-styles.css" (prefers-color-scheme: dark);
@import "/light-styles.css" (prefers-color-scheme: light);

While only the appropriate stylesheets actually get applied to the document, all .css files are downloaded. Not only are requests made for unused CSS files, those requests are high priority. The <link> element is better is this regard as stylesheets that don’t match the media query are downloaded at a lower priority.

All of the above

You can use all of these features together:

 @import "/foo.css" 
 layer(overrides) 
 supports(color: lch(29.2345% 44.2 27)) 
 (max-width: 700px);

Both <link> and @import support media queries. <link>, however, has no way to specify a supports condition and lacks the ability to assign a stylesheet to a cascade layer. We might eventually get a layer attribute and a supports attribute for the <link> element, but that is not supported in any browser yet.

Performance

Some articles accuse @import of being bad for performance, but that partly depends on how its used. Let’s look at an example:

<link href="/a.css" rel="stylesheet" />

If stylesheet a.css contains an @import statement, a waterfall will be created:

@import "/b.css" ;

Referencing one CSS file inside another causes the files to be downloaded sequentially — a.css needs to be downloaded before b.css can be requested. Using @import in this way should be avoided.

The following code in the <head> of the HTML document avoids that issue:

<style>
    @import "/a.css";
    @import "/b.css";
</style>

Even compared to this improved approach, there are performance reasons to preference the <link> tag over @import, at least when using media queries:

  • In Safari and Chrome, when using media queries as part of an @import statement, the preload scanner won’t pick up the stylesheets.
  • When using media queries, the <link> element downloads non-matching stylesheets with a low priority, @import downloads them with a high priority and blocks rendering.