Organizing CSS files means splitting by role rather than by habit: base rules, layout, components, utilities and page-specific rules, each with a clear job and a predictable load order. Split too early and you have added work instead of removing it.
When is a single stylesheet actually a problem?
Splitting a stylesheet too early creates work rather than removing it. Every extra file is another request, another place to look, and another decision about order. A single well-organised file is perfectly maintainable at small sizes, and many projects never outgrow it.
The signals that you have outgrown one file are practical rather than numeric. You find yourself scrolling past two thousand lines to reach the rules for the component you are working on. Two people are editing the same file and their changes collide in review. A rule that only applies to one page is sitting in the middle of a block of shared rules, so nobody can delete it safely. At that point, splitting by role pays for itself immediately.
What should the split look like?
Most projects that survive contact with a real team end up with a handful of layers, and the names are less important than the rule that each layer has one job.
styles/
reset.css
tokens.css
base.css
layout.css
components/
button.css
fixture-table.css
fixture-card.css
utilities.css
pages/
home.css
fixtures.css
membership.css
The reset file normalises the handful of browser defaults that cause trouble, such as the margin on headings. The tokens file holds the custom properties. The base file sets element-level defaults like the font stack and the default line height. The layout file holds the page shell: the header, the main region and the footer. The components folder holds one file per reusable piece. The utilities file holds single-purpose overrides. The pages folder holds the rules that belong to one page only.
The split between components and pages is the one that matters most. A component rule is reusable and belongs in the components folder. A page rule styles something that exists only on that page, so it belongs in the pages folder and can be deleted with the page.
How do you control the order the rules apply in?
Order is part of the cascade, so the order of your files is part of your design. Two rules with the same specificity are resolved by which one comes last, so a file of overrides must be able to arrive after the file it overrides.
<link rel="stylesheet" href="/styles/tokens.css">
<link rel="stylesheet" href="/styles/base.css">
<link rel="stylesheet" href="/styles/layout.css">
<link rel="stylesheet" href="/styles/components/button.css">
<link rel="stylesheet" href="/styles/components/fixture-table.css">
<link rel="stylesheet" href="/styles/utilities.css">
<link rel="stylesheet" href="/styles/pages/fixtures.css">
Cascade layers make that order explicit instead of implied. You declare the layers once and then assign each import to one of them, and the browser resolves conflicts between layers by declaration order rather than by specificity. Rules outside any layer win against every layered rule, which is a useful safety net for the emergency patch you have to drop in without touching anything else.
@layer reset, tokens, base, layout, components, utilities, pages;
@import "./reset.css" layer(reset);
@import "./tokens.css" layer(tokens);
@import "./base.css" layer(base);
@import "./layout.css" layer(layout);
@import "./components/button.css" layer(components);
@import "./components/fixture-table.css" layer(components);
@import "./utilities.css" layer(utilities);
@import "./pages/fixtures.css" layer(pages);
Two practical warnings. An @import statement must come before other rules in the file, with the exception of character set declarations and layer statements, so the layer list above has to be written first. And each imported file has to be fetched before the file that imports it can be parsed, so a deep chain of imports delays the first paint more than a set of parallel stylesheet links does.
How do you stop page rules from leaking?
Page-specific rules are the ones that cause trouble, because a rule written for one page tends to use selectors that match elements elsewhere. Scoping them under a page class keeps the blast radius small.
<main class="fixtures-page">
<table class="fixture-table">
<caption>Saturday fixtures</caption>
<thead><tr><th scope="col">Kick-off</th><th scope="col">Opponent</th></tr></thead>
<tbody><tr><td>14:00</td><td>Riverside</td></tr></tbody>
</table>
</main>
/* pages/fixtures.css */
.fixtures-page .fixture-table {
border-collapse: collapse;
}
.fixtures-page .fixture-table caption {
text-align: left;
padding-block-end: 0.5rem;
}
The class on the page wrapper is the scope, and it comes from the markup of that one page. A rule that has to be rewritten every time the layout changes is a sign it belongs in a component instead, where it can be shared and tested once.
Practical rules
- Keep one file per reusable component and one file per page that owns rules of its own.
- Declare the cascade layer order once, at the top of the entry file.
- Never rely on file order alone for an override; use layers or a deliberate order you can see.
- Keep the tokens file free of selectors other than the root so it can be loaded first and read early.
- Name files after the thing they style, not after the page you found them on.
- Delete a page file when the page is removed; a stylesheet is a source of truth about what exists.
- Stop splitting once each file has a single clear job.
Try it yourself: take a single stylesheet from a project of yours and split it into reset, tokens, base, layout, components and pages, moving every rule into exactly one of those files. Then convert the entry point to the layer-and-import pattern above and check every page in the browser, because the order of your rules just changed and any override that relied on file order will now surface. Finish by adding a page called results.html with its own file and confirm that none of its rules affect the fixtures page.