https://ecss.info/en/
ecss
IntroValuesPrinciplesRulesToolsContact Francais
Introduction
Avoiding common styling problems is simple. And you only need
efficient CSS. Nothing more.
ECSS sets simple rules for simple styling. No more naming everything,
no more technological dependencies. Only intentional, consistent,
simple, expressive, predictable, sustainable CSS.
Want help enforcing rules?
Gently nudge you and your team into ECSS.
Install the Stylelint config
Eager to build something?
A quick & efficient starting point is within reach!
Clone the scaffolding library (WIP)
Want to know the beast?
Go ahead and see what you think of it!
Dive into ECSS rules
Curious about it all?
Well, keep scrolling!
Why ECSS?
Because CSS as a language is misunderstood and undeservedly maligned.
Because simple rules and low impact tools can go a long way in making
sure the CSS codebase is light, simple and scalable. ECSS is a global
approach to producing efficient design systems with CSS.
Values
Intentionality
Clear objectives, rational decisions.
Every parcel of CSS has an objective. This objective should be clear
to anyone reading the code. Be it HTML or CSS. Selectors are the
perfect vehicle for intentionality. ECSS incentivizes the use of
rational selectors.
Consistency
Repeating patterns save time & energy.
Flexible naming guidelines give the codebase a consistency
encouraging standardization and reuse. Prefixes and usage guidelines
enforced through linting insures everyone on your team will follow
ECSS rules.
Simplicity
"Simple" is writing less code & and limiting dependencies.
Class soup & divitis aren't simple. Untold numbers of @media queries
aren't either. But modern CSS is. By accepting CSS for what it is (a
designing language!) you can do a lot of styling mileage with very
little code.
Expressivity
Code that speaks for itself is actually achievable.
"thumbnail as-circle with-border" is instantly understandable while
"h-10 w-10 bdr-50 br-1 overh" is not. Code should communicate
information. The clearer the information, the easier it is to
understand the system. Mistaken semantics should give way to
expressiveness. Expressive best practices work.
Predictability
Using simple but consistent authoring rules leads to predictability.
It feels good to dive into unknown code... while already knowing what
it looks like. Consistency, repeating patterns, simplicity (yes,
again) all coalesce into predictable code. And predictable code means
less stress, less friction and happier teams!
Sustainability
Vanilla CSS is future ready.
In practice it means being at the forefront of progress and
evolution. No need to wait for third parties to implement new
features. You can use clamp() as of now and say goodbye to 80% of
your media queries. No more sm-this md-that. Only clean and modern
code.
Guiding principles
CSS is a designing language, not a programming one.
CSS authoring should reflect design patterns, concepts and
perspectives. With global (sorry) scales, tokens, rules, etc. you
limit repetition and facilitate future styling extension.
Empowering designers to refine their work autonomously is efficient.
Making the needed parts of CSS being intelligible (code-savvy or
not!) to designers cuts stress, time and promotes good allocation of
human resources. Refining design in the browser is a great vector of
economic profitability.
Not all CSS properties are born equal.
The box-model properties (display, position, width, etc.) are
extremely sensitive and potentially way more disruptive than, say,
color or typographic ones. The former are the stuff of professional
CSS authors and the latter are the ones designers should be able to
use, modify and experiment with in context.
Properties & units have specific roles and usage.
Limiting their use to these roles enhances clarity and promotes
consistency as well as predictability. The same goes for units. Some
suggest type, others, images or rhythm. Use them intentionally &
rationally to communicate meaning.
Components and patterns need structure.
Trying to abstract it away is vain. Instead, documentation and
dilligence are key. HTML is semantically rich, so choose the right
tags and refrain from changing them for superficial reasons. One
would not remove or change class="header" without understanding its
purpose first, then one should not change or remove
on a whim either.
CSS Selectors are vehicles for intention.
Every selector particle must have a purpose and if not, must be
discarded with. This extraneous div in .card div h2 is what makes CSS
brittle. These are not "best practices".
Selector types all have a function.
Each one of them may be of use in a certain context. No absolute
usage rule should be edicted. It's not "always" or "never" but "it
depends" and "why". Absolute rules absolutely corrupt. Or something
like that.
Selection specificity should be harnessed, not rejected.
Nevertheless, "always" (ahem) keep it as low as possible. Yes, in
2015 it may have not been easy. But it's 2023 and new features are
now widely supported. Use them.
Global scope in CSS is not a sin.
Remember, CSS is a designing language and the global scope fits
perfectly in design's fundamentally layered, dimensional approach.
Programming best practices are not necessarily designing best
practices. Embrace design best practices.
Global design rules must be largely anonymous & unqualified.
Only a few declarations per ruleset only. We are talking rhythm,
typography, color; not display or position. This is the way of
limiting repetition, bloat & complexity.
One should wait until a concept is clearly understood before naming
it.
Until then use HTML elements. Premature abstraction or "solving
problems not yet encountered" are bigger problems than the use of
header in CSS. Yes, these are programming best practices. But who
told you CSS development was pure design?
When more than 3 rules are repeated more than 3 times, a concept is
emerging.
Rules should then be concentrated by naming and using said concept
instead of the bare rules. Reusability, expressivity and simplicity
are then greatly improved.
Named concepts must be autonomous and self-sufficient.
Named concepts should not be defined inside another named concept. If
it ever is necessary, the child concept must be internal to the
parent concept and is not to be reused anywhere else.
Manipulating named concepts throughout multiple files is strictly
prohibited.
All rules for a named concept must reside in the same, unique CSS
file. For authors and maintainers to have confidence in the system,
there must a single source of truth for any concept.
State, variant or structural are child concepts and must be
namespaced & prefixed.
These concepts may be represented as child or combined classes. Never
to be used alone, always alongside the parent concept. Yes, here are
both "never" and "always" in the same sentence. "Double jeopardy, we
are fine".
Namespaced classes should generally be implemented with specificity
reducing selectors.
Unless otherwise needed, one should strive to keep specificity at 21
or below. With a preference for around 10 to 12. Modern and widely
supported pseudo-selectors must here be used.
One may not come up at first with the efficient way to select
interface elements.
One's intention may not be clear at the beginning of a styling job.
Instead of fruitlessly trying and trying, a code "quarantine" file
should be temporarily used until the right selection manifests
itself. Still, no quarantine file should ever be published publicly.
HTML should be as simple and expressive as can be.
Refrain from over-containing, don't use
where you could use <
aside>. Dont wrap your single-level navigation in unordered lists.
And yes, simple navigation is accessible. Keep it simple... Suzy.
Any HTML element should endorse one role only.
As per the famous programming best practice (again, programming in
designing), semantic tags are for... semantics whilst
or
tags are for graphical or logical division. Any type of grid should
be implemented with
tags.
Any @media query adaptation must be included in its related concept's
file.
Not as as a separate file, not as suffixed classes. If one uses
utility classes, the provided rules should not be query dependent but
rather be universally needed, in every media configuration.
Whole stylesheets are better used when global.
Media query scopes should be used in HTML tags to promote
reuse, performance and optimization concerns. Each fundamental design
layer should be autonomous, independent and removable.
Component styling should only be served with live components.
Not as a big minified file in the tag of the document. This
way, the infamous problem of loading unused CSS is practically
solved, without any technological debt. First render is also faster
since only what is used is processed by the browser. As a bonus, you
get the CSS file path in component file.
Let the browser do as much of the rendering work as it can.
By embracing fluidity, adaptativity is mostly done by the browser.
Less rules are needed to insure correct rendering in the infinite
number of usage contexts.
Make full use of freely available development tools by going with the
grain of the Web platform.
By using "just-in-time" rulesets, design tokens, the cascade and
intentional selection, the debugging & refining workflow is simpler,
lighter and clearer. Devtools are our friends!
Eschewing technological abstractions like high-level frameworks spurs
lighter, simpler native code.
By writing native CSS, one uses CSS better; by writing native HTML,
one writes HTML better. With better CSS and better HTML comes a
better user experience.
Authoring rules
All component selectors must start with its filename.
Principles
* Autonomous concepts
* Concept manipulation
Notes
To exclude a file from this rule in the Stylelint config, prefix the
filename with a digit or "x-" as in "x-quarantine.css"
Example
/* Do */
/* card.css */
.card {
...
}
/* Don't */
/* card.css */
.small-card {
...
}
---------------------------------------------------------------------
All paddings should first be uniform and then be adjusted if
necessary.
Principles
* Specific role
Notes
Non-uniform paddings may be used in particular instances for overflow
reasons or for added pseudo-content.
Example
/* Do */
.card {
padding: 20px;
padding-top: 30px;
}
/* Don't */
.card {
padding: 30px 20px 20px 20px;
}
---------------------------------------------------------------------
Paddings are to be applied on containing and interactive elements
only.
Principles
* One role only
Notes
Text elements should only host text styles while container elements
can host theming styles. In some instances, container elements could
host text styles to be inherited by children.
Example
/* Do */
.card header {
padding: 10px;
}
/* Don't */
.card h2 {
padding: 10px;
}
---------------------------------------------------------------------
Horizontal margins should not be applied to text elements.
Principles
* Specific roles and usage
* One role only
Notes
Horizontal margins can be applied to text elements in the case of
inline elements such as icons or tags & on indented elements like
blockquote.
Example
/* Do */
.card {
padding: 20px;
}
/* Don't */
.card p {
margin-left: 40px;
margin-right: 40px;
}
---------------------------------------------------------------------
Typographic elements should use inheritance when possible.
Principles
* Specific roles and usage
* Global scope
Example
/* Do */
html {
line-height: 1.2;
}
/* Don't */
p {
line-height: 1.2;
}
---------------------------------------------------------------------
Design tokens should be used for most numerical values.
Principles
* Designing language
* Designer empowerment
* Development tools
Notes
Numerical values should only be used for exceptional alignments.
These should be commented to communicate the intent. Even then, why
not create a bespoke property meaningfully named?
Example
/* Do */
.card {
padding: var(--small-spacing);
}
/* Don't */
.card {
padding: 20px;
}
---------------------------------------------------------------------
Repeating default rules is discouraged.
Principles
* Selector intention
* Selector function
Notes
Any instance of repeating default must be justified in a comment.
Example
/* Do */
.dropdown:not(:hover) {
display: none;
}
/* Don't */
.dropdown {
display: none;
}
.dropdown:hover {
display: block;
}
---------------------------------------------------------------------
All class entities besides components must be prefixed.
Principles
* Child concepts
* Selector intention
* Selector function
* Autonomous concepts
Notes
We propose this nomenclature of prefixes:
* as-: generic graphical form.
* of-: group of elements.
* is- interactive state of the system.
* on-: user interaction.
* in-: internal concept.
* with-: functional rule.
* from-*-to-: adaptive patterns.
Example
/* Do */
.card.as-circle {
width: 20px;
}
.card .in-ctn {
display: grid;
}
/* Don't */
.card.circle {
width: 20px;
}
.card .ctn {
display: grid;
}
---------------------------------------------------------------------
Nesting is restricted to one level only.
Principles
* Child concepts
* Selector intention
Notes
Nesting can encourage authoring on autopilot, without thinking about
our selection intention. That's one reason why we will ultimately
write .card div h2 instead of .card h2, fragilizing our CSS. One
level of nesting should be enough almost all cases.
Example
/* Do */
.card {
& header p {
color: grey;
}
}
/* Don't */
.card {
& header {
& p {
color: grey;
}
}
}
---------------------------------------------------------------------
The use of specific dimensions should be avoided.
Principles
* Properties are not equal
* Specific role
* Browser rendering
Notes
The Web is fluid and authoring should embrace this fluidity. By
choosing to limit dimensions of our components instead of dictating
them, we delegate work to the browser, letting it do its job. Fixed
dimensions should be only be applied to graphical elements like
pictures, videos and icons.
Example
/* Do */
.card {
max-width: 30ch;
}
/* Don't */
.card {
width: 30ch;
}
---------------------------------------------------------------------
Use attribute selectors to convey unicity.
Principles
* Selector intention
* Specific role
* Harness specificity
Notes
Ids convey meaning but their CSS selector is a specificity nightmare.
Use attribute selector to keep the meaning (unicity!) while keeping
specificiy at sane levels. Other use cases includes form controls &
inputs and js managed states (ie: [data-state="active"])
Example
/* Do */
[id="main"] {
max-width: 80ch;
}
/* Don't */
#main {
max-width: 80ch;
}
---------------------------------------------------------------------
The use of positioning and display rules should minimized.
Principles
* Properties are not equal
* Browser rendering
Notes
Use these properties only on elements needing them. Use the narrowest
selectors that makes sense in the context.
Example
/* Do */
.card header {
display: flex;
}
/* Don't */
header {
display: flex;
}
---------------------------------------------------------------------
Overqualified selectors are discouraged.
Principles
* Harness specificity
* Selector intention
Notes
Class entities should not be combined with tag selectors. Generic
selectors can be used as a selection piece but not as qualifiers.
Exceptionally, certain specific tags such as html or body could be
used to restrict the application of a class entity.
Example
/* Do */
html.is-loading {
opacity: 0;
}
.card {
color: red;
}
/* Don't */
article.card {
color: red;
}
---------------------------------------------------------------------
Components cannot exert outside influence.
Principles
* Autonomous concepts
* Properties are not equal
* Specific roles and usage
Notes
Components are made to fit into "holes". These holes are managed by
another component or a higher-level utility (e.g. a grid or
carousel). No component in this sense should incorporate margins.
These are applied as a rhythm by a parent. Thus, the components are
versatile and reusable in various contexts.
Example
/* Do */
.as-pile>*+* {
margin-top: 20px;
}
/* Don't */
.card {
margin-top: 20px;
}
---------------------------------------------------------------------
Overflow should not be hidden.
Principles
* Browser rendering
* Properties are not equal
Notes
By hiding the way browsers treat overflowing items, we may hide
content without knowing. By using auto instead of hidden, the
oveflowing content will be accessible and the scrollbar will signal
the design problem instead of hiding it. Hiding scrollbars may be
necessary in some particular composition (see this very site's
portrait adaptation for an example!).
Example
/* Do */
.container {
max-height: 50dvh;
overflow: auto;
}
/* Don't */
.container {
max-height: 50dvh;
overflow: hidden;
}
---------------------------------------------------------------------
Numbers in class names are to be avoided.
Principles
* Selector intention
Notes
Numbers may convey intention in grid system cases as in, for example,
grid-10 or col-3. Still, the utility of these systems is low in
modern CSS times. Leaving them aside is suggested.
Example
/* Do */
.card {
color: grey;
font-size: 18px;
}
.card.as-featured {
color: red;
font-size: 20px;
}
/* Don't */
.card {
color: grey;
font-size: 18px;
}
.card-1 {
color: red;
font-size: 20px;
}
---------------------------------------------------------------------
Magic numbers must be avoided or explained in a line comment.
Principles
* Designing language
* Designer empowerment
* Specific roles and usage
Notes
Value and unit choices should be dictated by the graphic language and
its design tokens. Absolute values in numerical form should be
reserved to special cases like bespoke icon alignment or bullet list
padding. The intention must be clear in itself or commented if not.
Example
/* Do */
.card {
line-height: var(--line-tight);
}
/* Don't */
.card {
line-height: 17px;
}
---------------------------------------------------------------------
Rhythm must be applied in one direction only, preferably top.
Principles
* Designing language
* Specific roles and usage
Notes
Spacing is rhythm, a fundamental design concept. Rhythm occurs
between graphical elements and should be applied as such in CSS. The
lobotomized owl selector (+) is tailor made for this task. When using
a flex container, the gap property does it all for you!
Example
/* Do */
.as-pile>*+* {
margin-top: 20px;
}
/* Don't */
.as-pile>* {
margin-top: 20px;
margin-bottom: 20px;
}
---------------------------------------------------------------------
The use of problematic units is discouraged.
Principles
* Specific roles and usage
Notes
Static viewport units (vh, vw, vmin, vmax) are famously problematic,
particularly when used as 100vh or 100vw. Dynamic viewport units are
better suited for this task.
Example
/* Do */
nav {
min-height: 100dvh;
}
/* Don't */
nav {
min-height: 100vh;
}
---------------------------------------------------------------------
Tag selectors should be leveraged inside components.
Principles
* Component structure
* Expressive HTML
Notes
Components need to prescribe a certain HTML structure. Using said
structure through intentional tag selectors is the best way to insure
structural persistence, to keep specificity and complexity low and
prevent naming fatigue. Why rename that which is already named?
Example
/* Do */
.card header {
padding: 20px;
}
/* Don't */
.card .card__header {
padding: 20px;
}
---------------------------------------------------------------------
One entity per file with a soft cap of about a hundred lines.
Principles
* Autonomous concepts
* Concept manipulation
Notes
This is one of the most important rules for achieving sustainable
CSS. One autonomous concept per file prevents the multiplication of
overrides and redefinition across the codebase. It's even easy to
enforce with the ECSS Stylelint config.
Example
/* Do */
/* card.css */
.card {
padding: 20px;
}
/* .metainfo.css */
.metainfo {
color: grey;
}
/* Don't */
/* card.css */
.card {
padding: 20px;
}
.metainfo {
color: grey;
}
---------------------------------------------------------------------
Selectors must strictly include only necessary selection parts.
Principles
* Component structure
* Selector intention
* Harness specificity
Notes
This mistake, being passed as "best practices", is the source of many
self-induced CSS headaches. Fortunately, the solution is simple: do
not use selection parts that you could remove without changing the
original intention.
Example
/* Do */
.card p {
font-size: 14px;
}
/* Don't */
.card div p {
font-size: 14px;
}
---------------------------------------------------------------------
Duplication of a set of more than 3 rules is discouraged.
Principles
* Rule repetition
* Autonomous concepts
Notes
Repeating a pattern instead of naming it and grouping its rules
inside a unique declaration block is a classic CSS mistake. And it
can be hard to find and target instances of this problem. Tools like
PostCSS plugins can help by identifying repetition meeting a defined
threshold throughout a codebase.
Example
/* Do */
/* button.css */
.button {
padding: 10px;
color: white;
background-color: grey;
border: 1px solid black;
}
/* Don't */
/* card.css */
.card a {
padding: 10px;
color: white;
background-color: grey;
border: 1px solid black;
}
/* post.css */
.post button {
padding: 10px;
color: white;
background-color: grey;
border: 1px solid black;
}
---------------------------------------------------------------------
The use of the global scope is encouraged for all fundamental design
layers.
Principles
* Designing language
* Designer empowerment
* Global scope
* Global rules
* Harness specificity
Example
/* Do */
/* rhythm.css */
p+p {
margin-top: 20px;
}
/* Don't */
/* post.css */
.post p+p {
margin-top: 20px;
}
/* prose.css */
.prose p+p {
margin-top: 20px;
}
---------------------------------------------------------------------
HTML structure should be as flat as possible and bereft of single tag
nesting.
Principles
* Component structure
* Expressive HTML
Notes
Sometimes it could make sense to add a div even if it's the only
child. The case of a content grid comes to mind. See this page's
source for an example.
Example
---------------------------------------------------------------------
Specificity must be kept as low as possible.
Principles
* Harness specificity
* Child specificity
Notes
Sometimes it makes sense to add specificity. But don't do it until
proven necessary.
Example
/* Do */
.card :where(h1, h2, h3, h4, h5, h6) {
font-size: 24px;
}
/* Don't */
.card :is(h1, h2, h3, h4, h5, h6) {
font-size: 24px;
}
Related tools
* ECSS Stylelint config (get live authoring tips!)
* Scaffolding & code library (use it... or not!)
* Stylelint (lint and inform)
* CSSCSS (find rule duplication in your codebase)
* PurifyCSS, PurgeCSS or Chrome Coverage devtool