Start with a component boundary

A component’s styles should make it clear which elements they belong to. Consider a reusable notice with a heading, message, and optional action. In an HTML document, give its outer element the class notice, then give its internal elements classes such as notice__title, notice__body, and notice__action. The examples here use standard CSS and custom properties supported by modern browsers; they do not require a framework, CSS Modules, or a build step.

Write rules against those classes rather than broad selectors such as h2, p, or button. For example, .notice { padding: 1rem; border: 1px solid; } establishes the container, while .notice__title { margin: 0; } affects only titles deliberately assigned that class. This is a naming convention, not true CSS isolation: another stylesheet can still target the same elements. Its benefit is that ownership is visible when reading the markup and styles.

Keep selectors predictable

It can be tempting to describe every detail through descendant selectors, such as .notice h2 or .notice button. That works until the component gains a second heading or a button inside a nested component. Explicit part classes let you style the intended element without depending on its tag name or position. They also make it easier to change a heading’s HTML level while preserving its appearance.

Use a separate class for meaningful variants. A notice might have both notice and notice--warning on its outer element. The base class supplies shared layout; the variant changes only what differs. For example, .notice--warning { --notice-accent: #915600; } updates an accent value without repeating padding and spacing rules. Keep variant names about purpose rather than appearance where possible: warning communicates more than orange if the design later changes.

Specificity matters even with careful names. A rule like .notice .notice__title is more specific than .notice__title and can make later overrides harder. Start with simple class selectors unless a relationship is necessary. When two rules have equal specificity, source order decides which applies, so group component rules together and place intentional overrides after defaults.

Use custom properties for controlled variation

Custom properties let a component use values supplied by its surroundings. Define defaults on the root class: .notice { --notice-accent: #345b83; --notice-surface: #f3f7fb; background: var(--notice-surface); border-left: 0.25rem solid var(--notice-accent); }. A child can reuse the accent with .notice__title { color: var(--notice-accent); }. A page or variant can then change one value without rewriting every rule that uses it.

These properties inherit, which is useful for theming but worth planning for. If a parent sets --notice-accent, a notice inside it can receive that value. Defining a default directly on .notice gives each notice its own starting value; an author who wants to override it can set a value on that notice or use a more specific intentional rule. Choose property names such as --notice-accent instead of --accent when you want to reduce accidental interactions with other components.

A fallback in var(), such as var(--notice-accent, #345b83), applies when the custom property has no usable value. It does not rescue every invalid value: if --notice-accent is set to a value that is unsuitable for color, the resulting color declaration can still be invalid. Keep the values you expose limited and document what kind of CSS value each one expects.

Account for nested components and shared styles

A notice may contain a link or even another component. Avoid selectors such as .notice button that could style a nested component’s button by accident. Assign notice__action only to an action owned by the notice. If a part needs a reusable button component, let that button own most of its styling and use a small layout rule on the notice to position it.

Custom properties can cross component boundaries through inheritance. This is helpful when a page-wide theme sets shared tokens, but a nested component should not have to interpret --notice-accent. Give each component its own property names and decide deliberately which page-level tokens it reads. Classes establish a practical ownership boundary; they do not stop the CSS cascade or inheritance. Treat that distinction as part of the component’s design rather than as a problem to hide.

Test the boundary in a real page

Place two notices on one page: a default notice and a warning notice. Add ordinary headings and buttons outside them, and put another styled component inside one notice. Check that the outside elements remain unchanged, the nested component retains its own styles, and the two notices can use different accents. In browser developer tools, inspect a title’s computed styles if a color or margin is surprising; the matched rules and inherited custom properties usually reveal where the value came from.

Also check the states the component actually supports. Long text may wrap around an action, and a warning accent may need a readable foreground against its surface. Do not rely on color alone to communicate a warning; include a visible label or clear wording. A small set of explicit classes and well-named properties makes these checks manageable while leaving room for future variants.

Back to category ↗