Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Libraries: styles and scripts

CSS and JavaScript files in Drupal are not added with tags in a template. They are grouped into a library — a named set of files — which is then attached to whatever is being output. Drupal adds the tags to the page itself, and aggregates and compresses the files.

Style files

Create a css folder in the module and a file in it:

/* web/modules/custom/blog/css/card.css */
.blog-card {
  padding: 16px;
  border: 1px solid #d0d7de;
  border-radius: 8px;
}

On its own the file is not attached anywhere yet.

Describing a library

Module libraries are described in the file blog.libraries.yml in the root of the module folder. Create it by hand:

# web/modules/custom/blog/blog.libraries.yml
card:
  css:
    theme:
      css/card.css: {}

card is the library name. The full name is made of the module name and the library name: blog/card. File paths are written relative to the module folder. The theme key is the style level: it sets the loading order; component and theme suit component styling.

Attaching in a render array

// web/modules/custom/blog/src/Controller/BlogController.php
public function list(): array
{
    return [
        '#theme' => 'blog_card',
        '#title' => 'First article',
        '#text' => 'A short description of the article.',
        '#attached' => ['library' => ['blog/card']],
    ];
}

After drush cr the card on the /blog page gets a border and padding. The file loads only on the pages where this element is output.

Attaching in a template

The same can be done right in the Twig template:

{# web/modules/custom/blog/templates/blog-card.html.twig #}
{{ attach_library('blog/card') }}
<article class="blog-card">
  <h2>{{ title }}</h2>
  <p>{{ text }}</p>
</article>

This is more convenient: the styles are tied to the template and load wherever it is used.

Scripts

Add a JavaScript file and dependencies to the library:

# web/modules/custom/blog/blog.libraries.yml
card:
  css:
    theme:
      css/card.css: {}
  js:
    js/card.js: {}
  dependencies:
    - core/drupal
    - core/once

Dependencies are other libraries that must load first. core/drupal provides the Drupal object, core/once the once function.

// web/modules/custom/blog/js/card.js
(function (Drupal, once) {
  Drupal.behaviors.blogCard = {
    attach(context) {
      once('blog-card', '.blog-card', context).forEach((card) => {
        card.addEventListener('click', () => {
          card.classList.toggle('is-open');
        });
      });
    },
  };
})(Drupal, once);

The code is written as a behavior. Drupal calls attach when the page loads and every time new markup is added to it. The once function makes sure the handler is bound to each card only once. A click on a card now toggles the is-open class.

On every page

A library needed everywhere is attached by a hook in blog.module:

// web/modules/custom/blog/blog.module
 
/**
 * Implements hook_page_attachments().
 */
function blog_page_attachments(array &$attachments): void
{
    $attachments['#attached']['library'][] = 'blog/card';
}

Common mistake

You changed the CSS and the page looks the same. There are usually two causes. The first is file aggregation: switch it off during development at Configuration → Development → Performance. The second is the cache: blog.libraries.yml is read only on drush cr. And do not add files with link and script tags in a template: Drupal does not know about them and can neither aggregate them nor load their dependencies.

Thanks for reading!

Donate to the site — we work hard for you.