Toàn tập về BEM CSS

5/5 - (1 bình chọn)

Nếu bạn là một Frontend Developer, việc phải xây dựng các layout trên một website sẽ cần tới việc đặt tên các class và sử dụng element một cách hợp lý.

BEM là một thỏa thuận, theo như cách nói của những ai thường nghĩ tới việc teamwork, cho việc xây dựng tiêu chuẩn cơ bản về đặt tên trong CSS.

BEM rất phù hợp với các site production, tức hoạt động phục vụ end-client thay vì các template để sửa.

Tại sao lại vậy? BEM đưa ra những quan điểm mang tính phân loại thuộc tính CSS cao và tính chất dùng lại thấp hơn nhiều so với việc sử dụng reuse class như các framework thông thường. Điều này sẽ được lý giải sau khi chúng ta nghiên cứu cách sử dụng BEM trong bài viết này.

Block trong BEM

Như bất kỳ phương thức tạo ra element nào đó trong HTML, BEM cũng định nghĩa Block.

Block ở đây thông thường có thể hiểu như các thành phần header, body, content, footer, cũng có thể là left-side hay right-side hay column nằm trong grid. Về cơ bản, tức là 1 Block thì cần chứa tối thiểu 1 Element ở trong.

Như vậy, với quy tắc của BEM, ta có thể xây dựng một số ví dụ mẫu sau:

Section Hero có thể là tập hợp các block:

  • .hero__container làm nhiệm vụ cân max width và padding left right
  • .hero__inner làm nhiệm vụ cân padding top bottom và điểm bám cho các element absolute (như button chẳng hạn).
  • .hero__header chứa Sub-title.
  • .hero__content chứa Headline và Description, và ảnh minh họa cho section
  • .hero__footer chứa button CTA

Bạn có thể thấy prefix của tất cả class đều là hero. Điều này giúp chúng ta nhận diện section này rất dễ dàng khi update mục tương ứng trong CSS.

Element trong BEM

Tiếp đến trong Block ta thường có các element, những đối tượng chịu tác động nhiều về typography hơn là layout.

Ở ví dụ là section Hero, có thể có các element như sau:

  • .hero__headline là Text cỡ lớn. Thường là H1 hoặc H2.
  • .hero__intro, .hero__description làm mô tả content.
  • .hero__image chứa ảnh minh họa
  • .hero__button với style riêng cho buttom trong section này

Bạn có thể thấy, các element này cũng có prefix hero đúng không nào?

Nhưng hãy lưu ý những điểm quan trọng sau đây:

  • Các element được gọi ở trên có thể mang các thuộc tính class khác. Ta có thể gán class="button hero__button", nhưng class hero__button phải nằm cuối cùng.
  • Ta có thể gọi 1 element thông qua Block. Ví dụ button nhưng nằm ở footer của section: .hero__footer-button
  • Trường hợp phổ biến là khi ta vận dụng linh hoạt cả hai trường hợp trên để đáp ứng mục đích sử dụng tốt nhất.

Modifier trong BEM

Quá trình xây dựng layout và các element không thể tránh khỏi việc trùng các class của lớp Element. Lớp Block thì tỷ lệ đụng mặt nhau ít hơn.

Modifier giúp xử lý nó triệt để hơn. Không vội nói tới BEM, ngay như trong Bootstrap bạn cũng có thể thấy button có nhiều kiểu thông qua sử dụng class như .btn-primary , .btn-danger chẳng hạn.

Với Modifier, thay vì một dấu gạch, chúng ta cần hai dấu gạch để phân định extend (style mở rộng).

Tức là .hero__button có thể có kiểu khác như .hero__button--outline, .hero__button--alert

Điểm lưu ý quan trọng trong Modifier là tính kế thừa. Modifier không sử dụng độc lập mà đi sau class gốc.

Như thế, class="hero__button hero__button--outline" mới là hợp lý.

Các tình huống cần nắm rõ trong BEM CSS

Nhiều element cùng mang 1 class

Chẳng hạn, bạn có 3 button nằm lần lượt ở .hero__header, .hero__content.hero__footer.

Có hai trường hợp có khả năng xảy ra: cả 3 button đều giống nhau, hoặc có thể có các kiểu style khác nhau.

Trường hợp 1: Nếu cả 3 button đều giống nhau, kể cả về mặt khoảng cách với các element khác: bạn chỉ cần gán class hero__button là đủ. Không cần override thì không gán class. Đơn giản thôi.

Trường hợp 2: Nếu bạn cần điều chỉnh spacing giữa button với các element khác, bạn cần gán các class riêng. Ta có thể có các button với class:

<a class="hero__button hero__button--header"></a>
<a class="hero__button hero__button--content"></a>
<a class="hero__button hero__button--footer"></a>

Một ví dụ khác mà mình thấy khá thú vị khi xây dựng grid:

<div class="hero__col hero__col--left"></div>
<div class="hero__col hero__col--right"></div>

Trường hợp này phổ biến khi bạn muốn trên mobile tất cả cột đều là 100%, nhưng trên desktop cột bên trái chiếm hơn, ví dụ 75% chiều rộng trong khi cột còn lại chỉ 25%. Đặt tên class như này thì việc override rất dễ dàng như sau:

// hero.css, mobile-first, using postcss-custom-media package
// https://github.com/postcss/postcss-custom-media
.hero__col {
  width: 100%;
}

.hero__col--left {
  @media (--small-viewport) {
    width: 75%;
  }
}

.hero__col--right {
  @media (--small-viewport) {
    width: 25%;
  }
}

Nên override từ module class

Lợi ích của BEM là cấu trúc phải rạch ròi, nhưng bạn không nên quá tham lam để gán class override vô tội vạ vào các element sâu bên trong cấu trúc.

Lấy ví dụ, nhiều người thường khi mới làm quen với BEM muốn tái sử dụng module nào đó thường hay viết với độ phức tạo như thế này. Ta lấy 1 ví dụ trong WordPress với điều kiện is_single() như sau:

<div class="two-up">
  <div class="two-up__container">
    <div class="two-up__block<?php if( is_single() ) : ?> two-up__block--single<?php endif; ?>"></div>
  </div>
</div>

Điều này có thể đơn giản nếu nó xuất hiện chỉ một lần, nhưng sẽ rất phức tạp và rối rắm nếu bạn có hàng loạt element cần override style như thế này:

<h2 class="hero__headline<?php if( is_single() ) : ?> hero__headline--single<?php endif; ?>"></h2>
<div class="hero__description<?php if( is_single() ) : ?> hero__description--single<?php endif; ?>"></div>

Trong trường hợp này, ta nên có quy ước như sau:

Quy ước 1: Chỉ gán class override vào selector cao nhất của module cần tái sử dụng

Lấy ví dụ, ta có .two-up hay .two-up--intro có thể override element bên trong như sau:

.two-up {}
.two-up__headline {
  @extend .h2;
}
.two-up--intro {
  .two-up__headline {
    @extend .label;
  }
}

Điều này nhằm giảm mức độ khó sử dụng của BEM và nesting class chỉ xuất hiện trong tình huống có class module tác động vào.

Quy ước 2: Chỉ gán class override vào element con nếu nó xuất hiện hơn một lần trong cấu trúc

Quy ước này rất dễ hiểu, bạn chỉ cần đọc code dẫn tới 2 quy ước này thì sẽ thấy, việc bạn override khi một element xuất hiện duy nhất dẫn tới hệ quả là code sẽ rối và bị trùng lặp. Trường hợp __col--left và __col--right là cách tốt nhất để override __col nếu nó xuất hiện hơn 1 lần.

Các bạn có thể tham khảo các quy tắc được mình tóm gọn lại với nhiều ví dụ hơn trên GitHub.

Kết luận

BEM CSS là nhóm quy tắc có tác động rõ ràng nhất với mảng sản phẩm thực tế bởi tính ứng dụng của nó cao. Hi vọng sau khi tìm hiểu, các bạn sẽ nắm và ứng dụng nó nhiều hơn.

4 bình luận về “Toàn tập về BEM CSS”

    • Cứ giả sử em có 1 class .item, class .item này nếu được sử dụng trong .list, .blocks, .header chẳng hạn, thì sẽ tới lúc em thấy 1 .item chịu sự chi phối override của nhiều class cha. Điều này dẫn tới việc không biết thằng nào ghi đè lên thằng nào, hoặc sẽ có dạng css như .list.blocks > .item thì mới xử lý được.

      Bình luận

Viết một bình luận


Chuyên gia về Web
Bạn muốn làm việc với dịch vụ website do chúng tôi triển khai?
Gọi tư vấn 0982.90.4343
Chuyên gia về Web
Bài viết liên quan

07/04/2024

PHP: Format date dd/mm/yyyy và so sánh với Date hiện tại
Một vấn đề hay gặp trong quá trình xử lý PHP datetime là tình huống format date từ định dạng...

02/01/2024

Fix lỗi npm không thể cài các package devDependencies
Khi cài đặt dự án,  có lúc bạn sẽ cài mãi cũng không đủ các package npm, đặc biệt là...
Gọi file PHP trong WordPress

19/09/2023

Cài đặt và sử dụng WP-CLI trên môi trường Linux
Trên một số môi trường Hosting có thể cung cấp SSH hoặc Terminal access, song không có sẵn WP-CLI để...
Import database MySQL lớn trên môi trường Docker

06/08/2023

Import database MySQL lớn trên môi trường Docker
Mình sử dụng EasyPanel để quản lý các Docker và build môi trường app. Nay gặp tình huống phải import...