CSS files in the /develop/css
directory (css-root) contain only imports, but no custom rules. These files are compiled to (/public/assets/css
).
In the css-root there is an index.css
file. This file contains all fonts, libraries, configs, helpers and components that are relevant for most of the whole website.
For special pages a separate CSS file can be created. For example, this file can contain large libraries and/or components that are only relevant for certain pages/templates. Not everything needs to be placed in the index.css. Thus, the index.css can be kept small. The load time of the web pages are optimized thereby.
What should be swapped out to a separate css file?
In general, large CSS libraries (for example mapbox-gl.css) that are only needed on certain pages should not be imported into index.css.
Each Block must have a prefix (a
, m
, o
or t
, according to Atomic Design) followed by -
and the name of the block (e.g header
). Kebab-case is used, words are connected with -
.
Example: m-alert-banner
The name of the Element is separated by __
.
Example: m-alert-banner__heading
The name of a Modifier is preceded by -
. (e.g. -danger
). The Modifier isn’t hard wired to the Element (in pure BEM it would be m-alert-banner--danger
). This must be taken into account. The pros and cons of this technique are described in ABEM. A more useful adaptation of BEM. – CSS-Tricks.
Example: a-button -danger
Generally names should describe the function not the appearance. Names should be as general as possible but as specific as needed. A red “delete item” button should get a Modifier called -danger
instead of -red
. -red
is describing the appearance. -delete
is too specific, -danger
is more generic.
Variables should be named after the property for which they are intended.
Example: --transition-duration: 300ms
instead of --transition: 300ms
.
--transition: opacity 200ms linear
is fine, beacuse it describes the whole transition property.
Most of the time, variables are meant for a specific thing. To describe this specific thing, use -
to separate the attribute name from the thing.
Example: --transition-duration-popup: 500ms
For variables, the rule that function is preferable over appearance also applies.
Example: --font-family-heading
instead of --font-family-serif
.
Colors
Colors can be named according to their appearance. You should use --color-red
if you want something to be red. But if you want to indicate that something might have a negative effect, you should use --color-negative
. This could be an alias of --color-red
.
For graduations of colors you should use the numbers used for font-weight
, where 500 should be the default color.
--color-red-300: hsl(0, 90%, 70%);
--color-red-400: hsl(0, 90%, 60%);
--color-red-500: hsl(0, 90%, 50%);
--color-red-600: hsl(0, 90%, 40%);
--color-red: var(--color-red-600);
--color-negative: var(--color-red);
Nesting makes writing CSS much easier. But when it's overused, the specificity is getting higher and the indentations get longer and longer. It’s getting harder to read and more difficult to maintain. That's why not everything that can be nested is nested.
Each block get’s its own file (e.g. components/_m-alert-banner.css
. The file starts with the rule of the Block selector itself. The Block selector is followed by the Element selectors.
.m-alert-banner {
…
}
.m-alert-banner__heading {
…
}
.m-alert-banner__buttons {
…
}
Elements theoretically could be nested inside of the Block (&__heading
), but this would clutter the Block which make it harder to maintain and read.
Modifiers are written inside of each Block or Element. Preferably only Blocks do have Modifiers, but Elements can have Modifiers too.
.m-alert-banner {
&.-danger {
…
}
}
.m-alert-banner__heading {
.m-alert-banner.-danger & {
…
}
}
Inside a Block/Element there might be rules for other DOM elements. To avoid conflicts these rules should be very specific. Namely DOM elements should be selected only with the Child combinator.
.m-alert-banner {
> h2 {
…
> span {
…
}
}
}
Maximal two children should be nested.
The rules inside of a Block/Element could be very long that’s why a specific order could become handy to find things. After each group of rules there should be single empty line.
@keyframes
- Properties
See Order of properties - Pseudeo-elements
&::after
,::backdrop
- Child combinator
> h1
- Adjacent sibling combinator in combination with Child combinator
This is used to define how elements behave against other elements. E.g. themargin-block-start
of ap
element after ah1
element. (See The Stack: Every Layout)
> h1 + p
- Modifiers (Element with Block Modifier)
.m-alert-banner.-danger &
- Modifiers
&.-danger
@supports
rules- Pseudo-classes
:hover
,:focus-visible
- Adjacent sibling combinator for the Block/Element itself
This is used to define how this Block/Element behaves against other Blocks/Elements of the same kind.
& + &
- Adjacent sibling combinator for the Block/Element against other Blocks/Elements
This is used to define how this Block/Element behaves against other Blocks/Elements.
.m-form + &
@media
queries
(min-width: …)
beforenot all and (min-width: …)
.m-alert-banner {
@keyframes pulse {
…
}
--var-lorem: …;
padding: 1rem;
border: 2px solid var(--color-alert);
animation-name: pulse;
…
> h2 {
font-size: …;
}
> p {
…
}
> h2 + p {
margin-block-start: 1rem;
}
&.-danger {
…
}
@supports (…) {
…
&.-danger {
…
}
}
&:focus-inside {
> h2 {
…
}
}
& + & {
margin-block-start: 1rem;
}
.m-form + & {
…
}
@media (min-width: …) {
…
}
@media not all and (min-width: …) {
…
}
}
Blocks/Elements are independent components. That’s why Blocks/Elements may not be selected inside of another Blocks/Elements.
When it’s needed to style a Block depended on another Block, this could be achieved like this:
.a-button {
…
.m-alert-banner & {
background-color: …;
}
}
This syntax should be avoided. What if the a-button
is inside a m-dialog
(which is inside of m-alert-banner
). a-button
will get the background-color which might not be the intended behavior.
Modifiers should be used instead.
.a-button {
&.-alert {
background-color: …;
}
}
A mobile-first approach should be followed. In most cases, the user interface design for mobile devices is simpler than that for larger devices. Additional complexity (larger devices) is added to the simpler mobile styles. This is the reason why @media (min-width: …)
should be used.
To add specific styles for smaller devices the (min-width)
rule can be negated like this @media not all and (min-width: …)
Use rem
unit for media and container queries. 1
There are about 400 CSS properties. To quickly find the right property there is the following order. The list is incomplete. Missing properties can be sorted accordingly.
- variables
--var-*
- generated content
content
- appearance
position
,display
,appearance
,opacity
,visibility
,cursor
,pointer-events
- clipping
clip-path
,overflow
- clipping
- positioning
- grid/flex (Properties for grid/flex item)
grid-[row|column]-[start|end]
flex-base
,flex-shrink
,flex-grow
justify-self
,align-self
- inset/margin
inset
,z-index
,margin
- size
box-sizing
,inline|block-size
width
,height
,[min|max]-[[inline|block]-size|width|height]
, -translate
- border
box-shadow
,border
,border-radius
,outline
- padding
padding-[inline|block]
- content
- grid/flex (Properties for grid/flex container)
grid-template-[columns|rows]
,[justify|align]-[content|items]
,gap
- lists/columns/table
list-style
,columns
,border-collapse
- typography
text-[align|transform|...]
line-height
,letter-spacing
font-[size|weight|...]
white-space
,hyphens
color
- background
background-[image|color|...]
,backdrop-filter
- animation
transition
,animation
,will-change