On Github bradwade / bem-in-drupal-presentation
Created by Brad Wade / bwade@phase2technology.com and Brian McMurray / bmcmurray@phase2technology.com
BEM (Block, Element, Modifier) is a methodology for determining a website's nomenclature.
It helps you create ideal, semantic markup and class names that provide designers, frontend, and backend programmers the same terms to communicate with one another.
It helps achieve more organized, reusable, and maintainable code. It provides front end developers with rich, specific information about what they are styling.
BEM was developed by a group of Russian programmers working for Yandex company.
bem.info
Similar to SMACCS or other OOCSS.
Cure for Divitis You are able to clear out many divs that are there "in case you need them". It is easy to complain about how the markup and class names in Drupal are bloated -- but harder to suggested a good, flexible alternative, solution.
Get ready for Drupal 8 It looks like Drupal 8 is adopting a very similar CSS Architecture approach
CSS architecture (for Drupal 8)https://drupal.org/node/1887918
Reusable components! Styling of components is not based on context/location. Therefore moving a component between pages, content types, from the sidebar to footer or repeating it multiple times on a page should not affect the styling.
Maintainable
Readable
Predictable
Team scalability
body.article-node #sidebar .block-3 h3 { padding-left: 15px; }
.newsletter-cta__heading { padding-left: 15px; }
Block names must be globally unique.
Use a single dash for multi-word block names.
Keep it Semantic! We don't need to add styling information into classes. We can maintain seperation of styling and markup. Don't name things based on styling or position/context.
Bad: blue-search-bar, sidebar-search, footer-links.
Naming is hard. Don't get bogged down. Try to name quickly and move on.
/* example block/component names */ .logo {} .pager {} .tab-block {} .main-menu {}
<ul class="pager"> <li>First</li> <li>Previous</li> <li>3</li> <li>Next</li> <li>Last</li> </ul>
Element names only need to be unique within the component.
Use a single dash for multi-word element names.
Add the element name to the end of the block name and use a double underscore to separate the two.
<ul class="pager"> <li class="pager__control">First</li> <li class="pager__control">Previous</li> <li class="pager__current-page>3</li> <li class="pager__control">Next</li> <li class="pager__control">Last</li> </ul>
Use a single dash for multi-word element names.
Add the modifier name to the end of the block or element use a double dash to separate.
Modifiers can have a type and value for example: size-small size-large position-first position-last depth-2 depth-3 state-active priority-normal priority-important priority-critical status-active
<ul class="pager"> <li class="pager__control pager__control--first">First</li> <li class="pager__control pager__control--previous">Previous</li> <li class="pager__current-page>3</li> <li class="pager__control pager__control--next">Next</li> <li class="pager__control pager__control--last">Last</li> </ul>
.pager { @include horizontal-list(); } .pager__current-page { font-weight: bold; } .pager__control { @include hide-text(); display: block; background: url("images/sprite.png") 0 0; width: 20px; height: 20px; margin-right:5px; padding: 0px; } .pager__control--first { background-position: 0 0px; } .pager__control--previous { background-position: 0 -30px; } .pager__control--next { background-position: 0 -60px; } .pager__control--last { background-position: 0 -90px; }
Core CSS uses HTML elements with to great of a specificity. So it would clobber our BEM-y CSS.
/* Example from system.menus.css */ ul.menu li { margin: 0 0 0 0.5em; /* LTR */ } ul li.expanded { list-style-image: url(../../misc/menu-expanded.png); list-style-type: circle; }
Refer to non-existant files in your theme's .info file.
name = Theme name description = My sweet theme core = 7.x ; STYLESHEETS ;------------------------------------------------------------------------------- stylesheets[all][] = css/style.css ; Note: removes system so we don't have to override styles. stylesheets[all][] = system.menus.css stylesheets[all][] = system.theme.css
Drupal *gasp* is an opinionated system and isn't going to make everything easy for you when your ideals run counter to its opinions.
If you override every core theme function, you're in for a bad time.
If you can get your BEM classes in place, you can ignore some of Drupal's divitis. Focus instead on keeping divitis out of your CSS.
Exactly what it sounds like, make custom .tpl.php files in your theme and add your BEM classes as you'd like.
Can lead to lots of template files that are difficult to organize, manage, and not very flexible.
In Node template files, you can add BEM classes around field output.
To add BEM classes into field output, you'll have to add field templates or use other methods (like template preprocessing)
Views gives you lots of options to add custom CSS classes to its output.
Many of those options are buried in advanced options or settings.
It's like node templating, only with a user interface
You can add classes to the <body> tag
You can customize your layouts (but should consider leaving out BEM classes, layouts are meant to be more abstract and general use)
You can add a class to any or every Pane you add as content
template.php is the heart of your theme. Using process_ and preprocess_ functions will help you massage Drupal's output and can compliment the previous approaches.
Implement theme_ function overrides to further massage and alter Drupal's default HTML output. This can be useful for modifying output that doesn't run through the other methods, like menu trees, pagers, etc.
Add BEM classes to your nodes in YOURTHEME_preprocess_node(&$variables).
<?php $variables['classes_array'][] = 'node--' . $variables['type'] . '--' . $variables['view_mode'] . ' node--' . $variables['view_mode'];
The Problem: Drupal outputs menus all over using menu_tree() and theme('menu_tree') but the HTML output isn't BEM-y.
Solution: Implement theme_menu_tree.
Let's see what we have here...
Drupal's built-in theme_menu_tree is incredibly basic.
<?php function theme_menu_tree($variables) { return '<ul class="menu">' . $variables['tree'] . '</ul>'; }
The variable tree just contains rendered HTML of the menu tree.
The Problem Gets Harder: Our ideal menu markup includes the depth and the menu's name as part of the BEM classes, but those aren't in the $variables available to us in theme_menu_tree
Our ideal markup:
First, we need the menu name and depth available to us, but they aren't in $variables. In fact, they were available but were removed in template_preprocess_menu_tree().
<?php function template_preprocess_menu_tree(&$variables) { $variables['tree'] = $variables['tree']['#children']; }
To fix this, we have to alter the theme registry.
We alter the theme registry and prepend a new preprocess function before template_preprocess_menu_tree().
<?php /** * Implements hook_theme_registry_alter(). * * Alters the theme registry to insert our preprocess function before * menu_tree's default preprocess function. */ function YOURTHEME_theme_registry_alter(&$theme_registry) { // Insert a preprocess function before template_preprocess_menu_tree. array_unshift( $theme_registry['menu_tree']['preprocess functions'], 'YOURTHEME_preprocess_bem_menu_tree' ); }
The preprocess function grabs the menu_name and the depth from the original tree and saves them as new $variables for use in the theme implementation.
<?php /** * Custom preprpocess function to allow us to capture some variables in menu_tree * before `template_preprocess_menu_tree` destroys them. */ function YOURTHEME_preprocess_bem_menu_tree(&$variables) { $tree = $variables['tree']; $menu_name = FALSE; $depth = FALSE; foreach ($tree as $menu_item) { if (is_array($menu_item) && array_key_exists('#original_link', $menu_item)) { $menu_name = $menu_item['#original_link']['menu_name']; $depth = $menu_item['#original_link']['depth']; } } if (!empty($menu_name)) { $variables['menu_name'] = $menu_name; } if (!empty($depth)) { $variables['depth'] = $depth; } }
Simple updates using the newly available variables allows the inclusion of the menu_name and depth.
<?php /** * Implements theme_menu_tree(). * * @details Adds a unique class to each UL, using variables we * have captured with * `YOURTHEME_preprpocess_bem_menu_tree()`. */ function YOURTHEME_menu_tree($variables) { $ul = array( '#theme' => 'html_tag', '#tag' => 'ul', '#value' => $variables['tree'], '#attributes' => array( 'class' => array('menu'), ), ); if (!empty($variables['menu_name'])) { $ul['#attributes']['class'][] = 'menu-'. $variables['menu_name']; if (!empty($variables['depth']) && $variables['depth'] > 1) { $ul['#attributes']['class'][] = 'menu-'. $variables['menu_name'] .'--tier'. $variables['depth']; } } return drupal_render($ul); }
Created by Brad Wade / bwade@phase2technology.com and Brian McMurray / bmcmurray@phase2technology.com