195 Structuring CSS with the BEM pattern [4d]

Updated . Posted . Visible to the public.

We are using the BEM pattern ("Block, Element, Modifier") to structure our CSS in all new projects. We try to migrate legacy projects to BEM, block-by-block.

Important

Work on this lesson in teacher mode.

Learning goals

  • You can explain why unstructured CSS becomes hard to maintain — global scope, cascading side effects, selectors coupled to markup — and what a naming convention like BEM does about it.
  • You can explain the three BEM concepts — block, element, modifier — and write our naming convention for each: .block, .block--element, .block.-modifier.
  • You can explain how a BEM naming convention prevents accidental style inheritance, e.g. a .title rule of one component bleeding into nested or unrelated markup.
  • You can decide where to draw the line between blocks: when something is a reusable block of its own and when it is an element of a larger block. You cut blocks by responsibility, like well-designed Ruby classes.
  • You can build a screen from a small set of reusable blocks, with few elements and no one-off wrappers that exist only for styling.
  • You keep a block's own styling separate from its positioning on a particular screen.
  • You can space blocks consistently with one of our two spacing variants, without inventing a spacing container for a single screen.
  • You can recognize BEM-structured markup (and its absence) by inspecting a page.
  • You can migrate existing CSS to BEM step by step, block by block.

Resources

Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.

Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.

Cutting block boundaries

Deciding what becomes a block is the hardest part of BEM — the naming is easy. Cut blocks by responsibility, the way you cut Ruby classes: each block does one thing. Neither extreme works: one god block serves no second screen, and blocks cut too small can't stand on their own without wrappers.

It is often a good idea to have a dedicated layout block whose only responsibility is dividing the screen between global regions — typically things like a top bar, a sidebar or the main content area. Content blocks like a navigation or a list then only care about their own contents — how their items are displayed, what the current item looks like — and never about their own position on the screen.

Avoid unnecessary wrappers: use as few HTML elements as possible, but not fewer. A one-off wrapper that exists only for styling is usually a sign of a wrong cut.

A useful check: try to explain to someone else what a block does. If you find yourself meandering through a convoluted explanation with tons of context, or chaining responsibilities with "and" again and again, you probably made the wrong cut.

Rules for blocks and elements

Hard rules

These rules apply in every project. They keep each block independent, so it works wherever you place it, and changing it never breaks another block.

  • A block styles only itself, never its surroundings. It works on its own and makes no assumptions about where it is placed.
  • Once a name belongs to a block, no other block's CSS references it.
  • Elements are direct children of their block — never children of another element, not even of an element of the same block.
  • Elements position themselves relative to their block or their siblings. They don't define layout (display, position); only blocks do. display: none is always fine.
  • Blocks may contain other blocks. A block can be an element of its parent at the same time, e.g. .day-row.week--day.
  • You may always define a new block.

Project variation: spacing between blocks

Projects handle the space between blocks in one of two ways. Pick one per project and use it consistently.

  • Blocks bring their margins. A block has default outer margins, and utility classes adjust or remove them case by case, e.g. .mt-0, .mb-0, .my-0.
  • Blocks bring no margins. Spacing comes from one generic .stack block with modifiers for the gap size (.stack.-gap-1, .stack.-gap-2, …), or from the parent block when that parent is a real block anyway, like a list that spaces its items.

In both variants, gap sizes come from a small scale (see CSS basics), and you never invent a new spacing container for a single screen.

Softenings

Applied to the letter, BEM can cost more markup and time than it saves. For pragmatism and productivity, we allow the following exceptions. They are safe as long as their effect stays contained within a block. When in doubt, be strict.

  • A block may style the plain tags inside it without giving each one a class, especially cohesive structures like ul/li, table/tr/td or dl/dt/dd. Prefer child selectors (.block > li), and keep the effect contained: if your design nests such structures, like a list inside a list, give the descendants element classes after all. Give a descendant an element class also when it needs one anyway, e.g. for a modifier like the current item.
  • Libraries bring their own markup and class names, like Bootstrap's .card-body or Unpoly's <up-modal> elements. We don't rename or wrap them; our own blocks still follow our convention.
  • A base stylesheet or a top-level typography file may give plain tags modest defaults: sizes and weights for headings, bold for b and strong, vertical margins for headings, paragraphs and lists. Keep it to size, weight and vertical spacing — colors, borders, backgrounds and layout belong to blocks.
  • ::before and ::after may insert decoration instead of extra elements in the HTML.
  • Pseudo-classes like :hover or :first-child may replace a modifier.
  • Media queries may replace a modifier that JavaScript would otherwise set.

Exercises

Deskbot

Re-implement the look of this screen from Deskbot, the hot-desking app you use every day to book tables, parking and food:

Image

  • Work in your exercises repository, with static HTML and CSS files — no app framework for this one.
  • Invent enough fake weeks and days to lay out the list with real-looking data. Notice how the day rows come in several states — today, booked, absent — and how the same goes for the toggles.
  • Make sure that your implementation uses a clear set of BEM blocks with the right boundaries (see Cutting block boundaries and Rules for blocks and elements above). Blocks like the day rows should be reusable for other screens.
  • In your README, name the spacing variant you chose, and list each block with its responsibility in one sentence. If a sentence needs several "and"s, reconsider the cut.

What counts here is your HTML and CSS structure — we want to see how you cut. The page should look roughly like the screenshot: close enough to prove your HTML structure can carry the real layout. The test: pixel perfection must be reachable later mostly by editing CSS. Adding a layout container is fine; changing which blocks exist, or what they contain, is not. You don't need responsive behavior, JavaScript, or hover and animation polish.

For rows and columns, use the Flexbox basics from CSS basics. The next card goes deeper into Flexbox.

Note

  • You have access to Deskbot's repository, but don't peek at its actual HTML and CSS — it is a Bootstrap frontend, not BEM, so you would copy the wrong structure anyway.
  • You are likely to have trouble deciding where to draw the line between the blocks. E.g. a structure could either be two independent blocks or a block that contains two elements. There is not always a right answer to that. Discuss the pros and cons of each approach with your mentor.

MovieDB

Restructure the views and CSS of MovieDB so every screen is built from a small set of reusable BEM blocks:

  • Cut by responsibility and follow the rules (see Cutting block boundaries and Rules for blocks and elements above): a layout block owns the screen division; content blocks never position themselves.
  • Pick one of the two spacing variants and use it on every screen.
  • No one-off wrappers that exist only for styling.
  • In your merge request description, name the spacing variant you chose, and list each block with its responsibility in one sentence.
Profile picture of Henning Koch
Henning Koch
Last edit
Henning Koch
Attachments
Keywords
advanced
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra Curriculum (2015-09-08 16:23)