CSS is a designing language, not a scripting 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.
Efficient CSS is all you need to avoid common styling problems. Nothing more, really.
ECSS sets simple rules for efficient styling. No more naming everything, no more technological dependencies. Only intentional, consistent, expressive, predictable, sustainable CSS.
👮 Gently nudge you and your team into ECSS.
🏁 A quick & efficient starting point is within reach!
📰 Go ahead and see what you think of it!
Well, keep scrolling!
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.
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.
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.
“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.
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.
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!
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.
CSS authoring should reflect design patterns, concepts and perspectives. With global (sorry) scales, tokens, rules, etc. you limit repetition and facilitate future styling extension.
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.
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.
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.
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
<header></header>
on a whim either.
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”.
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.
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.
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.
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.
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?
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 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.
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.
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”.
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’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.
Refrain from over-containing, don’t use <div>
where you could use <aside>
.
Dont wrap your single-level navigation in unordered lists. And yes,
simple navigation is accessible.
Keep it simple… Suzy.
As per the famous programming best
practice (again, programming in designing), semantic tags are for…
semantics whilst <div>
or <span>
tags are for graphical or logical division. Any type of grid should be
implemented with <div>
tags.
@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.
Media query scopes should be used in HTML <link>
tags to promote reuse, performance and optimization concerns. Each
fundamental design layer should be autonomous, independent and
removable.
Not as a big minified file in the <head>
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.
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.
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!
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.
To exclude a file from this rule in the Stylelint config, prefix the filename with a digit or “x-” as in “x-quarantine.css”
Non-uniform paddings may be used in particular instances for overflow
reasons or for added
pseudo-content. Without these reasons, non-uniform spacing may be better
created with margins.
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.
Horizontal margins can be applied to text elements in the case of
inline
elements such as icons or
tags & on indented elements like blockquote
.
Inheritance allows for high-level styling. With little code, we cast a wide net while limiting specificity to a minimum.
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?
Certain properties need to be joined to others, using a precise structure, to be functional. It is important to keep this relationship clear and explicit. Nesting simply fulfills this role.
Any instance of repeating default must be justified in a comment.
Modifying components and entities across the code base, from one file to another, is the greatest source of unpredictability (and probably frustration) in CSS development. By forcing the use of explicit and exclusive prefixes, CSS becomes substantially more predictable, expressive and robust.
We propose this nomenclature of prefixes:
as-
: generic graphic form.of-
: group of elements.is-
interactive state of the
system.on-
: user interaction.to-
: functional role.__
: internal entity.with-
: functional rule.from-*-to-
:
adaptive patterns.&
.The use of &
in the selection makes the nesting
explicit and prevents comprehension errors due to long nestings.
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.
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.
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"]
)
Use these properties only on elements needing them. Use the narrowest selectors that makes sense in the context.
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.
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.
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!).
Adding a number to a class demonstrates a poor understanding of its role and communicates no clear intent. Ask yourself why you need to add a number and use the answer to explicitly name your class. If you need to use the structure to style certain elements, use the relevant pseudo-classes instead.
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.
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!
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.
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?
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.
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.
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.
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.
Sometimes it makes sense to add specificity. But don’t do it until proven necessary.