Closed mrkrsl closed 5 years ago
One comment on naming in CSS
There are 2 main approaches to CSS: traditional semantic markup and BEM (Block__Element--Modifier). We use BEM and I want to explain why.
1) But first.. Traditional semantic markup
<menu>
, <article>
, <h5>
and so on. There are also a few meaningless tags (div, span, b, i) and we strive to use them as rarely as possible. This is SEO and parser friendly, so we build bot-consumable websites with better accessibility out of the box, yay!p.caps
- there will be some styles assigned to p
first (font-size, font-family, line-height, font-weight), but there is an additional requirement that some elements on the website should be upper-cased, we define .caps
class. When it comes to upper-casing a paragraph, we cascade .caps
class rules over p
tag rules. This is fine, especially when we have design spec and understand well what the end result looks like and we know that it will never change. My example is too simple, I provide it for reference, in my practice I saw plenty of real-world examples more complex that this one, but idea is the same: this called "cascading". tag > class > another class > yet another class > maybe id > inline style
, that order MATTERS (if same rule e.g font-size
defined in a few places, the latest one will override all previous), each piece of this inheritance chain might be important for OTHER ELEMENTS who share same classes/tags/ids, in many cases you can't touch this "just to be on the safe site" and what you do? Add another class to increase "selector weight" and overrule some rules (or go with !important
if there is no time to explain), or copy-paste closest part and rename it so your adjustments won't hurt other parts of the app >> this makes changes hard to address, so in practice it all ends up with !important
or really long chains of classes.TL; DR; in order to ship fast an be able to frequently re-do HTML+CSS markup people invented BEM
2) BEM (Block__Element--Modifier)
.post
is Block, .post__title
is Element "title" of a Block "post"; .post .post__title
because .post__title
can ONLY exist inside of .post
and it ONLY describes the "title" element (e.g. font-size, color); .post__p
stands for an Element p
(paragraph) of a "post" Block. We don't try to style up all the paragraphs on the app (like we did if we were styling p
on tag level), we only want to be sure that within every "post" all paragraphs will look as they should. There might be other paragraphs on the app, we don't care while we work with .post..
block. .post__p--bold
is an example of a Modifier part. Modifiers don't exist on their own, they cascade on top of element and slightly "modify" its appearance, but no more. So this reads as there is a "post" Block, it contains "p" Elements, some of them might look slightly different when "bold" modifier applied. Selector will look like .post__p.post__p--bold
because we want all styles of .post_p
plus all styles modified by .post__p--bold
--bold
, --row
, --column
, local css variable --gap--small
), Block and Element are nouns. .box
is a perfect use case for BEM as we want this UI element to be consistent cross-apps using the framework (in case if change needed, we still can override base .box
styling with specific to a given app, so it's not a blocker but huge helper consistency-wise) Take this markup as an example
input.css, group.css
<div class="input-group">
<label class="label--left" for="[id]">[…]</label>
<input id="[id]" placeholder="Text">
</div>
1) class name is .input-group
so css file that contains this code should be called "input-group.css", according to BEM this is Block name (it consists of 2 words so they are -
separated, this is fine)
2) each child (Element) of the block, according to BEM, should start with the same Block name (as a prefix) e.g. it could be .input-group__input
where input-group
part is "block", input
part is "element. __
is a separator between Block and Element.
3) Looking at UI here we have 2 tasks to address: 1. style UI form elements (label
and input
) 2. line them up as a row, align vertically to centre or baseline
3.1) label
is label, input
is input. We can think of these 2 as independent Blocks (.label
.input
) and this works fine, but probably this blocks are too granular. Plus to that, they usually come together (at least label can't live on it's own) so we mentally group them for example as .form-input
block, that contains label .form-input__label
and field .from-input__field
. Here input
tag is referred as .form-input__field
and we can think of possible modifiers for the field, e.g make it wide, big, 100% width, no-border e.t.c. Using Modifier .input-form__label--200
, we can define label width (CSS looks like width: 200px, text-overflow: ellipsis e.t.c to we think of the case when actual text is longer than 200px and it will be truncated using "…")
3.2) to line things up we now have Grid in CSS so here we have even more options
3.2.1) modify Block e.g as
.form-input--row
: this will put label next to field left to right, In CSS this looks like display: grid; grid-auto-flow: column; .form-input--row.form-input--reversed
(we can modify modifier if we like) this puts field first, and label next to it. .form-input--column
will put label on top and field to the next line. This approach is cool as we can define width for "label" column and even right align labels using some tricks
form-input.css
<div class="form-input form-input--column">
<label class="form-input__label" for="[id]">[…]</label>
<input class="form-input__field" id="[id]" placeholder="Text" type="text">
</div>
3.2.2) by looking at our UI we know that there is a frequent task to just put controls in the row one after another, and align properly; SO there can be an another Block dedicated to solve alignment problems, not necessary controls related, anything, e.g navigation items, breadcrumbs and so on: we often need to group things and line then up. We could think of a Block like .block
and provide it with bunch of modifiers regulating space between elements, flow direction, line wrapping and the most importantly: alignment. If we have a Block like that, same HTML could use it together with .form-input
specific styles as: .block.block--column.form-input
form-input.css
<div class=block block--column block--gap--small form-input form-input--error">
<label class="form-input__label" for="[id]">[…]</label>
<input class="form-input__field" id="[id]" placeholder="Text" type="text">
</div>
.block
adds css grid
.block--column
changes elements flow from top to bottom
.block--gap--small
(modified modifier) adds gap--small sized spacing between cells of the grid
.form-input
(can do nothing, in this case, we can omit it and specify form-input--error alone
.form-input--error
adds pink background to the whole block in case of error
First steps at a broader approach to common styles. Contains a set of flat files and some (possibly questionable) selector naming.
https://github.com/universalbasket/css/issues/5