TS-40: CSS
This technical standard specifies a CSS architecture — a design methodology built on user-land conventions such as BEM, SMACSS, and OOCSS — that helps control the cascade and produce maintainable, scalable user interfaces.
It defines naming conventions and a system for separating the different concerns of a web interface: layout, elements, components, and modifiers.
The CSS architecture described herein applies equally whether stylesheets are hand-written, compiled from a preprocessor like Sass or Less, or generated by a CSS-in-JS framework.
Contents
Overview
CSS applies styles in a cascading fashion. Most properties set on an element trickle down to its descendants, and sideways to other elements of the same type. This inheritance model is intrinsic to CSS and cannot be turned off.
In small user interfaces the cascade is beneficial. A property set once trickles through to every relevant part of a design, producing small file sizes, efficient rendering, and consistent interfaces. But the cascade tightly couples the presentation of every component on a page. A change to one thing has the potential to alter the presentation of unrelated things. The bigger the project, and the more collaborators, the more the cascade becomes a hindrance rather than a help. CSS does not scale well for richly formatted, rapidly iterated websites.
Extended CSS languages (Sass, Less, Stylus, et al.) do not fix this scalability problem on their own. Left unchecked, they encourage code duplication and deep selector nesting. What is actually needed is a proper scoping mechanism — a standardized way to encapsulate styles within fragments of markup, so that complex designs can be split into independently developed and maintained modules of HTML, CSS, and optionally JavaScript.
CSS now has a native scoping mechanism: the @scope at-rule, supported by Chrome, Edge, Firefox, and Safari --- as of
2024. The wider Web Components platform also offers style encapsulation via the Shadow DOM API, though that is not
part of CSS itself.
Native scoping does not remove the need for a design methodology. @scope confines a style sheet’s selectors to a
fragment of markup, but it does not, by itself, provide naming conventions, dictate how content, layout, and behavior
should be separated, or establish a shared vocabulary for a team. Those benefits — easier maintenance, predictable
specificity, safer refactoring, and reusable, composable building blocks — come from imposing discipline on how CSS
is written, not merely on where it applies. That discipline is a web design methodology.
The same is true of tooling-provided scoping. A CSS-in-JS framework or a CSS Modules build step generates unique
class names automatically, so authored selectors never collide — but generated uniqueness is not a substitute for
this standard’s naming conventions, since it says nothing about whether a given class is a layout section, a
component, or a modifier, nor how the concerns of a design should be separated across files. This standard’s naming
and separation-of-concerns conventions apply unchanged regardless of how the resulting class names are generated or
scoped: a CSS Modules .card or a CSS-in-JS styled.div is still, architecturally, a component, and still MUST
follow the same CamelCase and single-responsibility conventions as a hand-written one (see
Class names) — the tooling changes how the resulting class name reaches the DOM, not what role that
class plays.
What a web design methodology is
A web design methodology is a system for applying CSS to HTML. Its primary aim is to make it possible to break up complex web designs into smaller, simpler modules of HTML and CSS (and optionally JavaScript) that can be developed and maintained independently. A methodology MAY also define class naming conventions, dictate syntax and formatting, describe commenting and documentation practices, and determine the source order of code.
A web design methodology is not a visual style guide or UI pattern library. It is not about tooling (IDEs, preprocessors, linting), specific libraries or frameworks, or team workflow (agile, pair programming, peer review). These things MAY complement a methodology’s day-to-day implementation, but they are not substitutes for one.
A good web design methodology:
- Makes the design process faster.
- Makes front-end code easier to read and understand.
- Makes it easier for multiple people to collaborate on a design.
- Reduces the learning curve for new contributors.
- Makes it easier to change things, and to refactor safely, since well-scoped rules can be changed or removed without unintended side effects elsewhere on the page.
- Makes it easier to scale a design to accommodate more content, functionality, and variety.
- Makes it easier to reuse things that already exist in a codebase.
- Makes it easier to port UI components between projects.
- Encourages well-formed, accessible, standards-compliant code.
- Enforces consistency, and limits duplication and bloat.
- Produces predictable specificity, avoiding the need for
!importantor ever-deeper selector chains to override styles. - Reduces page size and increases rendering speed.
- Provides ready-made documentation.
A methodology is essential for large, long-running projects, or where team members have widely differing skills or there is high staff turnover. Without one in place from the outset, a codebase can rapidly become unmanageable.
Several web design methodologies have been published, including Nicole Sullivan’s Object-Oriented CSS (OOCSS), Jeremy Clarke’s DRY CSS, Jonathan Snook’s Scalable and Modular Architecture for CSS (SMACSS), and Nicolas Gallagher’s SUIT CSS --- see References. There is no single "best" methodology. Different approaches suit different projects. What matters more than the specifics of any one methodology is the consistency with which it is applied within a codebase.
This technical standard does not prescribe conventions for CSS syntax formatting, or for comments and documentation. How code is formatted and documented is a project-level decision. Instead, this standard prescribes class naming conventions and a system for separating the different concerns of a web interface: layout, elements, components, and modifiers.
It is a pure CSS methodology. It applies equally whether a project uses plain CSS or an extended syntax such as Sass or Less.
Principles of good CSS
All web design methodologies are based on similar principles, though emphasis varies from one to another. This section sets out the principles behind the conventions defined in the rest of this standard.
Embrace the constraints of the cascade
To reduce the influence of the style cascade, some methodologies give every element a class and assign styles exclusively via classes rather than element types. So instead of this:
ul {
color: hsl(0, 0%, 27%);
font-size: 1.2rem;
list-style: square inside;
margin: 0 0 1em;
}
nav ul {
color: initial;
font-size: 1rem;
list-style: none;
margin: 0;
}They do this:
.list {
color: hsl(0, 0%, 27%);
font-size: 1.2rem;
list-style: square inside;
margin: 0 0 1em;
}
.nav-list {
font-size: 1rem;
}Using classes as the exclusive contract between HTML and CSS gives granular control over presentation, decouples selectors from particular HTML structures, and avoids the need to unwind unwanted inherited styles. But it also litters HTML documents with classes, and forgoes the convenience and efficiency of the inheritance model. Class-heavy designs also tend toward more rulesets overall, giving browsers more work to resolve final styles.
Note
Greater dependence on classes does not increase coupling between HTML and CSS. Classes exist for exactly this purpose. They are an abstraction layer linking two separate concerns, the equivalent of hooks in event-driven programming.
There is a balance between harnessing the cascade (assigning default properties to element types) and locking things down with classes. The default position SHOULD favor the cascade. A project SHOULD start by defining default styles for base typography, hyperlinks, tables, input controls, buttons, and every other visible element used in the theme. Resetting inherited styles from time to time is an acceptable cost. If resets become too frequent, iterate in a restrained way toward fewer default properties on type selectors and greater reliance on classes.
Resetting is done with one of CSS’s own inheritance keywords, valid as the value of any property:
inherit— takes the computed value from the parent element, even for a property that does not normally inherit.initial— resets to the property’s specification-defined default, ignoring both inheritance and any style sheet rule.unset— acts asinheritfor a property that naturally inherits (eg.color), or asinitialfor one that does not (eg.border) — the usual choice, since it does not require knowing which behavior a given property has.revert— resets to the browser’s built-in style for the element, rather than to the property’s specification default; rarely needed in this standard’s default-cascade-first approach, since there is normally no competing style sheet rule to revert past.all— a pseudo-property applying any of the above to every property at once, useful for fully isolating a component from unwanted inherited or cascaded styles (all: unset;followed by the component’s own declarations).
.Widget {
all: unset;
display: block;
font: inherit;
}Embracing the cascade also keeps visual design in check. It encourages style guides built around the limited set of native HTML elements, rather than the infinite possibilities afforded by classes.
Encapsulation
The style cascade makes everything global. The browser evaluates every selector in every style sheet against every new element it renders, so any ruleset can potentially apply to anything on the page. This is what makes CSS leaky.
Traditionally, styles have been scoped to fragments of HTML --- so preventing styles from leaking in and out of distinct parts of a UI --- using classes as namespaces. Example:
.sidebar a {
background: hsl(195, 5%, 80%);
color: hsl(0, 0%, 100%);
padding: 0.25em 0.5em;
text-decoration: none;
}The sidebar class namespaces styles to hyperlinks located within the sidebar. But if a calendar component were
added to the sidebar, this ruleset would apply to its links too, which is almost certainly unintended. Precision
matters. It must be clear exactly what is being selected for styling.
One option is to combine class-based namespacing with descendant selectors:
.sidebar nav > ul a {}
.sidebar .calendar table a {}But long descendant selectors like these couple rulesets to specific HTML structures — if the HTML changes, the design breaks — and they carry high specificity, which makes later overrides harder. Complex combinator selectors are also difficult for a reader to parse at a glance:
.sidebar section:first-of-type h2 + p {}CSS MUST be loosely coupled to HTML, not just for machine performance, but for the sanity of the people maintaining it.
Loose coupling
A maintainable codebase is one where developers can change one part with confidence that they are not inadvertently breaking another. In front-end engineering, this means the source order of an HTML document SHOULD be changeable without triggering CSS refactoring.
Given a tightly coupled selector:
.sidebar nav > ul a {}A more loosely coupled equivalent uses fewer descendant selectors and a more targeted encapsulation class:
ul.sidebar-menu a {}Or, applying the class directly to the target elements, dropping the descendant selector entirely:
a.sidebar-menu {}Applying classes directly to the elements being styled gives more flexibility to move things around and makes a designer’s intent explicit in the markup. The classes describe exactly what the elements should look like. The trade-off is more classes, potentially duplicated across sibling elements.
A flat hierarchy of simple type and class selectors SHOULD be preferred over deep selector chains, for long-term
maintainability. It MAY still be sensible to push encapsulation classes up one or two levels in the DOM — for example
onto a <table> or <ul> whose internal structure is unlikely to ever change:
table.account-balance {}
table.account-balance thead {}
table.account-balance th {}
table.account-balance td {}
table.account-balance td:first-child {}Defensive programming
Class selectors MAY be qualified with type selectors (type.class), which retains some coupling between HTML and
CSS but makes explicit the context a class is designed for:
p.dropcap {}If dropcap is applied to a different element by mistake, the ruleset simply will not match — no harm done. An
unqualified, global class (.dropcap {}) carries a real risk of accidental misuse producing unintended presentational
effects. Where a class is deliberately designed to be global, it SHOULD be qualified with the universal selector to
make that intent explicit:
*.clearfix {}Classes MUST NOT be qualified against element types that are freely interchangeable with others. This applies in
particular to the two non-semantic elements, <div> and <span>, and to the semantic sectioning elements —
<main>, <header>, <footer>, <article>, <aside>, <address>, <nav>, <section> — since it is often useful
to swap one for another (for example, replacing an <aside> with a <div>) without also refactoring the CSS.
Type-qualifying a component or layout class also raises that selector’s specificity above an unqualified class selector’s. This matters beyond the freely-interchangeable-elements case above: a modifier or utility class MUST be able to override a component’s default presentation, and it can only do so where both selectors carry the same specificity. Given a type-qualified component selector and an unqualified modifier:
button.Button { background: hsl(210, 80%, 45%); }
.background-midgrey { background: hsl(0, 0%, 60%); }.background-midgrey cannot override button.Button on a <button class="Button background-midgrey">, regardless
of source order, because button.Button carries higher specificity (see Specificity and !important). Leaving the
component selector unqualified (.Button {}) keeps its specificity equal to the modifier’s, so source order alone
determines which wins — which is what makes composing modifiers onto a component predictable in the first place (see
Composition).
Predictability
Classes scope styles to particular bits of markup, but leakage remains possible if class names are too generic:
<p class="tagline">Tagline</p> <h1>Headline</h1> <p class="meta">Author, Date</p>
In a large project, generic class names like tagline and meta could easily turn up elsewhere and leak styles
between unrelated things — acceptable only if global reuse was actually intended.
A robust, scalable CSS codebase is one in which every class behaves predictably. Reading a class name in markup SHOULD be enough to confidently predict its presentational effect and scope of influence, what else in the application may depend on it, and the consequences of removing it. The bigger the project and the more complex the design, the more this matters. This is achieved through consistent execution of a robust class naming methodology.
Separation of concerns
Classes are frequently overloaded. Some position things, some modify the default presentation of individual
elements, some encapsulate larger UI components, some act as JavaScript hooks, some are added dynamically for feature
detection. Given <div class="header">, it is not obvious from the class name alone whether it positions the
element, styles its background and borders, styles nested content, or serves as a DOM query hook.
It SHOULD be easy for a new contributor to a project to quickly determine, for any class in the markup: what it does, where it came from, where else it could be used, what else it relates to, and what happens if it is removed.
Distinguishing naming conventions for classes that fulfil different roles is how this is achieved. The naming convention SHOULD be simple. It MUST NOT demand a steep learning curve to use correctly.
Single responsibility principle
Classes SHOULD do only one specific thing. Single-purpose classes are the most predictable, because their names tend
to be self-explanatory. A class called box-shadow is unambiguous. A class called box is not.
Focused classes also tend to be more reusable. Consider:
.alert {
background: hsl(0, 100%, 50%);
color: hsl(0, 0%, 100%);
font-size: 1.6rem;
font-weight: 700;
left: 0;
padding: 0.5em 2em;
right: 0;
text-transform: uppercase;
top: 0;
}This mixes look-and-feel with layout and position, so it cannot be reused in a different location. Splitting the concerns apart fixes this:
.layout-alerts {
left: 0;
position: absolute;
right: 0;
top: 0;
}
.alert-box {
background: hsl(0, 100%, 50%);
color: hsl(0, 0%, 100%);
font-size: 1.6rem;
font-weight: bold;
padding: 0.5em 2em;
text-transform: uppercase;
}alert-box can now be composed with layout-alerts in one location and reused, unmodified, in another (for example,
a sidebar).
Composition
Responsibility can be delegated further, to more single-purpose classes:
*.warning {
background: hsl(0, 100%, 50%);
color: hsl(0, 0%, 100%);
}
*.shout {
font-size: 1.6rem;
font-weight: bold;
text-transform: uppercase;
}
.alert-box { padding: 0.5em 2em; }<div class="alert-box warning shout">
A design’s final presentation is composed from multiple classes. Variants are added by composing in new classes
(rounded-corners, box-shadow, a success variant that replaces warning) rather than modifying existing ones,
and the same specialist classes can be reused across otherwise unrelated components:
<div class="popup success rounded-corners box-shadow">
The more specialized a class, the more frequently it can be recycled across a UI. A class called rounded-corners
that also set borders, padding, and margins would be far less reusable.
Open/closed principle
Composing designs from small, specialized components speeds up development, because less CSS needs to be written and maintained, and produces a more stable codebase. Specialist classes need updating far less often than comprehensive ones.
A base class SHOULD stay closed for modification once established. New presentation SHOULD be layered on by adding classes, not by editing the base class, which may have implications for everything that already depends on it. As with software, classes SHOULD be open for extension but closed for modification.
Don’t repeat yourself
Breaking a design into small, portable components reduces duplicated declarations, because components are reused rather than redeclared. This does not, by itself, solve duplication in the default presentation of element types:
h2 { font-size: 2rem; line-height: 1.2; margin-top: 2em; margin-bottom: 1em; }
h3 { font-size: 1.4rem; line-height: 1.2; margin-top: 2em; margin-bottom: 1em; }
p { font-size: 1.4rem; line-height: 1.2; margin-bottom: 1em; }Reorganizing type selectors around shared properties, rather than around elements, eliminates the duplication:
h2 { font-size: 2rem; }
h3, p { font-size: 1.4rem; }
h2, h3, p { line-height: 1.2; }
h2, h3 { margin-top: 2em; }
h2, h3, p { margin-bottom: 1em; }Style sheets SHOULD stay DRY ("Don’t Repeat Yourself"), but a little WET ("Write Everything Twice") is acceptable where two unrelated components happen to share visual characteristics by coincidence. Duplicated properties do not need to be declared via the same selector if doing so would be the simpler solution.
Progressive enhancement
Progressive enhancement is about resilience as much as it is about inclusiveness.
– Ethan Marcotte
Progressive enhancement builds an interface up in layers: a plain text document, then HTML markup, then CSS, then JavaScript-driven behavior. Each of these layers can itself be layered further.
For CSS, the practical approach is to start with a minimum baseline design that works consistently across every target browser, including mobile, and progressively enhance it with more detail and more intricate layout as permitted by more capable browsers and larger screens — that is, mobile-first, using only the CSS properties supported by every browser being targeted initially.
.box-out {
border: 1px solid hsl(0, 0%, 80%);
margin: 1rem 0;
padding: 0.5rem 1rem;
}
@media screen and (min-width: 56.25rem) {
.box-out {
margin: 2rem 0;
padding: 1rem 2rem;
}
}
@media screen and (min-width: 75rem) {
.box-out {
margin: 2rem 4rem;
}
}Every current browser renders this consistently. Legacy browsers without media query support render the baseline mobile view. New CSS features also land in different browsers at different times. A newer property SHOULD be layered in as an enhancement, written after the baseline property it augments, so that browsers without support fall back to the baseline:
.box-out {
border: 1px solid hsl(0, 0%, 80%);
-webkit-border-image: url('/border.png') 30 30 round;
border-image: url('/border.png') 30 30 round;
margin: 1rem 0;
padding: 0.5rem 1rem;
}A vendor-prefixed property, such as -webkit-border-image above, SHOULD NOT be used in production CSS unless the
target browser support data shows it is still required. Where one is used, it MUST be written immediately before the
standard property it prefixes, in the order shown, so the unprefixed declaration always wins in browsers that support
both — and the surrounding code or a comment SHOULD note which browsers still need the prefix, so it can be removed
once they no longer do.
The native @supports at-rule complements class-based feature detection (see Dynamic classes) for enhancements
that only need to apply within a single style sheet, without a JavaScript-injected supports-* class:
.Gallery {
display: block;
}
@supports (display: grid) {
.Gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
}
}Prefer @supports where the enhancement is pure CSS and does not need to be known to JavaScript or reflected in the
markup. Prefer a supports-* class (see Dynamic classes) where the same feature test also has to gate JavaScript
behavior, since duplicating the test in both @supports and a script risks the two falling out of sync.
Progressive enhancement avoids CSS hacks, browser-detection scripts, and conditional comments for legacy browsers — just standard CSS. It also has knock-on benefits. Smaller, slower devices only need to render the simplest version of a page. Mobile-first design forces content into its logical order in the HTML source, rather than an order dictated by desktop layout, which improves accessibility. And the constraints of mobile devices (screen size, processing power, bandwidth) keep focus on what matters most — content, performance, usability, and accessibility — ahead of visual embellishment.
Class names
Each class MUST fulfil one, and only one, of the following roles:
- It sets the position of something, helping to create a layout.
- It encapsulates the presentation of a discrete UI component custom-made from multiple HTML elements.
- It modifies the default presentation of an individual HTML element, a component, or a layout section.
Layout classes are applied to sectioning elements (<main>, <header>, <footer>, <article>, <aside>,
<address>, <nav>, <section>) and to general <div> elements. Layout classes are responsible for positioning
content on the page. They MUST NOT style content directly (see Layout). They are concerned exclusively
with creating an empty, wireframe-like structure into which separately styled content can be placed and moved.
Components are custom UI components made from multiple standard HTML elements — navigation bars, modal popups, accordions, carousels, social sharing clusters. Components MAY be enhanced with dynamic behavior via JavaScript, and MAY in some cases be generated entirely by client-side scripting (see Components).
Modifiers are classes that modify the default presentation of something. A modifier MAY be global, applicable to anything, or scoped to change the default presentation of certain HTML elements, a certain component, or a particular layout section. Modifiers MAY also be added to a document dynamically by client-side JavaScript, to act as targets for CSS animations and transitions (see Modifiers).
Every class MUST represent the name of a layout section, the namespace for a component, or a modifier of something’s default presentation. A single class MUST NOT fuse these separate concerns.
ID selectors MUST NOT be used in style sheets, under any circumstances. The id attribute is reserved for document
fragments (#section) and for scripting hooks. Styling by ID carries two costs beyond breaking the class-only
contract above: an ID is, by definition, unique to one element, so an ID-selector ruleset can never be reused; and a
single #id in a selector (1-0-0) outweighs any number of classes, attributes, or type selectors combined (see
Specificity and !important), which is difficult to override without an equally drastic escalation.
Naming conventions
Layout classes, component classes, and modifier classes each use a distinct naming convention, so that a class’s role is identifiable from its name alone:
Role | Convention |
|---|---|
Layout |
|
Components |
|
Modifiers |
|
Modifiers dynamically added to a document by a client-side script MUST be prefixed is-, followed by a noun or verb
(eg. is-collapsed, is-animating). Modifiers that belong to a particular component or layout section MUST be
prefixed with the lower-case name of that component or section (eg. logo-homepage, contextmenu-open).
Example markup for a Trello-like board application, demonstrating all three conventions:
<main class="BOARD">
<div class="List">
<h2 class="list-header">
To do
<svg><title>Options</title>...</svg>
</h2>
<div class="Card is-new">
<a href="card/1234">
<h3 class="plaintext">Homepage changes</h3>
<ul class="card-members">
<li title="Kieran Potts">KP</li>
<li title="Maja Sienkiewicz">MS</li>
</ul>
</a>
</div>
<div class="Card">
...
</div>
</div>
<div class="List">
...
</div>
</main>Case alone conveys the role hierarchy: full-capitalization layout classes are the most conspicuous in the markup, camel-case component classes (always nested within layout sections) are less prominent, and lower-case modifier classes — the least critical to achieving a default presentation — are the least conspicuous.
Where a component’s markup is shared across multiple, independently-deployed applications (eg. after an acquisition,
or across a suite of related products with separate front ends), its CamelCase name MAY be prefixed with a
lower-case application namespace, joined by a colon: app1:NavBar, app2:NavBar. This disambiguates two components
that share a base name but diverge in markup or styling between applications, without abandoning the standard’s own
CamelCase convention for the base name itself. Reserve the namespace prefix for this specific, cross-application
collision case — a component consumed by only one application MUST NOT carry a namespace prefix it does not need.
A second example, for a page header:
<nav class="NAVIGATION navigation-homepage">
<div class="BRANDING">
<div class="Logo">
<a href="./" class="img"><svg>...</svg></a>
</div>
</div>
<div class="NAV_MAJOR">
<div class="NavBar">
<ul>
<li class="navbar-selected"><a href="learn.html">Learn</a></li>
<li><a href="about.html">About</a></li>
</ul>
</div>
</div>
<div class="NAV_MINOR">
<div class="NavBar navbar-minor">
<ul>
<li><a href="login.html">Login</a></li>
<li class="navbar-primary"><a href="join.html">Register</a></li>
</ul>
</div>
</div>
<div class="SEARCH">
<div class="SearchBox">
<form action="search.html" method="get">
<label for="input-search">Search</label>
<input name="q" type="search" id="input-search" />
<button type="submit">Search</button>
</form>
</div>
</div>
</nav>Layout classes: NAVIGATION, BRANDING, NAV_MAJOR, NAV_MINOR, SEARCH. Component classes: Logo, NavBar
(used twice), SearchBox. Modifier classes: navigation-homepage (varies NAVIGATION on the homepage),
navbar-selected (marks the current selection), navbar-minor (varies the second NavBar), navbar-primary
(emphasizes the "Register" link), img (a global modifier for any hyperlink that encapsulates an image).
Corresponding selectors:
.NAVIGATION {}
.NAVIGATION.navigation-homepage {}
.BRANDING {}
.NAV_MAJOR {}
.NAV_MINOR {}
.SEARCH {}
a.img {}
.Logo {}
.Logo a {}
.Logo svg {}
.NavBar {}
.NavBar ul {}
.NavBar li {}
.NavBar li.navbar-selected {}
.NavBar li.navbar-primary {}
.NavBar a {}
.NavBar.navbar-minor {}
.NavBar.navbar-minor ul {}
.NavBar.navbar-minor li {}
.NavBar.navbar-minor li.navbar-selected {}
.NavBar.navbar-minor li.navbar-primary {}
.NavBar.navbar-minor a {}
.SearchBox {}
.SearchBox form {}
.SearchBox label {}
.SearchBox input[type="search"] {}
.SearchBox button {}Dynamic classes
data-* attributes, not classes, SHOULD be used as DOM query targets from JavaScript:
document.querySelectorAll('[data-popup="true"]');This leaves the class attribute as the primary interface between HTML and CSS, and the data-* attributes (and,
where appropriate, the <data> element) as the interface between HTML and JavaScript — a clean separation of
concerns.
Where a codebase’s existing conventions instead rely on a class as the JavaScript query target — for example,
integrating with tooling or a third-party library that expects one — the class MUST be prefixed js- (eg.
js-toggle, js-modal-trigger) and MUST NOT also appear in a style sheet rule. This keeps the same separation of
concerns the data- convention above exists to protect: restyling a component can never silently break its
behavior, because a js- class is never a styling hook, and a reader can tell a class’s purpose from its prefix
alone. The js- prefix is a fallback for this specific case, not an alternative to data- in the general case —
prefer data-* attributes as the default JavaScript-hook mechanism throughout.
It is acceptable for modifier classes to be injected into a document dynamically by a client-side script. Dynamic
modifiers MUST use the is- prefix, so it is clear they were added after the page was first rendered. Temporary
classes that transition something from one state to another are named is- followed by a verb. Permanent classes
that fix a resulting state are named is- followed by an adjective:
.is-opening {}
.is-open {}
.is-closing {}
.is-closed {}
.is-first-frame {}
.is-animating {}
.is-last-frame {}Classes added to a document for feature detection MUST be prefixed supports-:
.supports-cssboxsizing {}
.supports-csstransforms3d {}
.supports-webworkers {}Class name semantics
Names SHOULD last the lifetime of the objects they are given to, so they SHOULD describe things that are unlikely to change. Names that are too literal — describing exact content, exact presentation, or exact function — tend to become inaccurate as a project evolves.
Consider a component presenting a scrolling marquee of breaking news headlines. Naming it after its current
presentation (TopRedBox) breaks if the box’s color or position changes. Naming it after its current content
(BreakingNews) breaks if it is later repurposed to promote a competition or a blog post. A more generic, slightly
abstract name (AlertBox) is more likely to remain accurate over time.
Layout section and component names SHOULD accurately describe the content they encapsulate without being overly
explicit — BANNER and AlertBox rather than something overly literal. A degree of ambiguity is beneficial for
these classes. Modifier classes, by contrast, generally apply a narrow, well-defined set of properties, so they
benefit from explicit, expressive names: rounded-corners, is-animating.
Layout
A new web design project SHOULD begin by creating a wireframe-like layout. A series of sectioning elements —
<main>, <header>, <footer>, <article>, <aside>, <address>, <nav>, <section>, and <div> — establish
distinct sections into which content will later be placed:
<body>
<div class="CONTAINER">
<header class="BANNER"> ... </header>
<nav class="NAVIGATION"> ... </nav>
<main class="MAIN"> ... </main>
<aside class="ADVERTS"> ... </aside>
<footer class="FOOTER">
<nav class="SITEMAP"> ... </nav>
<div class="LEGAL"> ... </div>
</footer>
</div>
</body>Each section MUST be given a unique name, written in UPPER_CASE with words delimited by underscores (see
Class names). Names SHOULD describe the content intended for each section without being overly
specific — SIDEBAR is preferable to RELATED_LINKS.
Layout selectors MUST NOT be qualified with element types. This preserves the freedom to swap sectioning elements in
the markup — for example, replacing an <aside> with a generic <div> — without needing to refactor the CSS:
.CONTAINER {}
.BANNER {}
.NAVIGATION {}
.MAIN {}
.ADVERTS {}
.FOOTER {}
.SIDEBAR {}
.LEGAL {}The default layout SHOULD be optimized for mobile devices, then progressively enhanced for larger viewports (and for print, where relevant). Media queries SHOULD be written immediately after the selector they modify:
.BANNER {
background: hsl(0, 0%, 93%);
padding: 2rem 0;
}
@media screen and (min-width: 90rem) {
.BANNER { padding: 4rem 0; }
}
@media screen and (min-width: 120rem) {
.BANNER {
padding: 6rem 0;
position: absolute;
right: 0;
top: 0;
width: 250px;
}
}
@media print {
.BANNER { display: none; }
}Breakpoints MUST be written as min-width/max-width conditions on the viewport, as above, never as device-width.
device-width reports the physical screen size, not the browser window’s rendered width, so it does not reflect a
resized window, a split-screen or foldable layout, or browser zoom — it stopped being a reliable signal once devices
with widely varying pixel densities and multi-window use became common. A breakpoint SHOULD also be chosen where the
content itself starts to look wrong — text lines becoming too long, awkward whitespace, cramped controls — rather
than at round numbers matched to specific, ever-changing device dimensions.
Layout sections are later populated with content. That content marked up as elements (see Elements) or components (see Components).
Layout rulesets are concerned exclusively with establishing the visual structure that holds that content. Layout classes MAY position sections and give them backgrounds, borders, padding, and margins, but MUST NOT set typographic styles, style form controls, or otherwise influence the presentation of anything placed within a section. The aim is to be free to move content around the layout without changing the CSS.
To that end, layout classes MUST NOT set inheritable properties — only non-inheritable properties, which do not cascade down to nested content, including:
displayfloatpositiontop,bottom,left,rightwidth,height,max-width, etc.background,background-color,background-image, etc.border,border-color, etc.margin,margin-left, etc.padding,padding-left, etc.z-index
Multiple layouts
Not every page needs to follow the same layout. Where a layout differs from the default, the root <html> element
SHOULD be given a unique class name, using the layout naming convention:
<html class="SEARCH">
This class can then be used to encapsulate adjustments to existing sections, and to introduce entirely new sections specific to that page:
.CONTAINER {}
.BANNER {}
.NAVIGATION {}
.MAIN {}
.ADVERTS {}
.FOOTER {}
.SITEMAP {}
.LEGAL {}
html.SEARCH .NAVIGATION {}
html.SEARCH .SIDEBAR {}
html.SEARCH .PAGINATION {}The template class (SEARCH) SHOULD be qualified with a type selector (html.SEARCH) to make clear that it
encapsulates a whole template, not a single layout section.
In designs with many layout variations, global layout sections MAY be prefixed GLOBAL_ to distinguish them from
template-specific sections:
.GLOBAL_CONTAINER {}
.GLOBAL_BANNER {}
.GLOBAL_NAVIGATION {}
.GLOBAL_MAIN {}
.GLOBAL_ADVERTS {}
.GLOBAL_FOOTER {}
.GLOBAL_SITEMAP {}
.GLOBAL_LEGAL {}
html.SEARCH .GLOBAL_NAVIGATION {}
html.SEARCH .SIDEBAR {}
html.SEARCH .PAGINATION {}Layout modifiers
Occasional, minor modifications to a section of layout SHOULD use a modifier class:
.BANNER {
background: hsl(0, 0%, 93%);
display: table-cell;
padding: 2rem 0;
vertical-align: middle;
}
.BANNER.banner-homepage { height: 100vh; }<header class="BANNER banner-homepage">
Layout modifier classes are OPTIONAL. It MUST never be necessary to apply a modifier to achieve the default layout.
Modifiers written lower-case, hyphen-delimited, and prefixed with the lower-cased name of the layout section they
vary (banner-homepage, sidenav-visible) reduce the chance of a name collision with an unrelated class, and make
clear in the markup what a modifier belongs to. Layout modifiers SHOULD also be qualified against their parent
section’s class:
.SECTION.section-modifier { ... }This further locks down the modifier’s scope, and limits the impact if it is accidentally applied in the wrong place. See Modifiers for the full modifier conventions.
Modern layout primitives
Flexbox, CSS Grid, and container queries did not exist when class-based grid frameworks (Bootstrap-style row/column
classes) were the only way to build responsive layout, and this standard predates them too. They do not change the
conventions above — layout sections are still named, UPPER_CASE classes, and grid systems still MUST NOT be used to
create layout (see Grid systems) — but they do change how a layout section’s own internal positioning is
implemented, and they replace float, display: table-cell, and absolute positioning as the default technique:
.NAVIGATION {
display: flex;
justify-content: space-between;
}
.MAIN {
display: grid;
grid-template-columns: 1fr min(75rem, 100%) 1fr;
}
.MAIN > * { grid-column: 2; }Flexbox SHOULD be used for one-dimensional arrangements — a row or a column of items, such as `.NAVIGATION’s links above. CSS Grid SHOULD be used for two-dimensional arrangements, where content needs to align on both axes at once, such as `.MAIN’s centered content column. A layout section MAY combine both: a grid establishing the overall structure, with flex containers for the one-dimensional arrangements nested inside it.
Container queries (@container) extend the progressive-enhancement approach in Progressive enhancement from the
viewport to an individual layout section or component’s own size, which matters once Flexbox and Grid make a
section’s width independent of the viewport width:
.SIDEBAR { container-type: inline-size; }
@container (min-width: 20rem) {
.Card { grid-template-columns: 1fr 2fr; }
}A component queried this way MUST still work without its container query matching — the unenhanced layout is the
baseline, exactly as with a @media breakpoint that fails to match.
Grid systems
Grid systems MUST NOT be used to create layout. A traditional, uniquely named layout is more flexible. Because each section has a unique name, different sections can collapse to mobile-friendly layout at different breakpoints, which is difficult to achieve with a generic grid system.
Grid systems ARE useful as components, for arranging content within an existing layout section — a role in which they MAY be implemented with CSS Grid rather than float- or class-based columns, without changing anything about how they are used here:
<main class="MAIN">
<div class="Grid">
<div class="grid-row">
<div class="grid-column-one-of-two">
<div class="Promotion">
<!-- Content of the "Promotion" component -->
</div>
</div>
<div class="grid-column-one-of-two">
<div class="PullQuote">
<!-- Content of a "PullQuote" component -->
</div>
</div>
</div>
<div class="grid-row">
<div class="grid-column-two-of-four">
<div class="Box box-shade1">
<!-- Content of the first "Box" component -->
</div>
</div>
<div class="grid-column-one-of-four">
<div class="Box box-shade2">
<!-- Content of the second "Box" component -->
</div>
</div>
<div class="grid-column-one-of-four">
<div class="Box box-shade3">
<!-- Content of the third "Box" component -->
</div>
</div>
</div>
</div>
</main>Elements
Once layout is established, the next stage is to set default presentation for the base HTML elements used to encapsulate content and render interactive controls. This typically covers:
- Headings, paragraphs, lists, and hyperlinks.
- Data tables.
- Forms, fieldsets, and legends.
- Input controls and buttons.
The sectioning elements (<main>, <header>, <footer>, <article>,
<aside>, <address>, <nav>, <section>) and the two elements with no
semantic value (<div>, <span>) MUST NOT be styled directly — they belong to
Layout and Components respectively.
Most selectors here are plain type selectors:
h2 {
font-size: 2rem;
line-height: 1.2;
margin-top: 2em;
margin-bottom: 1em;
}
h3 {
font-size: 1.4rem;
line-height: 1.2;
margin-top: 2em;
margin-bottom: 1em;
}Inheritable typographic styles MAY be set on <body> for efficiency, and
allowed to cascade:
body {
color: hsl(0, 0%, 27%);
font-family: Arial, Helvetica, sans-serif;
font-size: 62.5%;
}Pseudo-classes and pseudo-elements are useful here too:
a:hover { opacity: 1; }
q::before { content: ' «'; }Descendant, child, and sibling selectors SHOULD be used sparingly. The default presentation of an individual element MUST NOT depend on that element being used in a particular context:
li a {}
li > a {}
h2 + h3 {}
h2 ~ p {}Rulesets MUST NOT vary an element’s presentation according to its position within the layout — layout and content are separate concerns:
.MAIN h2 {}
.SIDEBAR h2 {}Deal with one element at a time:
h2 { font-size: 2rem; line-height: 1.2; margin-top: 2em; margin-bottom: 1em; }
h3 { font-size: 1.4rem; line-height: 1.2; margin-top: 2em; margin-bottom: 1em; }
p { font-size: 1.4rem; line-height: 1.2; margin-bottom: 1em; }To reduce duplication, selectors SHOULD be reorganized around shared properties rather than element types (see Principles of good CSS):
h2 { font-size: 2rem; }
h3, p { font-size: 1.4rem; }
h2, h3, p { line-height: 1.2; }
h2, h3 { margin-top: 2em; }
h2, h3, p { margin-bottom: 1em; }Default element presentation SHOULD be optimized for mobile, then progressively enhanced for larger viewports and other output devices such as printers, using media queries written immediately after the base selector they modify:
p {
font-size: 1.2rem;
line-height: 1.2;
margin-bottom: 1em;
}
@media screen and (min-width: 37.5rem) {
p { font-size: 1.3rem; }
}
@media screen and (min-width: 56.25rem) {
p { font-size: 1.4rem; }
}
@media print {
p { font-size: 12pt; }
}Tip
Use root em (rem) units for lengths in media queries. This keeps breakpoints
in proportion to the user’s zoom level.
Modifiers and components
HTML’s element vocabulary is not always sufficient to express a design’s full variety. Where, for example, paragraphs need several distinct presentations, use element modifiers:
p { font-size: 1.4rem; }
p.standout {
font-size: 1.8rem;
font-weight: bold;
}
p.smallprint { font-size: 1rem; }Alternatively, elements MAY be encapsulated within a component structure, where the component’s class acts as a namespace for alternative presentations of its constituent elements:
.Slideshow figure {}
.Slideshow figcaption {}
.Slideshow img {}
.Slideshow p.slideshow-description {}
.Slideshow p.slideshow-copyright {}See Components and Modifiers for the full conventions governing each technique.
There is a balance to strike. Too many default properties on naked element types means unsetting a lot of inherited properties when elements are reused in special contexts; too few means a larger portfolio of components and modifiers is needed to build up the design. Initially, prefer an exhaustive suite of default element styles, to maximize consistency and make the most of the cascade. Later, if components and modifiers require resetting a lot of inherited properties, iterate toward a lower baseline: fewer properties applied via type selectors, greater reliance on classes.
Components
HTML’s native elements do not provide a large enough vocabulary to build rich,
modern web interfaces on their own. Multiple elements often need to be grouped
to achieve a particular effect — an <input> and a <button> combined into a
search box, or a <ul> repurposed as a navigation menu.
These groups could be placed directly inside a layout section and styled by their position:
.NAVIGATION ul {}
.NAVIGATION li {}
.NAVIGATION a {}
.NAVIGATION form {}
.NAVIGATION label {}
.NAVIGATION input {}
.NAVIGATION button {}But this tightly couples the content’s presentation to its location. Moving it breaks the styling. Instead, recurring patterns of content MUST be bundled into independent, reusable modules — components — that can be freely moved and reused.
A component is any reusable UI pattern, from something as simple as a search box
to something as complex as a real-time comment feed. A component is created by
encapsulating its markup in a unique, CamelCase class (see Class
names):
<nav class="NAVIGATION">
<div class="NavBar">
<ul>
<li><a href="./">Home</a></li>
<li><a href="about.html">About</a></li>
<li><a href="learn/">Learn</a></li>
<li><a href="extend/">Extend</a></li>
<li><a href="share/">Share</a></li>
</ul>
</div>
<div class="SearchBox">
<form action="search.html" method="get">
<label for="input-search">Search</label>
<input name="q" type="search" id="input-search" />
<button type="submit">Search</button>
</form>
</div>
</nav>The component’s class acts as its own namespace, so its presentation no longer depends on the name of a containing layout section:
.NavBar {}
.NavBar ul {}
.NavBar li {}
.NavBar a {}
.NavBar a:hover {}
.SearchBox {}
.SearchBox form {}
.SearchBox label {}
.SearchBox input {}
.SearchBox button {}A component’s presentation is now independent of where it is placed, so it can be moved, duplicated, or reused — even copied between projects that share the same methodology — without touching the CSS.
Components SHOULD be designed to fit 100% of the width of their container. To float a component, or apply fixed or absolute positioning to it, wrap it in a new layout zone and position that instead.
Semantic sectioning elements — <main>, <header>, <footer>, <article>,
<aside>, <address>, <nav>, <section> — MUST NOT be used within or to
encapsulate components; they are reserved for layout sections (see
Layout). Components MAY be contained in generic <div> elements or
in any appropriate block-level element, such as <ul>, <table>, or
<blockquote>:
<table border="1" class="MiniCalendar">
<thead>
<tr class="nav">
<th class="prev"><a href="?m=02&y=2015">Prev</a></th>
<th class="this" colspan="5">January 2015</th>
<th class="next"><a href="?m=02&y=2015">Next</a></th>
</tr>
<tr class="days">
<th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th><th>Sun</th>
</tr>
</thead>
<tbody>
...
</tbody>
</table>Complete location-agnosticism is not always achievable. A component placed on a dark background may need adjusted colors, and responsive breakpoints can only be set relative to the viewport or display, not to a particular element’s size — so a component’s own breakpoints will depend on the size of container it is placed in. These are non-issues for global components that always appear in the same spot (a site logo, the primary navigation bar), but are worth avoiding in components meant to be reusable across many contexts.
Components work best when kept abstract. A ComparisonTable component is
preferable to a PriceComparison component, since the former can be reused to
compare things other than prices. The more abstract a component, the greater the
opportunity to reuse it.
Components inherit the default properties of the element types used in their
markup (see Elements). Those defaults MAY be changed and extended
on a per-component basis — for example, adjusting the default color and
text-decoration of <a> elements inside a NavBar. A small number of resets
like this is fine. If resets become frequent, apply fewer default properties via
unqualified type selectors instead, so components inherit less.
Important
Components and modifier classes MUST progressively enhance the default presentation of individual HTML elements, not regressively degrade it.
Not all content needs to be encapsulated in a component. Stand-alone elements and components MAY be freely mixed within a layout section:
<main class="MAIN">
<header class="HEADLINE">
<div class="ArticleHeadline">
<h1>The simplest way to design websites</h1>
<p>A new design system for modern web interfaces</p>
</div>
</header>
<img src="picture.png" alt="" class="float-right" />
<p>Lorem ipsum...</p>
</main>To style a component, combine its encapsulating class with an element type:
<div class="Headline"> <h1>Headline</h1> <p>Teaser</p> </div>
.Headline {}
.Headline h1 {}
.Headline p {}Where a component contains multiple elements of the same type that must look different from one another, introduce further classes:
<div class="Headline"> <p class="tagline">Tagline</p> <h1>Headline</h1> <p class="teaser">Teaser</p> <p class="meta">Author, Date</p> </div>
.Headline {}
.Headline h1 {}
.Headline p {}
.Headline p.tagline {}
.Headline p.teaser {}
.Headline p.meta {}These new classes are modifiers, and they carry a real risk of
cross-contamination. A selector like p.tagline {} or *.tagline {} could
exist elsewhere in the codebase and be inadvertently inherited by this
component. To avoid that, modifier classes used within a component MUST be
prefixed with the lower-cased name of the parent component:
<div class="Headline"> <p class="headline-tagline">Tagline</p> <h1>Headline</h1> <p class="headline-teaser">Teaser</p> <p class="headline-meta">Author, Date</p> </div>
.Headline {}
.Headline h1 {}
.Headline p {}
.Headline p.headline-tagline {}
.Headline p.headline-teaser {}
.Headline p.headline-meta {}The headline- prefix already provides adequate encapsulation on its own, so
the component class could in principle be dropped from the child selectors
(p.headline-tagline {}). Nonetheless, the component class SHOULD always be
included when selecting a component’s child classes — it is clearer, more
consistent, and matches how these selectors would compile from nested syntax in
an extended CSS preprocessor:
.Headline {
h1 {}
p {}
&.headline-tagline {}
&.headline-teaser {}
&.headline-meta {}
}Selectors more than one descendant level deep SHOULD be avoided. Prefer a new
class over a selector founded on a component’s markup structure. Immediate child
selectors and structural pseudo-classes (.Headline > h1, :first-of-type)
SHOULD also be avoided. They tightly couple HTML to CSS, making a component
harder to extend and modify later.
Nested components
Large, complex components — a modal popup, for instance — SHOULD be broken up into smaller components:
<div class="Popup">
<div class="PopupHeader">
<h2>Popup title</h2>
</div>
<div class="PopupBody">
<p>Popup message</p>
</div>
<div class="PopupFooter">
<p><small>Optional footer content</small></p>
</div>
</div>.Popup {}
.PopupHeader {}
.PopupHeader h2 {}
.PopupBody {}
.PopupBody p {}
.PopupFooter {}
.PopupFooter p {}Each sub-component is independently namespaced, so it can be developed, tested,
and maintained separately, and each specializes in one thing. The parent Popup
component is responsible only for the box and its position on the page. The
other components present the content of each section.
Unrelated components MAY also be freely combined to compose complex UI designs —
for example, a tabbed interface built from several independently maintained
components (Tabs, Panel, Slats, Slat).
Component inheritance
A component MAY extend another component. Given a simple dialog:
<div class="Dialog"> <h2>Message</h2> <p>Description</p> <button>Action</button> </div>
.Dialog {}
.Dialog h2 {}
.Dialog p {}
.Dialog button {}A variant may be created with a modifier class:
/* Default presentation: */
.Dialog {}
.Dialog h2 {}
.Dialog p {}
.Dialog button {}
/* Modified properties for 'alert' dialogs: */
.Dialog.dialog-alert {}
.Dialog.dialog-alert h2 {}
.Dialog.dialog-alert p {}
.Dialog.dialog-alert button {}Or, a new component MAY inherit the parent component’s presentation by combining both classes in the markup:
<!-- Parent component: --> <div class="Dialog"> <h2>Message</h2> <p>Description</p> <button>Action</button> </div> <!-- Child component: --> <div class="Dialog DialogAlert"> <h2>Message</h2> <p>Description</p> <button>Action</button> </div>
/* Parent component: */
.Dialog {}
.Dialog h2 {}
.Dialog p {}
.Dialog button {}
/* Child component: */
.DialogAlert {}
.DialogAlert h2 {}
.DialogAlert p {}
.DialogAlert button {}Because DialogAlert markup also carries the Dialog class, it inherits
`Dialog’s styles and then modifies and extends them.
Component inheritance works well when two or more components share a similar HTML structure, and MAY extend to deliberately abstract base components not intended for standalone use. Inheritance SHOULD be limited to a single tier — deep inheritance chains are harder to maintain, not easier. Composition SHOULD generally be preferred over inheritance. Composing complex interfaces from several small, independent components is usually simpler than maintaining an inheritance hierarchy.
Dynamic components
Components MAY be progressively enhanced with dynamic behavior. A Slideshow
component, for example, might initially render every image, stacked vertically:
<div class="Slideshow"> <a href="sydney.html"><img src="sydney.png" alt="Sydney" /></a> <a href="melbourne.html"><img src="melbourne.png" alt="Melbourne" /></a> <a href="perth.html"><img src="perth.png" alt="Perth" /></a> <a href="adelaide.html"><img src="adelaide.png" alt="Adelaide" /></a> <a href="darwin.html"><img src="darwin.png" alt="Darwin" /></a> </div>
A JavaScript enhancement then dynamically applies modifier classes that CSS uses to restyle and animate the component:
<div class="Slideshow is-animating"> <a href="sydney.html" class="is-hidden"><img src="sydney.png" alt="Sydney" /></a> <a href="melbourne.html" class="is-visible"><img src="melbourne.png" alt="Melbourne" /></a> <a href="perth.html" class="is-hidden"><img src="perth.png" alt="Perth" /></a> <a href="adelaide.html" class="is-hidden"><img src="adelaide.png" alt="Adelaide" /></a> <a href="darwin.html" class="is-hidden"><img src="darwin.png" alt="Darwin" /></a> </div>
See Class names and Modifiers for the is- naming
convention used by dynamically injected modifiers.
Modifiers
When something needs to be presented slightly differently than normal, give it a modifier class.
Modifiers MUST be written full lower-case with words delimited by hyphens. The
first character MUST be alphanumeric; a leading hyphen (-modifier) MUST NOT be
used, since that form is reserved for browser vendor extensions.
A modifier MAY be:
- Global.
- Specific to one or more element types.
- Specific to a component.
- Specific to certain elements within a component.
- Specific to a section of the layout.
Global modifiers apply to just about anything. Intent MUST be made explicit by including the universal selector:
*.ssh { color: #999; }Modifiers that vary an individual element’s presentation MUST be qualified with a type selector:
h1.homepage { font-size: 8rem; }Modifiers that vary a layout section’s presentation MUST be prefixed with the section’s name and qualified against the section’s class (see Layout):
.BANNER.banner-homepage { height: 100vh; }The same applies to component modifiers. Here, popup-alert is applied to the
same element that encapsulates the Popup component, and may vary the
presentation of that element or of anything nested within it:
.Popup.popup-alert { border-color: red; }A modifier MAY also target a specific part of a component’s markup — here,
popup-header must be applied to a descendant of the Popup container for the
selector to match:
.Popup .popup-header { padding: 1rem 2rem; }Modifier classes MAY be injected into a page dynamically by client-side
JavaScript. Dynamic modifiers MUST be prefixed is-, followed by a verb or noun
describing the component’s current state:
.SideNav.is-opening {}
.SideNav.is-open {}
.SideNav.is-closing {}
.SideNav.is-closed {}Classes added for feature detection MUST be prefixed supports- (eg.
supports-cssboxsizing).
Specificity and !important
A selector’s specificity is computed as a four-part value, most significant part first:
- Inline styles. A
styleattribute on the element itself. Always wins over any selector in a style sheet. - ID selectors. One point per
#idin the selector. - Classes, attributes, and pseudo-classes. One point per
.class,[attribute], or:pseudo-class(eg.:hover,:first-child). - Types and pseudo-elements. One point per type selector (
div,a) or::pseudo-element(eg.::before).
The universal selector (*) and combinators (` , `>, +, ~) contribute
nothing. Two selectors are compared part by part, most significant first:
.NavBar li.navbar-selected (0-2-1) beats .NavBar.navbar-minor (0-2-0),
because the two tie on classes but the first also carries a type selector. A
single ID selector (1-0-0) beats any number of classes, attributes, or type
selectors combined — one reason this standard does not permit ID selectors in
CSS. Where two selectors tie on specificity, the one that comes later in
source order wins.
This standard’s conventions keep specificity predictable without requiring the calculation above to be done by hand: layout, component, and unqualified modifier selectors normally carry a single class (0-1-0), so which ruleset wins is usually decided by source order (see Filesystem) rather than by specificity arithmetic. Qualifying a selector with a type or a second class — as the type-qualification and modifier-qualification rules elsewhere in this standard do — is a deliberate, visible increase in specificity, not an accident.
Global modifiers are inherently leaky. They may get added to things they were
not designed for, producing unexpected effects. And element or component
rulesets often carry higher specificity, which silently overrides a global
modifier’s properties — a common trigger for reaching for !important:
*.error { color: red !important; }!important SHOULD be reserved for cases where a property must not be
overridable by the client’s own style sheets. To avoid needing it here, prefer
qualifying a modifier against the element types, layout sections, or components
it applies to. This raises the modifier’s specificity to match, without leaking
or requiring !important:
p.error, ul.error,
input.error {
color: red;
}Global, unqualified modifiers remain useful for general utilities — a clearfix hack, or presentational effects like drop shadows and rounded corners that recur across otherwise unrelated components:
<div class="PullQuote clearfix box-shadow rounded-corners">
A narrow exception to the "reserved" guidance above is a small set of
single-purpose utility classes — .hidden { display: none !important; }, a
fixed-width helper — that MUST always win regardless of what else is applied
alongside them. Here !important is used proactively, before any specificity
conflict has actually arisen, as a deliberate guarantee rather than a
workaround: a class named .hidden is unambiguous about what it does, so
there is no risk of it masking a design that should have been an
appropriately-qualified modifier instead. This exception does not extend to
ordinary modifiers, where !important remains a sign that the modifier
should be qualified against its target instead.
Where an offending high-specificity ruleset cannot itself be refactored — most
often third-party or legacy CSS outside this standard’s control — two
remediation techniques exist without resorting to !important. Self-chaining
a class doubles its specificity without adding a location dependency:
.NavBar.NavBar {}And selecting an ID-bearing element by attribute, rather than by ID selector, matches the same element at class-level specificity:
[id="legacy-widget"] {}Both are remediation techniques for CSS this standard does not govern, not
patterns to reach for within a codebase that follows it. A codebase written to
this standard avoids the specificity trouble that makes them necessary in the
first place — shallow selectors, no ID selectors, and !important reserved as
above.
Naming
Modifiers SHOULD be highly specialized, setting only a few properties each.
Unlike layout and component names, which benefit from a degree of abstraction
(see Class name semantics), modifier names SHOULD be explicit
and expressive, so there is no ambiguity about what a modifier does —
text-shadow, is-collapsed.
High-fidelity designs will end up with many modifiers scattered through the HTML. That tradeoff — markup verbosity in exchange for maintainability and flexibility — is acceptable.
Filesystem
This technical standard does not prescribe a specific file layout for CSS source, but a project SHOULD maintain separate style sheets for browser resets, layout, base elements, and one style sheet per component, then aggregate and minify everything before publication. A preprocessor SHOULD automate this step.
Where a reset style sheet is used, it SHOULD set box-sizing: border-box on
every element, via the universal selector, so that an element’s declared
width and height include its padding and border rather than being
inflated by them:
*, *::before, *::after {
box-sizing: border-box;
}This one rule removes a whole class of layout arithmetic that would otherwise have to be repeated across every component and layout section, so it belongs in the reset rather than being redeclared per component.
@charset "UTF-8"; @import "reset"; @import "layout"; @import "elements"; @import "components/Grid"; @import "components/Hello"; @import "components/NavBar"; @import "components/Overlay"; @import "components/Popup";
The @charset rule, where used, MUST be the first line of the compiled style
sheet — nothing, not even a comment, MUST precede it — and MUST give the
encoding as a double-quoted string, @charset "UTF-8";. Any other position or
quoting is invalid and the browser silently ignores the rule.
Every @import MUST precede all other rules in the style sheet besides
@charset; an @import that follows a ruleset is invalid and is ignored by
the browser. An @import MAY be scoped to a media condition, avoiding the
need to wrap the imported file’s own rules in a media query:
@import "wide-screen" screen and (min-width: 90rem);
Print styles SHOULD be maintained in their own style sheet, imported with a
print media condition, rather than scattered as @media print blocks
throughout the layout and element style sheets:
@import "print" print;
A dedicated print style sheet keeps print-specific overrides — hiding
navigation and interactive controls, expanding hyperlink URLs, switching to
point-based font sizes — in one place, instead of interleaved with the
screen-oriented rules for every layout section and element. Occasional,
narrowly scoped @media print overrides alongside the screen rules they
adjust, as used elsewhere in this standard’s examples, remain acceptable in
smaller projects where a dedicated style sheet would be overkill.
The following typically belong in the elements style sheet:
- Embedded fonts (
@font-facedeclarations). - Animation keyframes (
@keyframes). - Styling for selected text (
::selection), scrollbars (::-webkit-scrollbar), and other such pseudo-elements. - Global modifier classes.
These MAY instead be split into their own style sheets:
@import "fonts"; @import "animations"; @import "chrome"; @import "helpers";
In larger projects, maintain a style sheet for the default layout (see Layout) and a separate style sheet for each child layout that extends it:
@import "layout"; @import "layout/HOME"; @import "layout/SEARCH"; @import "layout/PRODUCT"; @import "layout/BASKET"; @import "layout/CHECKOUT";
Source order
The source order of the compiled CSS matters:
- Reset. If a reset style sheet is used, it MUST come first.
- Elements. Rulesets for components inherit and extend the default presentation of individual HTML elements (see Components), so element type selectors MUST come before component rulesets.
- Components. Components MAY be ordered arbitrarily, though alphabetical
order is RECOMMENDED for convenience. Alphabetical order also ensures a child
component (eg.
DialogAlert) follows its parent (eg.Dialog), which matters for component inheritance (see Component inheritance). - Layout extensions. Page-specific changes and extensions to the default layout MUST be declared after the rulesets for the default layout itself.
Custom properties
CSS custom properties ("CSS variables") store a value once and reference it
throughout a style sheet with var():
:root {
--color-brand: hsl(210, 80%, 45%);
--spacing-unit: 1rem;
}
.NavBar a {
color: var(--color-brand);
padding: var(--spacing-unit) calc(var(--spacing-unit) * 2);
}Unlike a preprocessor variable, which is resolved once at compile time, a
custom property is a real CSS value, resolved by the browser at render time.
It therefore participates in the cascade and inheritance exactly like any
other property: it can be set globally, overridden for a specific layout
section or component, and changed dynamically (by a media query, a
:hover/:focus state, or client-side JavaScript) without recompiling any
style sheet.
Global custom properties — design tokens such as brand colors, spacing units,
and type scale — SHOULD be declared on :root, so they are available
everywhere:
:root {
--color-brand: hsl(210, 80%, 45%);
--color-error: hsl(0, 70%, 45%);
--spacing-unit: 1rem;
}A component or layout section MAY declare its own custom properties, scoped to its own selector, for values that only make sense in that context:
.Popup {
--popup-border-color: hsl(0, 0%, 80%);
border: 1px solid var(--popup-border-color);
}
.Popup.popup-alert {
--popup-border-color: hsl(0, 70%, 45%);
}Here, popup-alert overrides --popup-border-color rather than redeclaring
border outright — the same modifier convention as elsewhere in this
standard (see Naming), applied to a custom property instead of a
standard one. This keeps the override narrow: anything else .Popup sets
from --popup-border-color (a box shadow, an icon fill) also picks up the
modified value, without the modifier having to restate it.
var() accepts a fallback value, used when the custom property is unset:
.Card {
padding: var(--spacing-unit, 1rem);
}A fallback is useful for a component that may be dropped into a project which
has not defined the standard’s token set, but SHOULD NOT be relied on as a
substitute for declaring the token on :root in a project that does use this
standard’s conventions throughout.
Custom properties MUST NOT be used as a replacement for this standard’s class naming conventions. A custom property changes a value; a class changes which rules apply. Theming and one-off value substitution belong to custom properties; adding or removing presentation belongs to modifier classes (see Modifiers).
References
- Frost, B (2013). Atomic Web Design. — A methodology for thinking about interfaces as a hierarchy of progressively more complex objects.
- Yandex. Block Element Modifier (BEM). — A widely adopted class naming convention.
- Snook, J. Scalable and Modular Architecture for CSS (SMACSS). — A style guide covering base, layout, module, state, and theme rules.
- Gallagher, N. SUIT CSS. — A class naming convention combined with extended CSS syntax.
- Sullivan, N (2009). Object-Oriented CSS (OOCSS). — One of the earliest widely adopted web design methodologies.
- Roberts, H. CSS Guidelines. — A high-level advice document for writing sane,
manageable CSS. Also the source for the
js-JavaScript-hook-class fallback in Class names. - Google. Google HTML/CSS Style Guide. — Formatting and naming conventions for HTML and CSS.
- Brown, T (2018). CSS Master. O’Reilly Media. — The source for the
@charset,@import, andbox-sizingguidance in Filesystem. - Copes, F. The CSS Handbook. — The source for the
@importmedia descriptor and print style sheet guidance in Filesystem. - Painless CSS. Top 10 CSS Mistakes. — The source for the CSS-in-JS and CSS Modules scoping discussion in Overview.
- AllThingsSmitty. CSS Protips. — The source for the
unset/allinheritance-keyword guidance in Embrace the constraints of the cascade.