Three Ways to Attach CSS, and When Each Is Right

External, internal and inline styles. One of them is the answer almost always, and knowing why the other two exist is what tells you when to break the rule.

Concept

CSS reaches a page in one of three ways, and they differ in reuse, caching, override strength and how well they survive a growing project.

1. External stylesheet

<head>
  <link rel="stylesheet" href="/styles/site.css">
</head>

The default for almost everything.

  • One file styles the whole site, so a change lands everywhere at once.
  • It is cached, so the second page a reader visits does not download it again.
  • The markup stays readable.
  • It can be minified and versioned by a build step.

The rel="stylesheet" attribute is what makes it a stylesheet; without it the browser downloads nothing. The type attribute is unnecessary.

Stylesheets are render blocking: the browser will not paint until they have arrived. That is deliberate - painting unstyled content and then restyling it produces a visible flash - but it means every stylesheet is on the critical path. Keep them few and small.

<!-- conditional loading: only fetched with priority when it matches -->
<link rel="stylesheet" href="/styles/print.css" media="print">
<link rel="stylesheet" href="/styles/wide.css" media="(min-width: 60rem)">

2. Internal stylesheet

<head>
  <style>
    .hero { min-height: 60vh; display: grid; place-items: center; }
  </style>
</head>

Styles inside the document. Not cached across pages and not reusable, so it is wrong as a general approach - but it has two legitimate uses.

Critical CSS. Inlining the small amount of CSS needed to render what is above the fold means the first screen can paint without waiting for a network request. The rest of the stylesheet loads normally.

A single page document. A standalone file with no siblings has nothing to share a stylesheet with.

3. Inline styles

<div style="background-image: url('/images/hero.jpg')"></div>

A style attribute on one element. It applies to that element only, cannot be cached or reused, mixes presentation into the markup, and is close to impossible to override - it beats every selector in the cascade and can only be defeated with !important.

The legitimate cases are narrow:

  • A value that is genuinely dynamic and unknown at build time - a progress width, a chart bar height, a background image from a database.
  • HTML email, where external stylesheets are unreliable and inline styles are the norm.
  • A quick test in DevTools.

Even for dynamic values, a custom property is usually cleaner:

<div class="bar" style="--value: 68%"></div>
.bar::after {
  width: var(--value);
  background: #1e3a8a;
}

The markup carries data; the stylesheet keeps the design.

The cascade in one table

SourceWins over
Browser defaultNothing
External or internal stylesheetBrowser default
Inline style attributeBoth of the above
!important in a stylesheetInline styles
!important inlineEverything

Between stylesheets of equal weight, the later rule wins, which is why link order matters.

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>...</title>

  <link rel="stylesheet" href="/styles/site.css">
</head>

In the head, before the body content. A stylesheet discovered late causes a flash of unstyled content: the page paints with browser defaults and then repaints. It is jarring and entirely avoidable.

Loading a non critical stylesheet without blocking

<link rel="preload" href="/styles/extras.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles/extras.css"></noscript>

A known pattern for stylesheets that are not needed for the first paint. Use it sparingly; splitting CSS badly can cost more than it saves.

Web fonts

<link rel="preconnect" href="https://fonts.example.com" crossorigin>
<link rel="stylesheet" href="https://fonts.example.com/css?family=Inter">
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter.woff2") format("woff2");
  font-display: swap;      /* show fallback text immediately */
}

font-display: swap matters: without it the browser hides text until the font arrives, which on a slow connection means a page of invisible text.

Important rules

  • link is a void element and belongs in the head.
  • Stylesheets block rendering; scripts block parsing.
  • Later rules of equal specificity win, so link order is meaningful.
  • Inline styles beat every selector.
  • A media attribute lowers the priority of a stylesheet that does not currently match, but it is still downloaded.
  • @import inside CSS serialises requests and should be avoided.

Common mistakes

  • Putting the stylesheet link at the end of the body and shipping a flash of unstyled content.
  • Reaching for inline styles because they are quicker, then fighting them later.
  • Using !important to defeat an inline style instead of removing it.
  • Loading six separate stylesheets, each a blocking request.
  • Using @import in the main stylesheet.
  • Omitting rel="stylesheet" and wondering why nothing applies.
  • Web fonts with no font-display.

Best practices

  • External stylesheet in the head for essentially everything.
  • Inline only genuinely dynamic values, and prefer a custom property.
  • Keep the number of blocking stylesheets small.
  • Consider inlining critical CSS on the pages that matter most.
  • Version stylesheet file names so they can be cached for a long time.
  • Reserve !important for utility classes and third party overrides.

Practice

  1. Link a stylesheet from the end of the body on a throttled connection and describe what the reader sees. Move it to the head and compare.
  2. Set a colour in an external stylesheet, an internal one and an inline attribute, and work out which wins and why.
  3. Replace an inline width with a custom property and a class.
  4. Add a print stylesheet with media="print" and check the print preview.

Useful resources

Hand picked references for this topic
Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All HTML notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.