Apply
CSS architecture
Good CSS architecture should reduce decisions, not create weekly debates about where a button belongs. Structure should make ownership obvious and maintenance boring in the best possible way.
File structure
css/
- site.css
- settings/
- base/
- utilities/
- layout/
- components/
- theme/
- legacy/# inherited first-party CSS (optional)
- thirdparty/# vendor CSS when split from legacy (optional)
- hacks/
Root files should be import maps, not giant dumping grounds. Each component should own its own partial instead of disappearing inside one oversized stylesheet that is hard to maintain.
Root file as import map
@layer legacy, settings, base, utilities,
layout, components, theme, hacks;
@import './legacy/vendor.css' layer(legacy);
@import './settings/tokens.css' layer(settings);
@import './layout/page-shell.css' layer(layout);
@import './components/product-card.css' layer(components);
@import './theme/brand.css' layer(theme);The main stylesheet should define layer order first, then import files into the correct place. If the root file becomes a working area instead of an import map, architecture starts leaking. Shared design values live in settings/tokens.css — seedesign tokens for naming and scales. Vendor CSS can live in legacy/ or a separatethirdparty/ layer declared before legacy when both buckets are needed — seelayer guidance.
Component partials
One component, one partial. Each file should contain stable styles, child selectors, modifiers, state, and breakpoint changes for that component only.
File size and cognitive load
Most component partials should stay small enough to understand quickly. Around 50 to 250 lines is common. Larger complex components may reach 300 to 400 lines, but that should trigger review, not pride.
Large files usually hide sub-components, repeated patterns, old hacks, or responsibilities that should have moved out months ago.
CSS nesting (team choice)
Native CSS nesting is optional. LSCSS does not require it — some teams prefer flat selectors in a component partial; others nest modifiers, children, and breakpoints under the component root. Choose based on browser support, build tooling, and stylelint rules. Seebrowser support before adopting it site-wide.
When nesting is allowed, keep it shallow and inside the component you own. A typical partial nests&--featured for modifiers, short child selectors, and breakpoint tweaks under one root such as .product-card. That compiles to the same ownership model as flat.product-card--featured rules in the same file.
/* components/product-card.css — one component root per file */
.product-card {
/* Base styles */
&--featured {
/* Modifier-only deltas; HTML still needs product-card + product-card--featured */
}
& > .title {
/* Child scoped under the same root */
}
}- One component root per partial — do not nest across unrelated components.
- Modifiers still need the base class in HTML (
product-card product-card--featured). Seemodifiers and state. - Prefer shallow nesting over deep selector archaeology.
- Nesting does not replace layers, unlayered CSS discipline, or state vs modifier rules.
Media query organisation
Put shared styles first. Then add breakpoint rules only where something changes. The example below also shows a modifier (&--large) nested under the component root — the same pattern as CSS nesting for variants. Breakpoints should describe differences, not restate the entire component unnecessarily.
.modal {
/* Shared styles first */
&--large {
/* Modifier nested under the component root */
}
@media (--desktop) {
/* Only what changes */
}
@media (--tablet) {
/* Only what changes */
}
@media (--mobile) {
/* Only what changes */
}
}- Shared styles first.
- Only changed values inside breakpoints.
- Keep modifier and state breakpoint changes nearby.
- Keep child selector changes near the child selector they affect.
- Use named custom media queries, not remembered magic numbers.
Working rules
- If a group of rules feels like its own component, it probably is.
- Do not hide temporary fixes inside normal component files.
- Do not leave third-party CSS outside a dedicated low-priority cascade layer (often
legacyorthirdparty). - Do not use utility chains instead of actual ownership.
- Large files trigger review, not acceptance.
- If the project becomes difficult to explain, it becomes worse to maintain.