Adding Bootstrap button stylings to Drupal 8's action links and node links

Submitted by on

When all goes according to plan—which is surprisingly often—theming in Drupal 8 is a straightforward matter of editing templates and stylesheets. We found things did not go according to plan when styling page-level action links (such as "Add new Forum topic") and content-level action links (or node links as Drupal 8 still calls them, such as "read more" or "add comment"). We are going to show how to add Bootstrap-specific styles, but the same approaches would be useful to add special stylings, button-like or otherwise, to selected action links and node links.

First, let's revisit the steps to follow when trying to add classes to Drupal-produced markup:

  1. Enable Twig debugging.
  2. Using your browser's inspector, identify the template providing the code you need to modify.
  3. Copy the template from where Twig debug output says it lives in Drupal core (or contrib) to your theme. If needed, use template name suggestions with a --modifier to selectively override the template in specific cases.
  4. Add BEM-compliant classes to your template. This is usually accomplished by tacking the addClass("here are--my-classes") method onto an attributes variable.
  5. Use the classes you added in your CSS.

Now let's try applying that to styling action links and node links (but only the actual link parts, not the surrounding text) as buttons.

Page-level action links

Here is an action link provided by Drupal core in the Forum module. It's classes don't align with Bootstrap's, so it displays without style.

A "Forums" headline with an unstyled "Add new Forum topic" action link below it.

The process

If we open the template that is providing the HTML for each link, menu-local-action.html.twig, it is only one line of code (and 12 lines of comments). It couldn't be simpler!

{#
/**
 * @file
 * Default theme implementation for a single local action link.
 *
 * Available variables:
 * - attributes: HTML attributes for the wrapper element.
 * - link: A rendered link element.
 *
 * @see template_preprocess_menu_local_action()
 *
 * @ingroup themeable
 */
#}
<li{{ attributes }}>{{ link }}</li>

Except... the attributes variable we have available is on the list item (li tag), not the link itself. Using this template we can't add classes to the already-rendered link element. Putting the button class on the list item would result in a common UX problem: button-looking elements with parts that are not clickable.

Even though this template cannot be used directly, it points us in the right direction. On line 10, a comments suggest us to see template_preprocess_menu_local_action(). So we shall.

A symbol finder in an IDE or grep will quickly take us to line 65 of core/includes/menu.inc:

/**
 * Prepares variables for single local action link templates.
 *
 * Default template: menu-local-action.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: A render element containing:
 *     - #link: A menu link array with 'title', 'url', and (optionally)
 *       'localized_options' keys.
 */
function template_preprocess_menu_local_action(&$variables) {
  $link = $variables['element']['#link'];
  $link += array(
    'localized_options' => array(),
  );
  $link['localized_options']['attributes']['class'][] = 'button';
  $link['localized_options']['attributes']['class'][] = 'button-action';
  $link['localized_options']['set_active_class'] = TRUE;

  $variables['link'] = array(
    '#type' => 'link',
    '#title' => $link['title'],
    '#options' => $link['localized_options'],
    '#url' => $link['url'],
  );
}

Here we can see exactly how Drupal is adding classes ('button' and 'button-action') to the buttons. Let's add our own preprocess function assuming our theme is name exampletheme:

  1. Add a function to our .theme file. In this example, the file would be exampletheme.theme.
  2. Name the function exampletheme_preprocess_menu_local_action(). That is, replace the word 'template' with the name of our theme name.
  3. Modify the $variables array to add our classes.

We could even remove the existing classes from the link, but we'll leave them for now. Note that the link that gets processed is $variables['link'] rather than $variables['element']['#link'].

The solution

/**
 * Extends template_preprocess_menu_local_action().
 *
 * Add Bootstrap button classes to a single local action link.
 *
 * Default template: menu-local-action.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - element: A render element containing:
 *     - #link: A menu link array with 'title', 'url', and (optionally)
 *       'localized_options' keys.
 */
function exampletheme_preprocess_menu_local_action(&$variables) {
  $variables['link']['#options']['attributes']['class'][] = 'btn';
  $variables['link']['#options']['attributes']['class'][] = 'btn-success';
}

A forums listing page with a large green button labeled "Add new Forum topic" at the top.

Content-level action links

Next let's style node links as buttons. It's well-nigh impossible to get the btn and btn-success classes on the login and register links within the sentence "Log in or register to post comments". Therefore, we will use Bootstrap's handy mixins. The following is a SCSS code snippet which is turned into CSS by a SASS preprocessor.

.links--node a {
  @include button-variant($btn-success-color, $btn-success-bg, $btn-success-border);
  @include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $btn-border-radius-base);
}

Finally, we just need to add the links--node class. Assuming our theme is called exampletheme:

/**
 * Implements hook_preprocess_links() for node entities.
 */
function exampletheme_preprocess_links__node(&$variables) {
  $variables['attributes']['class'][] = 'list-unstyled';
  $variables['attributes']['class'][] = 'links--node';
}

A comments section noting there are no comments yet, and providing two links, styled as buttons, in the sentence 'Log in or register to post comments'

Bonus: Link field links as buttons

For styling the output of link fields as buttons, the aptly-named Button link formatter module can help you out without the need for custom code nor templating.

Are you styling action links, node links, and other links?

Have you faced similar needs for changing the look of links, to be buttons or otherwise? How have you met them? Let us know in the comments!

Add new comment