RizTech Academy logo
RizTech Academy
Best PracticesLesson 1 of 535 min

Naming classes, and what a component is

CSS has no modules, no imports and no private scope. Every class name you write is in one global namespace shared by every stylesheet on the page. Naming is therefore not tidiness — it is the only scoping mechanism you have.

The name says what it is, not what it looks like

.red-text { color: #b3261e; }
.big { font-size: 2rem; }
.mt-20 { margin-top: 20px; }
.blue-button { background: #1a4d2e; }

Every one of those will lie to you. The design changes, the error colour becomes orange, and now you have .red-text { color: orange } — or, worse, somebody adds .orange-text and both exist. The last one is already lying: a .blue-button that is green.

.error { color: var(--colour-error); }
.section-title { font-size: var(--step-3); }
.field + .field { margin-block-start: var(--space-3); }
.button--primary { background: var(--colour-accent); }

Name it for its role in the page. Then a design change is a value change, and the name stays true.

The test: could this name survive a redesign? .error survives any colour. .red-text survives nothing.

The same argument as the shared package

Module 6's two-layer tokens were the same idea one level down: --green-700 is what a colour is, --colour-accent is what it does, and components reference only the second. Class names are the same distinction applied to elements.

A naming convention, and why any of them works

You will meet several. The important thing is picking one, not which.

BEM is the most common:

.card            { }        /* block: a standalone component */
.card__title     { }        /* element: a part of it */
.card--featured  { }        /* modifier: a variant */

Two underscores for a part, two hyphens for a variant. It is ugly and it is unambiguous — you can tell from any class name alone what it is and where it belongs, which is the whole point.

<article class="card card--featured">
  <img class="card__image" src="…" alt="…">
  <div class="card__body">
    <h3 class="card__title">Chakki Fresh Atta</h3>
    <p class="card__price">₹450</p>
  </div>
</article>

What BEM buys, concretely:

Flat specificity. Every rule is one class, 0,1,0, so anything can override anything by being later. Compare with .card .body h3, which is 0,2,1 and breaks when somebody adds a wrapper.

Searchability. grep card__ finds every part of the component.

No nesting dependence. .card__title works wherever it sits, so restructuring the HTML does not break the CSS.

The cost is verbosity, and .card__body__title — three levels — is a sign the component should be two components. BEM is deliberately only two levels deep.

The alternative worth knowing

Utility classes — Tailwind's approach — go the other way:

<div class="flex gap-4 p-4 rounded-lg border border-gray-200">

No naming at all, because there are no component names. It genuinely solves the naming problem and the specificity problem, and it moves the information into the HTML, which becomes long and which some people find unreadable.

It is a legitimate approach and worth understanding. Learn to write your own CSS first. A utility framework is much easier to use well once you know what it is generating, and much easier to misuse if you never wrote the underlying CSS.

What a component is

The question that decides your structure, and there is a usable test.

A component is something you could put anywhere on the page and it would still make sense. A card, a button, a field, a navigation bar, a price badge.

Not a component: a layout position. .homepage-left-box is not a component; it is where something sits. Separate the two:

<!-- the layout owns position; the component owns appearance -->
<div class="layout__sidebar">
  <article class="card">…</article>
</div>
.layout__sidebar { grid-area: side; }
.card { padding: 1rem; border: 1px solid var(--colour-border); }

A component should not know where it is. No margin on its outside edge, no width, no position. Those belong to whatever placed it — otherwise the card that works in the sidebar needs a variant for the main column, and you are back to .card--in-sidebar.

This is the same argument module 4's container queries made from the other side: a component adapting to its slot rather than being told about the page.

Margins belong to the parent

The practical version, and it is worth adopting as a rule:

/* wrong: the card decides its own spacing */
.card { margin-block-end: 1.5rem; }

/* right: the container decides */
.card-list { display: grid; gap: 1.5rem; }

gap is better than margins between children in every way — it never collapses, it never leaves a stray margin on the last item, and it means the component is reusable.

When gap is not available, the owl:

.stack > * + * { margin-block-start: var(--space-3); }

"Every child that follows another child." No margin on the first, nothing to reset on the last, and the spacing is the container's decision.

Files

styles/
  main.css              the only file the HTML links
  base/
    reset.css
    tokens.css          custom properties
    typography.css
  layout/
    page.css
    container.css
  components/
    button.css
    card.css
    field.css
    nav.css
  utilities/
    visually-hidden.css
/* main.css */
@import url("base/reset.css") layer(reset);
@import url("base/tokens.css") layer(base);
@import url("components/card.css") layer(components);

One file per component, named for the component. Then finding a card's CSS is obvious and two people can work on two components without conflicting.

Two warnings about @import:

Plain @import is a serial round trip. The browser must fetch main.css, parse it, discover the imports, then fetch each one — so a page with ten imports waits for eleven round trips before it can paint, which module 1's lesson explains the cost of. On a mid-range Android on mobile data that is real.

So in production, concatenate into one file. Any build tool does it; for a hand-written site you can simply write one file with clear section comments, which is what the capstone does.

The layer() syntax in the import is the exception worth knowing: it lets you assign cascade layers at import time, which the next lesson uses.

Naming files and URLs

From module 1, restated as a rule because it causes a deployment failure:

about-us.html          good
About-Us.html          works on your Mac, 404s on Linux
about us.html          becomes about%20us.html
aboutUs.html           works, and nothing else on the web is camelCase

Lowercase, hyphens, no spaces. Applies to HTML files, CSS files, image files and folder names. The single most common first-deploy failure, and it costs you nothing to avoid.

Names to avoid

Avoid Because
.wrapper, .container, .box every project has six of them and they mean nothing
.left, .right, .top they lie the moment the layout changes, and in RTL
.style1, .style2 a filing cabinet with unlabelled drawers
.mt-20, .p-15 a value in the name; also a lie once you change it
.homepage-thing ties a component to one page
.temp, .new, .old all three become permanent
#main-content for styling an id's specificity, which only an id can override

.container is the one exception worth allowing, because it has a settled meaning — the centred, max-width page wrapper — and everybody recognises it. Use it for exactly that and nothing else.

Consistency beats correctness

The last point, and the most practical.

A codebase consistently using a convention you mildly dislike is far easier to work in than one where three conventions coexist because three people each knew better. If you join a project using something you would not have chosen, use what is there.

If you are starting fresh: BEM-ish names, one class per rule, one file per component, tokens for every value. Write it down in the README so the next person has something to be consistent with.

Check your work

Why naming is not tidiness in CSS. One global namespace, and no other scoping mechanism.

The naming test. Could this name survive a redesign?

What BEM's three parts mean. Block, element with __, modifier with --.

Three things BEM buys. Flat specificity, searchability, and independence from nesting.

What .card__body__title means. That it should be two components — BEM is two levels.

What utility classes trade. The naming and specificity problems, for long HTML — and learn to write your own CSS first.

The component test. Could it go anywhere on the page and still make sense?

What a component must not know. Where it is — no outer margin, width or position.

Why gap beats margins between children. No collapsing, no last-child reset, and the component stays reusable.

What the owl selector does. Spaces every child after the first, with nothing to reset.

Why plain @import is costly. Serial round trips before the page can paint.

The file-naming rule. Lowercase, hyphens, no spaces — Linux is case-sensitive and your Mac is not.

The one acceptable generic name. .container, for exactly the centred page wrapper.

What beats being right. Consistency with whatever is already there.

Practice

  1. Find every class in your own CSS named for an appearance. Rename them for their role.
  2. Change your error colour to orange and count how many class names became lies.
  3. Convert one component to BEM. Count the specificity of every rule before and after.
  4. Write .card__body__title and then restructure it into two components.
  5. Take a component with .card .body h3 and add a wrapper div. Watch it break, then fix it with flat classes.
  6. Find a component with a margin on its outer edge. Move the spacing to its container.
  7. Replace margins between list children with gap, then with the owl selector.
  8. Put the same component in two different layout positions with no modifier class.
  9. Split your CSS into one file per component with @import, then measure the requests in the Network tab. Then concatenate and measure again.
  10. Rename a file to About-Us.html, link to it as about-us.html, and deploy.
  11. Search your CSS for .wrapper, .container and .box. Decide what each actually is.
  12. Find an id used for styling and work out what would be needed to override it.
  13. Write a three-line naming convention in your project's README.
  14. Look at any open-source CSS codebase and identify which convention it uses.

Official documentation

Next: organising the stylesheet so specificity stops being a fight.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship