CSS@import

@import: Syntax, Layers, and the Performance Story

@import pulls one stylesheet into another. It looks like an innocent convenience, but it has real performance consequences — enough that "avoid @import in production CSS" has been standard advice for over a decade. This page covers the full syntax (including the newer layer and supports forms), why it hurts loading, the cases where it is perfectly fine, and how Sass changed the meaning of the word entirely.

Basic Syntax

CSS
/* Both forms are equivalent */
@import url("typography.css");
@import "typography.css";

/* Absolute and relative URLs both work */
@import url("https://example.com/theme.css");
@import url("../shared/reset.css");
Position matters
@import must appear before every other rule in the stylesheet — only @charset and @layerstatements may precede it. An @import placed after a normal rule is silently ignored.
Conditional Imports: Media Queries

You can append a media query so the imported styles only apply under that condition. Note the subtlety: in most browsers the file is still downloaded either way — the condition gates application, not necessarily the fetch.

CSS
/* Print-only styles */
@import url("print.css") print;

/* Only applies at wide viewports */
@import url("wide-layout.css") (min-width: 1024px);

/* Type and feature combined */
@import url("landscape.css") screen and (orientation: landscape);

/* Multiple comma-separated queries (OR) */
@import url("compact.css") (max-width: 480px), (max-height: 500px);
Conditional Imports: supports()

CSS
/* Only import if the browser supports grid */
@import url("grid-layout.css") supports(display: grid);

/* Combine a support test with a media query */
@import url("fancy.css") supports(backdrop-filter: blur(2px)) (min-width: 768px);
Layer Imports

The modern superpower: import an entire file into a cascade layer. This is the cleanest way to demote third-party CSS below your own styles, no specificity hacks required.

CSS
/* Establish layer order first */
@layer vendor, base, components;

/* Everything in bootstrap.css lands in the vendor layer */
@import url("bootstrap.css") layer(vendor);

/* Import into an anonymous layer */
@import url("widget.css") layer;

@layer components {
  /* Beats any vendor rule, regardless of its specificity */
  .btn { background: rebeccapurple; }
}
Why @import Hurts Performance

CSS blocks rendering. When the browser discovers a stylesheet via a link tag in the HTML, its preload scanner can start fetching it immediately — even multiple stylesheets in parallel. An @import, however, hides inside a CSS file: the browser cannot know about it until it has downloaded and parsed the importing file. The result is a sequential request chain.

HTML
<!-- SLOW: sequential waterfall -->
<!-- index.html → main.css → (parse) → theme.css → (parse) → colors.css -->
<link rel="stylesheet" href="main.css" />
<!-- main.css contains: @import url("theme.css");
     theme.css contains: @import url("colors.css"); -->

<!-- FAST: parallel downloads, discovered from the HTML -->
<link rel="stylesheet" href="main.css" />
<link rel="stylesheet" href="theme.css" />
<link rel="stylesheet" href="colors.css" />

Approach

Discovery

Downloads

Render blocking time

Multiple <link> tags

HTML preload scanner

Parallel

One round trip

@import chain (depth 2)

After parsing each file

Sequential

Two+ round trips

Bundled single file

HTML preload scanner

One request

One round trip

Note
Each level of @import nesting adds roughly one network round trip to your First Contentful Paint. On a high-latency mobile connection that can mean hundreds of milliseconds per level.
When @import Is Acceptable
  • During development, when a bundler will inline the imports at build time anyway.

  • With a build pipeline (PostCSS, Lightning CSS, Vite) that resolves @import into one flat file.

  • Layer imports of third-party CSS — layer(vendor) — where the ergonomics beat the cost and the file is cached.

  • Print stylesheets or rarely-hit conditional styles where the extra latency is off the critical path.

  • Quick prototypes and CodePen-style demos where performance is irrelevant.

Rule of thumb
In shipped production CSS served to browsers, preferlink tags or a bundled file. Reserve raw@import for cases your build tool flattens.
Sass @import vs @use

Sass historically overloaded the same keyword: Sass @import is a compile-time include — it copies the partial into the output, so there is no runtime cost. But it had serious design flaws: every import dumped its variables and mixins into one global namespace, and files could be imported (and emitted) multiple times. Sass has deprecated @import in favor of @use / @forward.

CSS
/* Legacy Sass — deprecated */
@import "variables";
@import "mixins";
.card { color: $brand; }        /* global leakage */

/* Modern Sass */
@use "variables" as v;
@use "mixins";
.card { color: v.$brand; }       /* namespaced */

/* Re-export from an index file */
@forward "variables";
@forward "mixins";

Feature

Sass @import (legacy)

Sass @use

Namespace

Everything global

Namespaced per module

Loaded multiple times

Yes — duplicate output possible

No — loaded once

Private members

None

Names starting with - or _ stay private

Status

Deprecated (removal planned)

Recommended

Warning
Do not confuse the two worlds: a plain CSS @import that survives to the browser costs network round trips; a Sass@import/@use is resolved at build time and costs nothing at runtime.
Key Takeaways
  • @import must be the first thing in a stylesheet (after @charset / @layer statements).

  • It supports media conditions, supports() tests, and cascade layer targets.

  • Native @import creates sequential request chains that delay rendering — prefer link tags or bundling in production.

  • layer(vendor) imports are the modern, legitimate use case for taming third-party CSS.

  • Sass @import is compile-time and free at runtime, but deprecated — migrate to @use and @forward.