skip to content

In a CSS file the browser loads directly with a <link> tag, what rules govern where the @import at-rule may appear, and what does it cost at runtime compared with composing the same files at build time?

level: middleimportance: should knowfreq 45%

answer

  1. before every style rule, or ignored
  2. the browser must parse to discover
  3. a chain, not a fan-out
  4. fine for authoring, poor for delivery
  5. the build flattens the graph

basics

~20 s

CSS @import rules must appear before any style rule — only @charset and @layer statements may precede them — and a misplaced one is silently ignored. At runtime each import is discovered only after its parent sheet is parsed, so requests serialize; a build step inlines them instead.

solid answer

~50 s

`@import` pulls another stylesheet into the current one, but it has a strict placement rule: it must come before every style rule in the file, preceded only by `@charset` and by `@layer` statements that just declare layer names. Put an `@import` after a normal rule and the browser silently ignores it — one of the classic "my CSS isn't loading" bugs. The runtime cost is the bigger issue: the browser cannot know an imported URL exists until it has downloaded and parsed the sheet that references it, so a chain of imports becomes a chain of round trips rather than parallel requests. That is why `@import` is fine as an *authoring* mechanism and poor as a *delivery* one: a build step resolves the import graph and concatenates it into one file, so splitting your CSS across fifty files costs nothing at runtime.

code

css · 13 lines
css
@charset "utf-8";
@layer reset, base, components;

@import url("reset.css") layer(reset);
@import url("base.css") layer(base);
@import url("components/card.css") layer(components);
@import url("print.css") print;

body {
  margin: 0;
}

@import url("utilities.css");

go deeper

for a junior

Know that @import pulls in another stylesheet, that it has to sit at the top of the file before any rules, and that a misplaced one is ignored with no error.

for a middle

Explain the discovery chain: the browser only learns an imported URL exists after parsing the sheet that references it, so imports serialize requests where <link> elements parallelize them. State the exact placement rule including @charset and @layer statements.

for a senior

Draw the authoring-versus-delivery line clearly — imports as source composition flattened by a build, never as a runtime delivery mechanism — and explain why the entry file's import order remains the project's cascade contract after flattening.

for a principal

Treat the entry file as a governed artifact: it encodes override order for every team, so decide who reviews changes to it, and be able to justify how CSS is chunked for delivery against the cost of extra requests versus over-shipping unused rules.

## What @import does `@import` is CSS's own file-inclusion mechanism. Written inside a stylesheet, it tells the browser to fetch another stylesheet and apply its rules as though they appeared at that point: ```css @import url("reset.css"); @import url("tokens.css"); @import url("components/card.css"); ``` It can also carry a media query, so the import only applies under matching conditions: ```css @import url("print.css") print; ``` On the surface this looks like exactly the file organisation you want: one entry stylesheet in the `<link>`, with the real structure expressed as an ordered list of imports. The order of the `@import` rules *is* the cascade order of the files, which makes the entry file a readable statement of architecture. ## The placement rule `@import` is only valid near the top of a stylesheet. It must precede every style rule, and the only things permitted before it are `@charset` and `@layer` **statement** rules — the form that declares layer names without a block, like `@layer reset, base, components;`. Anything else in front of it makes the import invalid, and an invalid `@import` is dropped silently: no console error by default, no missing-file warning, just styles that never arrive. ```css @layer reset, base, components; /* allowed before @import */ @import url("reset.css"); /* applied */ body { margin: 0; } @import url("components.css"); /* ignored - a style rule came first */ ``` This catches people when they append an import to the bottom of a growing file, which is exactly where a hurried edit tends to land. ## Why runtime @import serializes work The browser discovers subresources by parsing. When the HTML references `main.css`, the browser requests it. Only once `main.css` has arrived and been parsed does the browser learn that `reset.css`, `tokens.css` and `card.css` exist — and only then can it request them. If `card.css` itself imports something, that is a third round trip. Compare that with declaring the sheets directly in the HTML: the parser sees several `<link>` elements at once and can request them in parallel immediately. `@import` converts a parallel fan-out into a dependency chain whose depth equals your import nesting. On a fast local network this is invisible; on a high-latency mobile connection each extra hop is a full round trip added before the page can render. This is why "never use `@import` in production CSS" became folklore. The rule is right, but the reason matters: the problem is not the at-rule, it is *discovering files late over the network*. Whether that latency is worth optimising for a given page is a performance-strategy question in its own right; the mechanical fact — imports are discovered serially — is what you need to be able to state. ## What a build step changes Every modern CSS toolchain resolves `@import` at build time. The tool reads the entry file, follows the import graph, and emits the concatenated result as one stylesheet (or as a small number of chunks aligned with routes). The browser then receives a file with no `@import` rules in it at all. That inverts the tradeoff completely: - **Authoring**: you keep one file per component, one file per token group, one entry file that reads as an ordered table of contents. - **Delivery**: a single request, no discovery chain, and the concatenation order is exactly the order you wrote the imports in — so the cascade behaves precisely as your entry file describes. The configuration and behaviour of any particular bundler is that tool's own subject; the architectural point is only that composition moved from run time to build time, and that the entry file's ordering survives the move. ## Practical guidance Use `@import` freely as the source-level composition mechanism when a build step will flatten it. Avoid it in stylesheets shipped raw to the browser, and if you must ship raw CSS, prefer several `<link>` elements over an import chain so requests can go out in parallel. Whichever you do, keep the entry file *only* imports. The moment a real rule appears in it, two things break at once: any import written below that rule is silently discarded, and the file stops being a readable map of the project.

  • If an @import is silently ignored, how would you actually notice during development?
    You mostly notice by the missing styles, which is the problem. The reliable checks are the network panel — the imported file never appears as a request — and the browser's stylesheet inspector, which lists the sheets that were actually applied. Some linters flag imports that follow style rules, which is the cheapest way to catch it before it ships.
  • Is loading several stylesheets with separate <link> elements better than one @import chain, and why?
    For raw delivery, yes. The HTML parser sees every `<link>` at once and can issue all the requests in parallel, whereas an import chain discovers each URL only after parsing the sheet above it. The links also make the load order explicit in the document. Neither beats a build step that emits one flattened file.
  • If the build flattens everything anyway, does the order of @import rules in the entry file still matter?
    Very much — the concatenation preserves that order, and source order is what decides conflicts between rules of equal specificity. The entry file is effectively the project's cascade declaration: reset before base, base before components, components before utilities. Reordering two lines there can change which rule wins across the whole app.

saying these in an interview costs you the question

  • Appends new @import rules to the bottom of the file
  • Believes @import and <link> load identically
  • Thinks @import is slow because parsing is slow
  • Assumes a build tool is required for @import to work at all
  • Puts real style rules in the entry file alongside imports

context