# Stylesheet

## What it is

A stylesheet, spelled style sheet in the specifications and often just called
the CSS file, is one collection of CSS rules that a browser fetches, parses and
applies to a document as a single unit.

A page can receive stylesheets in four ways. A `link` element with
`rel="stylesheet"` points at a separate file. A `style` element carries rules
inside the HTML itself. An `@import` rule at the top of one stylesheet pulls in
another. And a script can build one with `new CSSStyleSheet()` and attach it to
a document or a shadow root through `adoptedStyleSheets`. The first two look
like this in the head of a page:

```html
<link rel="stylesheet" href="/css/site.css" />
<link rel="stylesheet" href="/css/print.css" media="print" />
<style>
    .banner {
        border-block-end: 2px solid;
    }
</style>
```

The author's sheets are not the only ones in play. Every browser ships a default
stylesheet, which is why an unstyled heading still comes out large and bold, and
a reader can add a user stylesheet of their own; the cascade weighs the three
origins against each other. The `style` attribute on one element is a different
thing: declarations with no selector, belonging to that element and to no sheet.

Once loaded, a stylesheet becomes a `CSSStyleSheet` object in the CSS Object
Model, the specification this term's definition comes from. The object records
the element that owns the sheet, the media it applies to, its title, whether it
is disabled, and its list of rules, and `document.styleSheets` lists a page's
sheets in order. One flag exists for privacy: a sheet fetched from another
origin without CORS is not origin-clean, and a script that asks for its rules
gets a security error instead.

The `media` attribute limits a sheet to an environment. A sheet marked
`media="print"` is still downloaded, but the browser waits only for sheets whose
media matches, so a print sheet does not hold up the screen.

## Why it matters

A stylesheet decides what a browser has to fetch before it shows anything. The
HTML standard treats a stylesheet link the parser finds as render-blocking
whenever its media matches, so the first paint waits until that file has arrived
and been parsed, instead of drawing the page unstyled and redrawing it in front
of the reader. Its size, its host and the round trips needed to discover it all
sit between a click and the first thing a reader sees.

The number of files is the first decision. One bundle shared by every page is
fetched once and then served from the cache; a stylesheet per page sends less up
front and more as a reader moves around. Rules inlined in a `style` element cost
no extra request, but they travel again with every page, because they cannot be
cached apart from the document that carries them. Each `@import` adds a full
round trip, because the browser learns the imported address only after the
importing file has arrived.

Caching is the second. A stylesheet cached for a year makes every later page
faster, but a change then has to reach readers holding the old copy. The usual
answer is a fingerprinted file name, with a hash of the content inside the name,
so a changed file is a new address and an old one can stay cached for as long as
anyone likes, while the HTML pointing at it is kept short-lived.

Trust is the third. A stylesheet decides what is visible and where; it can hide
a warning, restyle a button, or request images from any address its rules name.
Loading one from a third party hands that power to the third party, along with a
host the page cannot render without. Subresource Integrity lets the page state a
cryptographic hash for the file, and the browser refuses any file whose bytes
differ.

## What it looks like in practice

The site's own stylesheet bundle shows how far one inlined asset can tip the
balance. At 87 KB it was, for a document site with no framework, most of a
page's weight spent before any content arrived, and two thirds of the largest
stylesheet turned out to be one base64-encoded font: about 56 KB of text
encoding about 42 KB of font. It had been inlined for a first-paint benefit that
a preloaded external file gets anyway, and every page paid for it whether the
weight was needed or not. Taking it out took that file from 84 KB to 28 and
[the whole bundle from 87 KB to 31 KB](/portfolio/cut-the-stylesheet-bundle-from-87kb-to-31kb-139/),
with the font weights preloaded instead. Base64 writes every three bytes as four
characters, so a binary file inlined into a stylesheet always weighs about a
third more than the file served on its own.

Paper is a medium a stylesheet has to serve too. The site's case studies are
what someone prints and takes into a meeting, so
[one 637-line stylesheet](/portfolio/built-a-637-line-print-stylesheet-136/)
with a single page rule and one print block makes them print on unknown paper in
both colour themes. The page rule sets a margin and deliberately no paper size,
and a measure of about seventy-two characters keeps a landscape sheet from
running a line to about a hundred and ten. Four engine behaviours are written
down as traps, each having caused a real defect: viewport units measured
against the page box by one engine but against the window by another, which
cropped a third of every line; fixed-position elements that paint on every
sheet; a palette defaulting to white text for the dark theme, which printed four
section labels white on white; and a flex or grid box that paged media cannot
split, which produced a blank first sheet. A six-part check covers six paper
and orientation combinations, both colour-scheme preferences and the real page
count, and it was that check which noticed when a stray comment terminator made
the CSS linter swallow a whole rule and pass clean twice.

The site's [security page](/security/) lists Subresource Integrity on the
stylesheet: the bundle is fingerprinted and hashed at build time, so a reader's
browser can verify that it arrived unmodified. The same page asks the reader to
count the hosts in the browser's network panel and gives the answer as one, the
site's own.

## Where it goes wrong

A stylesheet the browser learns about late arrives late. A link at the end of
the body, a sheet injected by a script, or a chain of `@import` rules each lets
the page paint before the styles land, so raw text jumps into a layout, or a
finished layout shifts. An import chain looks harmless in the source:

```css
/* site.css: the browser finds base.css only after this file arrives */
@import url("base.css");
@import url("theme.css");
```

Put stylesheet links in the head and flatten imports into the bundle at build
time.

A deploy can pair new markup with old rules. Readers whose cache holds the
previous stylesheet get HTML expecting classes the old file has never heard of,
and their layout breaks until the cache expires, while anyone testing with a
fresh cache sees a working site. The cause is a stylesheet name that never
changes combined with a long cache lifetime, and a fingerprinted name removes
it.

A stylesheet served with the wrong content type is ignored. The HTML standard
applies a linked stylesheet only when it arrives as `text/css`, with one
exception for a same-origin file on a page in quirks mode. A server that labels
`.css` files as `text/plain`, or answers a missing file with an HTML page and a
success status, leaves the page unstyled while the network panel shows a request
that apparently worked. Check the header on the deployed file.

An integrity hash goes stale when the file changes after hashing: a hand edit on
the server, a CDN that minifies on the way through, or a build that hashes
before its last rewrite. The browser treats the mismatch as a network error and
renders without the sheet. On another origin, integrity also needs CORS: a
`crossorigin` attribute on the link and a matching header from the server.
Compute the hash in the step that writes the final file.

A framework stylesheet on a public CDN used to be justified by readers already
having it cached from other sites. The Fetch standard partitions the HTTP cache
by top-level site, so each site's copy is fetched and stored separately, and
what remains is a second host on the critical path under someone else's control.
Serving the file from the site's own host costs the same bytes without the extra
connection.

## Further reading

- [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/#css-style-sheet) —
  what a style sheet is inside the browser, and the interface scripts use to
  read and change it.
- [HTML Standard: Links](https://html.spec.whatwg.org/multipage/links.html) —
  the `stylesheet` link type, the content-type rule and when a stylesheet blocks
  rendering.
- [Subresource Integrity](https://www.w3.org/TR/SRI/) — the `integrity`
  attribute and what the browser does with a file that fails the check.
- [Fetch Standard: HTTP cache partitions](https://fetch.spec.whatwg.org/#http-cache-partitions)
  — how the cache is keyed by top-level site.
- [CSS Paged Media Module Level 3](https://www.w3.org/TR/css-page-3/) — the
  `@page` rule and page margins a print stylesheet works with.
- [Media Queries Level 5](https://www.w3.org/TR/mediaqueries-5/) — print,
  screen, colour-scheme preferences and the other conditions a `media` attribute
  can test.

<https://engineer.company/glossary/stylesheet/>
