CSS variables, organization and naming
Your stylesheet is growing. The same teal appears in twenty places, and changing it means hunting through the whole file. Professionals solve this with variables and a clear organization. In this lesson you will learn CSS custom properties, how to structure a stylesheet, how to name classes in a consistent way, and how to approach native CSS nesting prudently.
The problem with repetition
Look at your CSS so far. The same colors, spacing values and font stacks are written many times. If you decide your accent color should be a little darker, you must find and change each occurrence, and you will certainly miss one. Repetition is the enemy of maintenance.
Custom properties (CSS variables)
A custom property is a value that you name once and reuse everywhere. Its name starts with two hyphens. You read it with the var() function.
:root {
--color-primary: #0f766e;
--color-primary-dark: #115e59;
--color-text: #1f2937;
--color-surface: #ffffff;
--radius: 0.5rem;
--space: 1rem;
}
.button {
background-color: var(--color-primary);
border-radius: var(--radius);
padding: var(--space) calc(var(--space) * 2);
}
.button:hover {
background-color: var(--color-primary-dark);
}
The :root selector represents the top of the document (the html element), so variables defined there are available everywhere. Change --color-primary once, and every use updates. You can also do arithmetic with calc().
Fallbacks and scope
A variable can have a fallback value, used if the variable is not defined:
color: var(--color-accent, #f59e0b);
Variables follow inheritance and the cascade, like any property. That means you can redefine them locally, and everything inside that element updates:
.hero {
--color-text: #ffffff;
--color-surface: #134e4a;
background-color: var(--color-surface);
color: var(--color-text);
}
Inside .hero, anything using var(--color-text) now sees white. This is far more powerful than the variables of a preprocessor, because they live in the browser and can change at runtime.
Themes in a few lines
Thanks to this local redefinition, a dark theme is simple. Define the colors as variables, then override them for visitors who prefer a dark interface:
:root {
--bg: #f9fafb;
--text: #1f2937;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827;
--text: #e5e7eb;
}
}
body {
background-color: var(--bg);
color: var(--text);
}
When adding a dark mode, check the contrast of every color pair again: it does not stay good automatically.
What to put in variables
Not everything deserves a variable. Create them for values that:
- are reused in several places (colors, spacing scale, radius, shadows, font stacks);
- are likely to change together (a brand color);
- make the design system explicit (a spacing scale such as
--space-1,--space-2…).
Name them by role rather than by value: --color-primary is better than --teal, because if the brand turns purple the name is still right.
Organizing your stylesheet
A single CSS file can stay manageable if it follows a predictable order. A common structure, from general to specific:
- Variables on
:root. - Reset and base:
box-sizing, body defaults, headings, links, images. - Layout: the container, header, footer, grids.
- Components: buttons, cards, navigation, forms, each in its own section.
- Utilities: tiny helpers such as
.visually-hidden. - Media queries: either next to each component, or grouped at the end.
Separate sections with a comment banner so that you can find them quickly:
/* ==========================================================
Components: project card
========================================================== */
This order also works with the cascade: general rules come first, specific ones later, and ties are resolved in favor of the more specific.
As a site grows, you can split CSS into several files (base.css, layout.css, components.css) and link them in order. For a small portfolio, a single well-organized file is simpler and loads with one request.
A small reset
Browsers apply default styles that differ slightly. A minimal reset gives you a predictable starting point:
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 0;
line-height: 1.6;
}
img, video {
max-width: 100%;
height: auto;
display: block;
}
h1, h2, h3 {
line-height: 1.2;
}
Do not remove everything: browser defaults such as list bullets and focus outlines exist for good reasons.
Naming classes: the BEM convention
Naming is hard, and inconsistent names create bugs. BEM (Block, Element, Modifier) is a popular convention that makes the relationships between classes obvious:
- Block: a standalone component, such as
card. - Element: a part of a block, written with two underscores:
card__title,card__image. - Modifier: a variation or state, written with two hyphens:
card--featured.
<article class="card card--featured">
<img class="card__image" src="images/street-01.jpg" alt="...">
<h3 class="card__title">Street portraits</h3>
<p class="card__text">A series taken in my neighborhood.</p>
</article>
.card { background: var(--color-surface); border-radius: var(--radius); }
.card__title { margin-top: 0; }
.card--featured { border: 2px solid var(--color-primary); }
The benefits: every selector is a single class (low, flat specificity as recommended earlier), the names show where a class belongs, and a style for card__title cannot accidentally leak into other components. You do not have to adopt BEM, but choose a convention and apply it consistently. Other tidy habits help: use lowercase and hyphens, avoid names based on appearance, and avoid styling by tag name deep in the markup.
Native CSS nesting, with caution
CSS now supports writing rules inside other rules, which reduces repetition:
.card {
padding: 1rem;
& h3 {
margin-top: 0;
}
&:hover {
box-shadow: 0 6px 18px rgb(0 0 0 / 18%);
}
@media (min-width: 40em) {
padding: 2rem;
}
}
The & refers to the parent selector. This feature is supported by current browsers, but older versions do not understand it, and an unsupported rule is silently ignored, so the card would lose its styles entirely. Before you rely on it, check the current compatibility tables on MDN and consider who your visitors are. Nesting also makes it easy to create overly deep and specific selectors: keep it to one or two levels. For a first site, plain, flat CSS with BEM-style classes is perfectly fine and safe everywhere.
Other tidy habits
- Delete unused rules from time to time: dead CSS adds weight and confusion.
- Prefer logical property names such as
margin-inlineandpadding-blockwhen they help; they adapt to writing direction. - Use shorthand properties, but know what they reset:
backgroundresets all background sub-properties. - Comment the why, not the what: “keeps the header above the cards” is helpful, “sets color to teal” is not.
Tip. In the developer tools, the Computed pane shows the resolved value of a variable, and the Styles pane shows var(--name) with the value on hover. You can also edit a variable on :root live and watch the entire page update.
Warning. Variables are case-sensitive: --Color-Primary and --color-primary are different. A misspelled variable name does not produce an error; the declaration just becomes invalid and the property falls back to its inherited or default value.
Your turn
- Create a
:rootblock at the top ofstyle.csswith variables for your three main colors, a text color, a surface color, a radius and a spacing unit. - Replace the repeated values in your existing CSS with
var(). - Rename your project card classes with BEM:
card,card__title,card--featured. - Reorganize the file into labeled sections: variables, base, layout, components, utilities.
- Change one variable and observe the entire site update.
See a solution
:root {
--color-primary: #0f766e;
--color-primary-dark: #115e59;
--color-accent: #f59e0b;
--color-text: #1f2937;
--color-surface: #ffffff;
--color-bg: #f9fafb;
--radius: 0.5rem;
--space: 1rem;
}
body {
color: var(--color-text);
background-color: var(--color-bg);
}
.card {
background-color: var(--color-surface);
border-radius: var(--radius);
padding: calc(var(--space) * 1.5);
}
.card--featured {
border: 2px solid var(--color-primary);
}
In the HTML, update the classes to card card--featured for the featured project. When everything is migrated, changing --color-primary should recolor links, buttons and the featured card at once.
Key takeaways
- Custom properties (
--name, read withvar()) remove repetition and can be redefined locally or in media queries. - Name variables by role (
--color-primary), not by value. - Organize a stylesheet from general to specific: variables, base, layout, components, utilities.
- BEM (
block__element--modifier) keeps class names consistent and specificity flat. - Native CSS nesting is useful but check browser support first, and keep it shallow.