Reusable Components
A reusable UI component is a self-contained, themeable building block — like a button or card — built with consistent class naming such as BEM and CSS custom properties as its public API, so you can drop it in anywhere and restyle it without rewriting it.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free HTML & CSS course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
By the end of this lesson you'll be able to design a small, themeable component system — a button with variants and sizes, and a compound card — using BEM naming, modifier classes, and CSS custom properties as the component's public API.
A reusable component is like a LEGO brick. A single 2×4 brick is one shape (the base ), but it comes in red, blue, or green ( variants ) and in tall or short heights ( sizes ). You design each brick type once, then snap copies together into any structure — you never re-mould a brick for every model.
The studs on top are the brick's API : a fixed, documented way to connect to it. Your component's CSS custom properties are exactly that — a small set of named knobs ( --primary , --radius ) that anyone can turn from the outside, without prising the brick open to see how it's made.
A component system follows one consistent shape: a base class that holds the shared structure (layout, spacing, corner radius), plus small modifier classes that change one visual thing each — a colour, a size, a state. You put several classes on one element and they compose: class="btn btn--primary btn--lg" .
The win is that the base stays written once. Five colours times three sizes is eight tiny classes that stack, not fifteen fat ones that each repeat the padding and radius. BEM naming keeps this readable: block (the component), block__element (a part inside it), and block--modifier (a variant or state).
Here is the pattern in full. The .btn base sets layout, padding, and radius and no colour at all . Each .btn--* modifier touches only the properties it owns, so colour, size, and state never fight each other and can be combined freely. Every colour and the radius come from :root tokens — that block is the button's theming API.
A card is a compound component : a block made of several named parts — .card__header , .card__body , .card__footer . Each part is optional , so the same block works whether a card has all three or just a body. Notice the selectors stay flat — one class per part — instead of brittle chains like .card div p , so a card's styles never leak into content you drop inside it.
The .btn base is done. Add a success colour variant and a small size, then apply both to a button. Fill in the blanks marked ___ and run it.
This card has a body but no footer, and no way to highlight it. Add a .card__footer element and a .card--accent modifier, then use them in the markup.
Support is faded now — only an outline is given. Build a small badge component (a pill label) with a base class and colour variants, driven by tokens. Use the button worked example as your reference if you get stuck.
You can now design small, themeable component systems instead of one-off styles. The essentials:
Next up: Canvas , where you'll draw and animate graphics directly in the browser.
Practice quiz
What do the three parts of BEM stand for?
- Base, Element, Method
- Box, Edge, Margin
- Block, Element, Modifier
- Block, Engine, Module
Answer: Block, Element, Modifier. BEM = Block (the component, .card), Element (a part inside it, .card__title), and Modifier (a variant or state, .card--featured).
In BEM, which separator marks an element (a part inside a block)?
- A double underscore, like .card__title
- A single dash, like .card-title
- A double dash, like .card--title
- A dot, like .card.title
Answer: A double underscore, like .card__title. Elements use a double underscore: .card__header. A double dash (--) marks a modifier; .card-header reads as a separate block.
In BEM, which separator marks a modifier (a variant or state)?
- __ (double underscore)
- A single underscore
- A slash
- -- (double dash), like .btn--primary
Answer: -- (double dash), like .btn--primary. Modifiers use a double dash: .btn--primary, .card--accent. They describe a variant or state of the block.
What is the recommended split for a component's styles?
- One giant class per combination
- A base class for shared structure plus small modifier classes that each change one thing
- Inline styles only
- ID selectors for every part
Answer: A base class for shared structure plus small modifier classes that each change one thing. A base class holds the structure (layout, spacing, radius) and small modifiers each change one visual thing, so they compose freely.
How do you combine a variant and a size on the same button?
- Stack classes: class="btn btn--primary btn--lg"
- Create one .btn-primary-lg class
- Use an ID with both
- Nest the buttons
Answer: Stack classes: class="btn btn--primary btn--lg". Put multiple classes on the element. The base, the variant, and the size each touch only their own properties, so they compose cleanly.
Why prefer flat selectors like .card__body over .card div p?
- They render faster only
- Descendant selectors are invalid
- .card div p is fragile, leaks into nested content, and has higher specificity that's hard to override
- Flat selectors look nicer
Answer: .card div p is fragile, leaks into nested content, and has higher specificity that's hard to override. Deep descendant chains catch unrelated nested elements, break when markup changes, and raise specificity. One flat class per part is predictable.
What serves as a reusable component's public theming API?
- Inline style attributes
- CSS custom properties (tokens) declared on the component or :root
- IDs
- !important rules
Answer: CSS custom properties (tokens) declared on the component or :root. Custom properties like --primary and --radius are the small, documented knobs anyone can override to re-theme the component without editing its CSS.
Why should the base class hold structure but NOT colour?
- Colour is invalid on base classes
- Browsers ignore colour on base classes
- It makes the file smaller
- So variants can set colour cleanly without fighting a value the base already applied
Answer: So variants can set colour cleanly without fighting a value the base already applied. If the base sets a colour, every variant has to override it. Keeping the base structural lets each --modifier own exactly the properties it changes.
What is the difference between a variant class and a state class?
- They are the same thing
- A variant is a design choice you pick (.btn--primary); a state reflects what's happening now (disabled, error)
- States can't use modifiers
- Variants only apply on hover
Answer: A variant is a design choice you pick (.btn--primary); a state reflects what's happening now (disabled, error). Variants are chosen when you place the component; states reflect current status and often map to attributes/pseudo-classes (:disabled). Keeping them separate lets them compose.
What is the benefit of declaring --primary once and using var(--primary) everywhere?
- It disables dark mode
- It improves load speed only
- Rebranding is a single edit instead of many find-and-replace changes
- Variables can't be overridden
Answer: Rebranding is a single edit instead of many find-and-replace changes. A token defines the value in one place, so changing the brand colour is one edit — and consumers can re-theme by overriding the variable on a parent.