Everything you need to know about the BEM CSS architecture

DDD
Release: 2024-10-02 06:12:02
Original
276 people have browsed it

I wrote this article aiming to answer questions about BEM that are generally not asked, but the lack of answers impacts the development and understanding of the architecture.

This article is not aimed at beginners who are getting to know CSS VERY well now and/or have never had contact with CSS class naming conventions. If this is your case, skip to the sources section.

Header adapted from the official legacy BEM CSS website

 

  • What is a CSS architecture for?
  • What is BEM CSS?
    • Categorizing UI elements according to their coupling
    • Expressing the relationship of components through CSS
    • Complex component relationships
  • Specificity control with BEM
  • CSS Reuse
  • BEM and Sass
  • Interactions with other architectures
  • Sources

? What is a CSS architecture for?

Like plants, the way a code will grow and prosper depends on how we create it, maintain it and where things are located.

There are plants with different needs for soil, treatment, water and sunlight, just as there are codes with different needs for standards, location and management.

CSS is a critical element in loading a page - the rendering process does not start before the browser requests, downloads and transforms all HTML and CSS into DOM and CSSOM and assembles the render tree. The less CSS, the better the performance, the more organized, standardized and robust the CSS, the better it is to maintain, manage, scale and compress.

The way CSS selectors are named influences the scope of the rules, specificity, location and semantics of these, speeding up or deteriorating the development process. In projects where more than one person writes CSS, we have different levels of skill in the language and different customs or personal standards, which if not managed can result in excessive repetition of rules and bugs.


? What is BEM CSS?

BEM is a CSS architecture focused on styleguide, it defines standards for categorizing and writing CSS rules. BEM was created at Yandex in a context of applications that contained several pages managed by one or a few style sheets.

BEM is the acronym for Block, Element, Modifier. Each of these entities represents a category of interface element, which is directly represented in CSS.

Tudo o que você precisa saber sobre a arquitetura BEM CSS

Representation of visually separated interface components, Source: BEM CSS official website

 


? Categorizing UI elements according to their coupling

A Block represents a UI element that is independent, it can have children, like a header has its navigation links and these children, if they are independent, can be blocks too.

The concept of independence of an interface component can be defined by the following axiom

"If the component only makes sense within a specific context, it must be an element of that context. If it can exist in several contexts, it must be an independent block."

An element is a component that makes up another larger one and belongs to it. If this component does not exist and does not depend solely on the context in which it is applied, it can be a block. In BEM, blocks can contain other blocks, if an element needs to contain an element, it is probably also a block.

The relationship between a block and an element is represented by the double underscore block__element.

Blocks and elements may contain aesthetic or layout variations within the same context. For this we have modifiers. These classes can represent different states or variants of the same component. Modifiers such as elements depend on the block and derive only from it.

The relationship between a block or an element and its modifiers is represented by the double dash block--modifier or block__element--modifier.

 

Referência Descrição
Tudo o que você precisa saber sobre a arquitetura BEM CSS Um checkbox é independente, pode ser aplicado dentro de outros componentes como
,
ou , por exemplo.
Tudo o que você precisa saber sobre a arquitetura BEM CSS Uma label pode ser considerada um bloco independente se ela for igualmente aplicada nos inputs da aplicação. Se a diferença entre labels for estética, ela pode ser um bloco que contém diversas variantes (modifiers), se a diferença entre as labels do input for estrutural (no template), faz sentido ela ser o elemento de um bloco
Tudo o que você precisa saber sobre a arquitetura BEM CSS Um card pode ser incluído em qualquer container, em diferentes contextos, podendo ser considerado independente (block). Se a imagem e textos dentro dos cards tiverem características que só faz sentido no contexto do card, elas podem ser consideradas elements
Tudo o que você precisa saber sobre a arquitetura BEM CSS Um botão pode ser administrado em qualquer lugar, inclusive mais de uma variante no mesmo lugar. Cada variante pode ser representada pela classe modificadora derivada do mesmo bloco ou elemento.

 

? Expressing the relationship of components through CSS

Using the example of checkbox, the way we build components and define their responsibilities influences what can be a block, element or modifier. This decision making starts with the template:

<div class="form-field">
  <input
   class="form-field__input form-field__input--checkbox" 
   type="checkbox"
   value=""
   id="checkbox"
  />
  <label class="form-field__label" for="checkbox">
    Default checkbox
  </label>
</div>
Copy after login

In this template we have the .form-field component as a block, which will always contain a .form-field__input input and a .form-field__label class label. The relationship between block (.form-field) and element (label or input) is expressed by a double underline. This writing means that through CSS we understand that:

  • form-field is an independent entity, it does not depend on the context of the component or parent container for its styles to work, any form can receive fields of type .form-field

  • In turn, the form-field can contain an __input and a __label, and its layout and appearance depends on the form-field container

.form-field {}
  .form-field__input {}
  .form-field__input--checkbox {}
  .form-field__label {}

Copy after login

 

When we change the relationship in the template, we change the topography of the CSS class. In the example below input and label are separate entities:

<div class="column">
  <label class="label" for="checkbox">
    Default checkbox
  </label>
  <input
   class="input input--checkbox"
   type="checkbox"
   value=""
   id="checkbox"
  />
</div>
Copy after login
  • .column in this case can be seen as both a block and simply a utility class. In addition to other CSS architectures, it is possible to use BEM with utility classes, including those following their own convention.
  • .input is a block that refers to all inputs, and can be used within a .column container or any other, working regardless of the existence of .label. It has a --checkbox variant that loads specific styles for input of the checkbox type, such as the status of :checked.
  • .label is a block whose layout and appearance do not depend on the input, and can even have an .input as a child element.

Expressed in CSS, it would look like this:

.column {}

.input {}
.input--checkbox {}
.input--checkbox:checked {}

.label {}

Copy after login

 

BEM focuses on the independence of components - changing a component's location in the HTML should not affect its layout, changing the CSS of a component should not affect the other components. When we create elements, we demonstrate a list of what will be affected when we change their block. This way you have visibility of what would be a component coupled with its parts and a completely independent module. As cited in the legacy documentation:

In 2006, we started working on our first large projects: Yandex.Music and Ya.Ru. These projects, with dozens of pages, revealed the main drawbacks of the current approach to development:

  • Any changes to the code of one page affected the code of other pages.
  • It was difficult to choose classnames.

Translation

In 2006, we started working on our first big project: Yandex.Music and Ya.Ru. These projects that had dozens of pages revealed themselves to be the disadvantages of the current development approach:

  • Any change to the code of one page would affect the other pages.
  • It was difficult to choose how to name the classes.

 

? Complex component relationships

In 2010, when BEM became open source and gained a website, the idea that each UI element was a 'component' was already present, even before atomic design (Brad Frost, 2013) in response to the challenge of maintain consistency of the same components on different pages. When BEM was created at Yandex, for example, they used several HTML files and only one CSS and one Javascript file.

If each page was a block and its elements were dependent on the context of that page, all code that was consistent between pages would be duplicated. Separating the UI into small components allowed the reuse of these parts regardless of which pages they were used on.

To exemplify this concept of reuse, we can use a more complex component, such as a header:

Tudo o que você precisa saber sobre a arquitetura BEM CSS

This header can be written as follows (short HTML):

<nav class="navbar">
  <div class="navbar__container">
    <a class="navbar__logo" href="#">Navbar</a>
    <button class="navbar__toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar__toggler-icon"></span>
    </button>
    <div class="navbar__collapse" id="navbarSupportedContent">
      <ul class="navbar__list">
        <li class="navbar__item">
          <a class="navbar__link active" aria-current="page" href="#">Home</a>
        </li>
        <!-- etc -->
        <li class="navbar__item navbar__dropdown">
          <a class="navbar__dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Dropdown
          </a>
          <ul class="navbar__dropdown-menu">
            <li>
              <a class="navbar__dropdown-item" href="#">
                Action
              </a>
            </li>
            <!-- etc -->
          </ul>
        </li>
        <!-- etc -->
      </ul>
      <form class="navbar__form" role="search">
        <input class="navbar__form-input" type="search" placeholder="Search" aria-label="Search">
        <button class="navbar__form-btn" type="submit">
          Search
        </button>
      </form>
    </div>
  </div>
</nav>

Copy after login

Notice that in this example the entire navbar was declared as one big block and everything it contained was considered element of navbar. As a result, we lose several opportunities for reuse.

Oportunidade Descrição
Tudo o que você precisa saber sobre a arquitetura BEM CSS O logotipo dentro de .navbar__logo é representado por um link contendo imagem ou texto. Existem diversos elementos que podem ser 'envelopados' em um link, como ícones back to top ou cards clicáveis. Esse código poderia pertencer a um bloco .link ou .media
Tudo o que você precisa saber sobre a arquitetura BEM CSS Os itens .navbar__list e .navbar__item nada mais são que uma lista desordenada no formato de linha ao invés de coluna. Isso poderia reutilizado representando-a pelo bloco .list com o modificador .list--row. O dropdown por sua vez pode ter o mesmo funcionamento fora do navbar, dentro do seu próprio bloco .dropdown, com seus elementos .dropdown__toggle e, como ele renderiza uma lista, podemos reutilizar .list e, se necessário, complementar com um modificador
Tudo o que você precisa saber sobre a arquitetura BEM CSS Esse componente de busca pode ser representado de forma mais "fechada" com um bloco .search e elementos .search__input, search__label e .search__trigger ou de forma mais "aberta" como .form-field, .input--text, .label e .button--outline

 

Ainda pensando nessa relação de componentes "abertos" e "fechados", podemos representar o componente de busca usado no último exemplo das duas forma usando CSS.

Componente de busca "fechado"
Os elementos input, label e button são acoplados ao bloco search, mudanças no componente ou no HTML de search influenciam nos seus elementos.

.search {}
  .search__input {}
  .search__label {}
  .search__button {}
Copy after login

Componente de busca "aberto"
Os elementos input, label e button são independentes do bloco de search, ele apenas organiza os blocos já existentes que compõe esse componente.

.form-field {}

.input {}
.input--text {}

.label {}

.button {}
.button--outline {}
Copy after login

? Controle da especificidade com BEM

Um dos problemas citados pela galera do Yandex é que mudanças no CSS podiam afetar partes indesejadas. No caso deles era utilizada uma folha de estilo pra todas as páginas da aplicação, e você pode achar que não é um problema pra ti se você usa algum processador como Sass ou diversos arquivos de CSS, mas é uma dor de cabeça ainda maior quando seletores de diferentes abrangências moram em múltiplos arquivos.

A forma que o BEM encontrou de contornar esse problema além da modularização de blocos é a especificidade. BEM só utiliza classes por motivos bem claros:

  • Classes podem ser reaproveitadas
  • Como você se refere diretamente aos elementos usando as classes, a especificidade tende a ser sempre muito próxima de 0,1,0
  • Pra alterar ou sobrescrever uma regra, basta adicionar outra classe
  • Regras que se referem à seletores mais globais como de resets ou normalizers são facilmente sobrescritas pelas classes.

Abaixo vemos um accordion que mantém uma diversidade de seletores, pra nos referirmos, por exemplo ao h2 desse accordion, temos que explicitamente dizer #accordion-item > h2:

<div id="accordion-item">
  <h2>
    <button class="btn">
      Accordion Item #1
    </button>
  </h2>
  <div class="collapse show">
    <div>
      <strong>This is the first item's accordion body.</strong>
    </div>
  </div>
</div>
Copy after login

 

Pra estilizar todos os elementos desse componente, precisamos estabelecer as seguintes relações:

/* 1,0,0 */
#accordion-item {}

/* 1,0,1 */
#accordion-item > h2 {}

/* 1,1,0 */
#accordion-item .btn {}

/* 1,1,0 */
#accordion-item .collapse {}

/* 1,2,0 */
#accordion-item > .collapse.show {}

/* 1,1,1 */
#accordion-item > .collapse > div {}

/* 1,1,2 */
#accordion-item > .collapse strong {}

Copy after login

Esse exemplo assume que .collapse por não se referir diretamente ao accordion (não é um .accordion-collapse, por exemplo) pode existir em outros lugares do código ou até em outros lugares de #accordion-item. Isso obriga a gente a deixar explícita sua relação com #accordion-item.

A regra #accordion-item > .collapse > div existe pois o estilo não pode impactar qualquer div, nem qualquer .collapse, pra alterar apenas aquilo que existe dentro do bloco #accordion-item precisamos adicionar muito mais complexidade e especificidade, além de que qualquer mudança no HTML desse componente causa bugs imprevisíveis.

<div class="accordion-item">
  <h2 class="title">
    <button class="button">
      Accordion Item #1
    </button>
  </h2>
  <div class="accordion-item__collapse">
    <div class="accordion-item__wrapper">
      <strong>This is the first item's accordion body.</strong>
    </div>
  </div>
</div>
Copy after login

No HTML acima estabelecemos as relações entre o .accordion-item e o .accordion-item__collapse sem adicionar especificidade. Ao nos referirmos diretamente aos elementos pela classe e não compondo seletores, quando alteramos o .accordion-item podemos quebrar o .accordion-item__collapse, mas ao alterar .accordion-item__collapse, o .accordion-item dificilmente será influenciado.

.title e .button serem blocos independentes faz com que alterarmos o HTML desse componente ou o CSS dessas classes não cause nenhum bug, pois elas existem de forma independente do bloco do accordion. Em relação à especificidade, a relação passa a ser da seguinte forma:

/* 0,1,0 */
.accordion-item {}

/* 0,1,0 */
.accordion-item__collapse {}

/* 0,1,0 */
.accordion-item__wrapper {}

/* 0,1,0 */
.title {}

/* 0,1,0 */
.button {}

Copy after login

 

A especificidade dessas regras é muito mais previsível, tanto que se eu quiser adicionar uma variante ao .accordion-item__wrapper basta eu criar uma classe com as regras e incluir no elemento no HTML, sem precisar criar no CSS um seletor .accordion-item__wrapper.accordion-item__wrapper--vertical.


? Reuso de CSS

Quando me refiro a reuso de CSS, não falo apenas de blocos que podem ser reaplicados em diferentes componentes, à nível de seletores, mas também à conjuntos de regras que realizam as mesmas ações. No exemplo abaixo, temos 3 componentes diferentes e mesmo se escritos na convenção do BEM haverá repetição de conjuntos de regras:

Elemento Regras
Tudo o que você precisa saber sobre a arquitetura BEM CSS
.alert {
  display: flex;
  gap: 0.5rem;
  /* etc... */
}
Copy after login
Tudo o que você precisa saber sobre a arquitetura BEM CSS
.nav {
  display: flex;
  gap: 1rem;
  /* etc... */
}
Copy after login
Tudo o que você precisa saber sobre a arquitetura BEM CSS
.breadcrumb {
  display: flex;
  gap: 0.5rem;
  /* etc... */
}
Copy after login

 

Percebe como todos os elementos que tem a disposição de 'linha' muito provavelmente terão um grupo de regras de display: flex o configura dessa forma, terão um gap: npx e provavelmente um flex-wrap: wrap quando fizer sentido pro layout 'quebrar' pra linha de baixo?

Usando o breadcrumb como exemplo, podemos criar um bloco que represente uma linha ou coluna genérica e que seja configurável à partir de variantes.

<ol class="breadcrumb">
  <li class="breadcrumb__item">Home</li>
  <li class="breadcrumb__item active">Library</li>
</ol>
Copy after login

Ao invés de repetir o conjunto de regras que configura uma coluna, podemos adicionar à classe .breadcrumb o bloco de .row. Não há restrições sobre o mesmo elemento ser representado por dois blocos distintos, pois o conceito de bloco é acoplado com seus elementos, não com componentes específicos.

/* Breadcrumb deixa de configurar o layout e passa apenas a implementar estilos estéticos */
.breadcrumb {
  font: 200 1rem Poppins;
  line-height: 1.45;
}

/* Row se torna responsável pelo layout */
.row {
  display: flex;
  gap: 1rem;
}

/* E pode ter variantes na convenção BEM caso conveniente */
.row--flexible {
  flex-wrap: wrap;
}

Copy after login

Nesse caso o gap de .row é estático, mas os elementos apresentados nos exemplos tem diferentes valores nessa propriedade, de quem é a responsabilidade? Se você possuí uma estrutura de CSS utilitário, a responsabilidade pode ser de .row que será configurada pelo CSS utilitário .gap-0.5 ou .gap-sm.

Se você não usa CSS utilitário, o gap pode ser configurado pelo próprio componente breadcrumb:

/* Ao implementar o gap com variáveis CSS temos um valor
default e um valor configurável */
.row {
  --row__gap-default: 1rem;
  display: flex;
  gap: var(--row__gap, var(--row__gap-default));
}

/* Dessa forma podemos configurar o gap pelo breadcrumb.
A row ainda não depende dele, mas caso ele seja aplicado com
uma row, ele a configura */
.breadcrumb {
  --row__gap: 0.5rem;
  font: 200 1rem Poppins;
  line-height: 1.45;
}

Copy after login

Separar os estilos de layout dos de aparência é uma prática do OOCSS (link do artigo na Smashing Magazine, em inglês). Categorizar o CSS em skin e structure vem do entendimento de que a estrutura e layout de um elemento ou componente é mais reaproveitável e ubíqua do que a aparência de um componente.

Usando a convenção BEM é possível criar blocos que se referem à estruturas e modificadores que se referem à aparência, sendo ela apenas uma convenção de escrita de classes, a categorização e separação dessas classes pode ficar na responsabilidade de outro tipo de arquitetura.


? BEM e Sass

Sass foi criado em 2009, um ano antes de BEM se tornar open source, a interação entre eles funciona extremamente bem (rsrs) por dois motivos:

  • Sass permite a criação de múltiplas folhas de estilo e a co-locação dos estilos de acordo com as necessidades do projeto,logo podemos criar uma folha pra cada bloco ou conjunto de blocos que se referem a um componente ou contexto.
  • Sass tem suporte à concatenação de string, o que torna ainda mais visual a relação da classe com seus elementos e modificadores. Pensando no exemplo do bloco .search citado anteriormente:
.search {
  &__input {}
  &__label {}
  &__button {}
}
Copy after login

 

Com CSS a relação de blocos e elementos se dá pelo prefixo do bloco, ex: .search__, no Sass os elementos são declarados dentro do bloco.

Um problema muito comum com Sass é o 'overnesting', que é quando aninhamos múltiplas classes em um bloco. Essa abordagem além de dificultar a legibilidade do bloco cria especificidade sem necessidade e acopla os elementos ao bloco mais do que o necessário.

/* Esse tipo de nesting ao invés de gerar seletores  0,1,0 */
.search {
  .search__item {}

  .search__label {
    &.search__label--floating {

    }
  }
}

/* Gera o CSS */
/* 0.2.0 */
.search .search__item {}

/* 0.2.0 */
.search .search__label {}

/* 0.3.0 */
.search .search__label.search__label--floating {}
Copy after login

O overnesting geralmente acontece pelo não conhecimento de como o Sass funciona ou pela tentativa de imitar o formato do HTML no CSS.


? Interações com outras arquiteturas

Como naming convention BEM é flexível quando nos apropriamos de outras arquiteturas ou apenas características delas. Como citado na documentação:

No matter what methodology you choose to use in your projects, you will benefit from the advantages of more structured CSS and UI. Some styles are less strict and more flexible, while others are easier to understand and adapt in a team.

Tradução

Não importa qual metodologia você use em seus projetos, vbocê vai se beneficiar das vantagens de uma UI e CSS mais estruturados. Alguns estilos são menos estritos e mais flexíveis, outros sãi fáceis de entender e adaptar em um time.

 

Podemos escrever as regras do tipo block do CUBECSS na convenção do BEM.

A diferença entre os blocos do CUBECSS e do BEM é o seu escopo, no CUBE há uma separação semântica entre estrutura, configuração e aparência. Após declarar a camada de Composition que defini layouts num nível mais macro (Como o object no OOCSS ou ITCSS) e criar classes utilitárias na camada de Utility, sobra pouca configuração a se fazer na cabada do Block, o deixando mais enxuto.

Podemos também criar as diversas camadas propostas pelo ITCSS e criar Objects, Components e Utilities na sintaxe do BEM. O ITCSS não define a forma que criamos modificadores e a relação dos componentes e seus estados/variantes, mas sim a taxonomia e localização dos elementos respeitando seu escopo e dando previsibilidade na sua especificidade e posição na cascata.

Podemos expressar as mesmas relações do SMACSS, por exemplo, declarando states quando esses alteram um módulo específico na sintaxe BEM:

/* Na convenção SMACSS */
.tab {
  background-color: purple;
  color: white;
}

.is-tab-active {
  background-color: white;
  color: black;
}

/* Na convenção BEM */
.tab {
  background-color: purple;
  color: white;
  &__is-active {
    background-color: white;
    color: black;
  }
}
Copy after login

States que não são específicos de um módulo podem ser declarados como classes utilitárias ou blocos independentes.

Há muitas outras formas de se escrever BEM, não necessariamente vinculadas a uma arquitetura, como no exemplo abaixo.


Afinal, BEM é a melhor arquitetura de CSS?

Não existe 'melhor arquitetura'. Todas as metodologias já propostas são claras nos problemas que elas visam resolver, e elas não necessariamente vão resolver todos os problemas da sua organização, time ou codebase. BEM pode resolver seus problemas de acoplamento de classes e especificidade, mas talvez não resolva o de performance ou localização.

O interessante é aumentar seu gabarito de metodologias, convenções e arquiteturas e escolher sua ferramenta de acordo com os problemas que tu visa resolver.

BEM num contexto de aplicação Vue com SFC pode ser incrível quando localizamos o bloco CSS no mesmo lugar que o componente que ele estiliza, mas pode gerar muito CSS duplicado ou inutilizado se não houver uma arquitetura mais voltada pra reuso de estruturas, como OOCSS ou CUBECSS.


? Fontes

  • Artigo BEM: guia definitivo do padrão CSS mais famoso de Tárcio Zemel (DPW), em português (Muito bom)
  • Vídeo BEM: A Convenção CSS Para Um Front End Muito Melhor de Tárcio Zemel (DPW), em português
  • Artigo An Introduction To Object Oriented CSS (OOCSS) na Smashing Magazine, em inglês
  • Artigo MindBEMding – getting your head ’round BEM syntax de Harry Roberts, em inglês
  • Artigo BEMIT: Taking the BEM Naming Convention a Step Further de Harry Roberts, em inglês
  • Artigo Side Effects in CSS de Phillip Walton, em inglês
  • Artigo BEM 101 em Smashing Magazine, em inglês

The above is the detailed content of Everything you need to know about the BEM CSS architecture. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!