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.
-
HTML Basics
- What is HTML: The Structure Layer of Every Web Page
- HTML Document Structure: DOCTYPE, html, head and body
- Elements, Tags and Attributes: The Vocabulary of HTML
- HTML Comments: Notes That Ship With Your Code
- Block Level and Inline Elements
- Writing and Running Your First HTML Page
- How a Browser Turns Markup Into a Page
- Text and Formatting
- Links and Navigation
- Images and Media
- Lists
- Tables
-
Forms
- Form Structure: form, action and method
- Input Types: Text, Email, Number, Date and the Rest
- Labels: The Most Important Element in a Form
- Checkboxes, Radio Buttons and Grouping
- select, option, optgroup and datalist
- textarea, File Uploads and Hidden Fields
- Buttons: submit, reset and button
- Built In Form Validation
- GET or POST: What Happens When a Form Is Submitted
- Semantic HTML
- HTML5 Features
- Head and Metadata
- HTML with CSS
- HTML with JavaScript
- Accessibility
-
HTML SEO
- How Google Works: Crawling, Indexing and Ranking
- SEO Friendly HTML Structure
- Titles and Descriptions That Earn Clicks
- Headings and Content Structure for Search
- Internal Linking and Anchor Text
- robots.txt and XML Sitemaps
- Canonical URLs and Duplicate Content
- Structured Data and JSON-LD
- Image SEO
- Core Web Vitals and Mobile Friendliness
- DevTools and Debugging
- Editor Productivity
- HTML Best Practices
- HTML Projects
- Advanced Projects
- Practice and Exams
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
| Source | Wins over |
|---|---|
| Browser default | Nothing |
| External or internal stylesheet | Browser default |
Inline style attribute | Both of the above |
!important in a stylesheet | Inline styles |
!important inline | Everything |
Between stylesheets of equal weight, the later rule wins, which is why link order matters.
Where to put the link
<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
linkis 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
mediaattribute lowers the priority of a stylesheet that does not currently match, but it is still downloaded. @importinside 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
!importantto defeat an inline style instead of removing it. - Loading six separate stylesheets, each a blocking request.
- Using
@importin 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
!importantfor utility classes and third party overrides.
Practice
- 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.
- Set a colour in an external stylesheet, an internal one and an inline attribute, and work out which wins and why.
- Replace an inline width with a custom property and a class.
- Add a print stylesheet with
media="print"and check the print preview.