Module 1: Introduction & Fundamentals
Ways to Apply CSS: Inline, Internal and External
The three ways to attach CSS to HTML, what each costs you in caching, specificity and load speed, and which one to reach for by default.
There are three places CSS can live: on the element, in the page, or in its own file. They all end up in the same cascade, but they behave differently in specificity, caching and loading. Pick the wrong one and you either fight your own styles later or slow the page down.
Inline styles#
An inline style is written directly on the element in a style attribute.
<p style="color: crimson; font-weight: 700;">Payment failed.</p>
There is no selector, because the element is the selector. This also means inline styles carry the highest specificity of any normal declaration. Nothing in a stylesheet can override them unless it uses !important.
That is the real problem with inline CSS. It works great right up until you need to change it from a stylesheet. Then you start adding !important to beat it, and the codebase gets worse from there.
Inline styles also cannot express hover states, pseudo-elements or media queries. They are one flat list of declarations and nothing else.
The legitimate use is values that are computed at runtime. JavaScript setting a progress bar width or a custom property is a good example:
bar.style.width = `${percent}%`;
card.style.setProperty("--accent", userColor);
Internal styles#
An internal stylesheet is a style element, normally inside head.
<head>
<style>
body { margin: 0; font-family: system-ui, sans-serif; }
.alert { padding: 1rem; background: #fff4e5; }
</style>
</head>
You get full selector power with no extra network request. The cost is that the CSS ships inside the HTML, so it cannot be cached separately and every page repeats it.
Use internal styles for single-page demos, HTML emails, and a small "critical CSS" block that makes the first paint fast. For a real multi-page site, it is the wrong default.
External stylesheets#
An external stylesheet is its own .css file, linked from the HTML.
<head>
<link rel="stylesheet" href="css/styles.css">
</head>
This is the default for good reasons. The browser downloads the file once, caches it, and reuses it on every page. You edit one file and the whole site changes. Your CSS stays separate from your markup, which makes both easier to read.
Two details trip people up regularly.
First, href is resolved relative to the HTML file. Second, any url() inside the CSS file is resolved relative to the CSS file, not the HTML page. If your stylesheet is in css/ and refers to url("img/hero.jpg"), the browser looks for css/img/hero.jpg.
project/
├── index.html href="css/styles.css"
├── css/
│ └── styles.css url("../img/hero.jpg") ← goes up one level
└── img/
└── hero.jpg
Why @import is a trap#
CSS has its own way to pull in another file:
/* styles.css */
@import url("buttons.css");
@import url("cards.css");
It looks tidy, but it is slow. The browser has to download styles.css, parse it, and only then discover that it needs two more files. Those downloads cannot start earlier.
With separate link tags in the HTML, the browser finds all of them while scanning the page and fetches them in parallel.
<link> x3: index.html ──┬─► styles.css
├─► buttons.css all start together
└─► cards.css
@import: index.html ─► styles.css ─► buttons.css ─► cards.css
(each one discovered only after the previous loads)
If you want to split CSS across files, use multiple link tags or let a build tool bundle them (Module 10). Save @import for Sass, where it is resolved at compile time.
Linking by media#
The media attribute lets you tell the browser a stylesheet only applies in some situations:
<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="wide.css" media="(min-width: 1024px)">
The browser still downloads non-matching files, but at low priority, and it does not block the first render waiting for them.
Order matters#
When two rules have equal specificity, the one that comes later wins. That applies across files as well, so the order of your link tags is part of your CSS.
<link rel="stylesheet" href="framework.css">
<link rel="stylesheet" href="site.css"> <!-- overrides framework.css on ties -->
Put third-party and base styles first, your own styles last.
Which should you use#
- External file by default, for everything that is not special.
- Internal
stylefor a tiny critical-CSS block, a demo, or an HTML email. - Inline
stylefor dynamic values set from JavaScript, ideally as custom properties.
One more reason to avoid inline styles: a Content Security Policy with a strict style-src can block them entirely. Many production sites run that policy.
Common mistakes#
- Wrong path. The page is unstyled and nobody tells you why. Check the Network tab for a 404 on the CSS file.
- Missing
rel="stylesheet". Without it thelinkis not treated as a stylesheet at all. - Fixing conflicts with inline styles. It works today and traps you tomorrow. Fix the selector instead.
- Editing CSS and seeing no change. You are looking at the cached copy. Hard reload, or use hashed file names like
styles.4f9a1c.cssin production. @importchains in production CSS.
Practice#
- Make a folder with
index.html,css/styles.cssandimg/bg.jpg. Link the stylesheet and useurl("../img/bg.jpg")as a background. Make it work. - Move one style into a
styleattribute and try to override it fromstyles.csswithout!important. Describe what happens. - Replace two
linktags with@importin one file. Open the Network tab and compare the waterfall before and after. - Add a
print.csswithmedia="print"that hides the navigation. Use print preview to check it.