Advertising sustains the DA. Ads are hidden for members. Join today

Adding assets (CSS, JS) to a Drupal theme via *.libraries.yml

Last updated on
21 December 2023

This documentation needs review. See "Help improve this page" in the sidebar.

This documentation is for themes. For modules, see Adding stylesheets (CSS) and JavaScript (JS) to a Drupal module.

In Drupal, CSS stylesheets (CSS) and JavaScript (JS) are loaded through the same system for modules and themes as asset libraries.

Drupal uses a high-level principle: assets (CSS or JS) are still only loaded if you tell Drupal it should load them. Drupal does not load all assets on every page because it slows down front-end performance.

Differences from Drupal 7

There are six important differences compared to Drupal 7 for themers:

  1. The THEME.info.yml  file has replaced the  THEME.info file (with the same data).
  2. The stylesheets property (for adding CSS) in THEME.info has been removed and replaced by *.libraries.yml where * is the name of the theme or module.
  3. The scripts property (for adding JS) in THEME.info has been removed and also replaced by *.libraries.yml where * is the name of the theme or module.
  4. Only CSS, and JS that is required on a page will be loaded. JQuery, for example, is no longer automatically loaded unless explicitly specified in *.libraries.yml. If your theme requires jQuery or other assets you want to load on all pages, add them in *.libraries.yml. and then include the library in the THEME.info.yml.
  5. In Drupal 7 libraries had to be defined using hook_library_info(). That has been replaced with *.libraries.yml file.
  6. In Drupal 8 drupal_add_css(), drupal_add_js() and drupal_add_library() were removed in favor of #attached.

The process

To load CSS or JS assets:

  1. Save the CSS or JS to a file using the proper naming conventions and file structure.
  2. Define a "library", which registers these CSS/JS files with your theme.
  3. "Attach" the library to all pages, to specific Twig templates, or target specific pages via a render element in a preprocess function.

Defining a library

Define all of your asset libraries in a .libraries.yml file in your theme folder. If your theme is named fluffiness, the filename should be fluffiness.libraries.yml. Each "library" in the file is an entry detailing CSS and JS files (assets), like this:

# fluffiness.libraries.yml
cuddly-slider:
  version: 1.x
  css:
    theme:
      css/cuddly-slider.css: {}
  js:
    js/cuddly-slider.js: {}

In this example, the cuddly-slider.js and cuddly-slider.css are located in the js and css directories of your theme.

Note that while this example demonstrates adding a single css and js file + jquery. There are significantly more options available when defining libraries. These can be found in the Defining Libraries: Options & Details section.

Including jQuery in your Library

Remember, Drupal no longer loads jQuery on all pages by default, so for example if cuddly-slider needs jQuery you must declare a dependency on the core library that contains jQuery (Drupal core provides jQuery, not a module or theme). Declare the dependency with an extension name followed by a slash, followed by the library name, in this case core/jquery. If another library required cuddly-slider it would declare fluffiness/cuddly-slider, the theme name, followed by the library name. You cannot declare an individual file as a dependency, only a library.

So, to make jQuery available for cuddly-slider, we update the above to:

# fluffiness.libraries.yml

cuddly-slider:
  version: 1.x

  css:
    theme:
      css/cuddly-slider.css: {}

  js:
    js/cuddly-slider.js: {}
  dependencies:
    - core/jquery

Declaring dependencies

To declare a dependency, the required library is declared in the form resource/library. For core libraries, the resource is core, while for others it is the module name or the theme name. So if new_library is dependent on jQuery from core, my_library declared in my_theme, and my_library declared in my_module, you would declare the dependencies as:

# fluffiness.libraries.yml
new_library:
  js:
    js/new_library.js: {}
  dependencies:
    - core/jquery
    - my_module/my_library
    - my_theme/my_library

The module and theme names provide the namespace for libraries of the same name.

Attaching a library to all pages

Most themes will use a global-styling asset library, for the CSS styles that need to be loaded on every page where the theme is active. It is also possible to do with JavaScript via a global-scripts asset library.

# fluffiness.libraries.yml (multiple libraries can be added to a libraries.yml file, these would appear below the cuddly-slider libraries added earlier)
global-styling:
  version: 1.x
  css:
    theme:
      css/layout.css: {}
      css/style.css: {}
      css/colors.css: {}
global-scripts:
  version: 1.x
  js: 
    js/navmenu.js: {}   

Attaching libraries from your theme's info.yml

To be available everywhere in the theme, the global-styling/global-scripts libraries must then be added to your theme's .info.yml file. (In this case, that file is fluffiness.info.yml). The core_version_requirement key indicates that this theme is compatible with all versions of Drupal 9 and 10. If the Drupal Core versions before 8.7.7, you should replace core_version_requirement: ^9 || ^10 to core: 8.x.

#fluffiness.info.yml
name: Fluffiness
type: theme
description: 'A cuddly theme that offers extra fluffiness.'
core_version_requirement: ^9 || ^10 
# by adding global-styling and global-scripts here, the css/js files in the library become 
# available to every page presented by the theme
libraries:
  - fluffiness/global-styling
  - fluffiness/global-scripts
base theme: stable9
regions:
  header: Header
  content: Content
  sidebar_first: 'Sidebar first'
  footer: Footer

Attaching libraries globally from your .module file

If you prefer attaching a library from within your module you can use hook_preprocess_HOOK() or hook_page_attachments(). Find some more examples for hook_preprocess_HOOK() below.

In your theme's .module file add 

function fluffiness_page_attachments(&$variables) {
  $variables['#attached']['library'][] = 'fluffiness/global-styling';
}

or

function fluffiness_preprocess_page(&$variables) {
  $variables['#attached']['library'][] = 'fluffiness/global-styling';
}

If you want to conditionally change the libraries, see hook_page_attachments_alter() which is called after hook_page_attachments(). An example can be found further down below.

Attaching a library via a Twig template

You can attach an asset library to a Twig template using the attach_library() function in any *.html.twig, file like so:

{{ attach_library('fluffiness/cuddly-slider') }}
<div>Some fluffy markup {{ message }}</div>

Attaching a library to a subset of pages

In some cases, you do not need your library to be active for all pages, but just a subset of pages. For example, you might need your library to be active only when a certain block is being shown, or when a certain node type is being displayed.

A theme can make this happen by implementing a THEME_preprocess_HOOK() function in the .theme file, replacing "THEME" with the machine name of your theme and "HOOK" by the machine name of the theme hook.

For instance, if you want to attach JavaScript to the maintenance page, the "HOOK" part is "maintenance_page", and your function would look like this:

function fluffiness_preprocess_maintenance_page(&$variables) {
  $variables['#attached']['library'][] = 'fluffiness/cuddly-slider';
}

You can do something similar for other theme hooks, and of course your function can have logic in it — for instance to detect which block is being preprocessed in the "block" hook, which node type for the "node" hook, etc.

Important note! In this case, you need to specify the cacheability metadata that corresponds to your condition! The example above works unconditionally, so no cacheability metadata is necessary. The most common use case is likely where you attach some asset library based on the current route:

function fluffiness_preprocess_page(&$variables) {
  $variables['page']['#cache']['contexts'][] = 'route';
  $route = "entity.node.preview";
  if (\Drupal::routeMatch()->getRouteName() === $route) {
    $variables['#attached']['library'][] = 'fluffiness/node-preview';
  }
}

Defining Libraries: Options & Details

Adding properties to included css/js

Properties are added in the curly brackets following each file added in your theme's THEMENAME.libraries.yml file

CSS properties

The following properties are optional and applied per CSS asset.

attributes Optional attributes. Known use case Bootstrap CDN.
{ attributes: { crossorigin: anonymous } }

browsers
(deprecated in Drupal 9.1.0)

Load asset conditionally based on browser.
Note that this method uses conditional comments, which are only supported in IE up to IE9.

{ browsers: { IE: 'lte IE 9', '!IE': false } }
group

Assets are aggregated per group.
Default: The SMACSS group in which the asset is placed.

Is rarely used
media

Media type.

{ media: print }
minified

Whether the asset is already minified.
Default: false

{ type: external, minified: true }
preprocess Whether the assets should be aggregated.
Default: true
{ preprocess: false }
type The source of the asset.
Default: file
{ type: external, minified: true }
weight

Adjusts order relative to other assets (within the same SMACSS group).
Default: 0. Use a numeric between -50 to +50.

{ weight: 1 }

JS properties

The following properties are optional and applied per JS asset.

attributes

Additional script attributes.

See Mozilla's "<script>: The Script element" page for the list of available attributes and values.

{ type: external, 
  attributes: { 
    async: true,
    type: module
  } 
}
browsers
(deprecated in Drupal 9.1.0)
Load asset conditionally based on the browser.
Note that this method uses conditional comments, which are only supported in IE up to IE9.
{ browsers: { IE: 'lte IE 9', '!IE': false } }
preprocess Whether the assets should be aggregated.
Default: true
{ preprocess: false }
type The source of the asset.
Default: file
{ type: external, minified: true }
weight Discouraged, use dependencies instead.
Adjusts order relative to other assets. Must be negative.
{ weight: -1 }

Overriding and extending libraries

You must go to *.info.yml to override libraries defined in *.libraries.yml They can be either overridden or extended using libraries-override or libraries-extend. Overrides you add to *.info.yml will be inherited by sub-themes.

The root key to use in the override (as seen in the examples below is in the format module-or-theme-name/key-within-the-libraries.yml-file. So for the example contextual/drupal.contextual-links, the module or theme is contextual and the key found within contextual.libraries.yml is drupal.contextual-links.

The stylesheets-remove property used in the *.info.yml file has been deprecated and is planned to be removed in Drupal 10.0.x. The stylesheets-override property has already been removed.

libraries-override

When overriding a library, there are two situations that may occur:

  1. If the active theme is extending another theme, and that parent theme is overriding the library already, see 'Overriding a library or part of library already overridden by a parent theme'
  2. If the active theme does not have any overriding for the library already, see 'General overriding a library or part of library'

Overriding a library or part of library already overridden by a parent theme

The logic you will need to use when creating overrides is:

  1. Use the original module (or core) namespace for the library name.
  2. Use the path of the most recent override as the key.
  3. That path should be the full path to the file.

For example:

libraries-override:
  contextual/drupal.contextual-links:
    css:
      component:
        /core/themes/stable/css/contextual/contextual.module.css: false
    

Here contextual/drupal.contextual-links is the namespace of the core library and /core/themes/stable/css/contextual/contextual.module.css: is the full path to the most recent override of that library. In this case, the file has been overridden withfalse.

It's important to note here that only the last part is an actual file system path, the others refer to namespaces. The css: and component:lines reflect the structure of the library that is being overridden.

When using this remember that reliance on the file system path means that if the file structure of your site changes, it may break this path. For that reason, there is an issue to remove reliance on the full path by using stream wrappers.

General overriding a library or part of library

Here are a few other ways to use libraries-override to remove or replace CSS or Javascript assets or entire libraries your theme has inherited from modules or themes.

libraries-override:
  # Replace an entire library.
  core/drupal.collapse: mytheme/collapse
  
  # Replace an asset with another.
  subtheme/library:
    css:
      theme:
        css/layout.css: css/my-layout.css

  # Replace an override asset from stable.
  contextual/drupal.contextual-toolbar:
    css:
      component:
        core/themes/stable/css/contextual/contextual.toolbar.css: css/contextual.toolbar.css

  # Replace a core module JavaScript asset.
  toolbar/toolbar:
    js:
      js/views/BodyVisualView.js: js/views/BodyVisualView.js

  # Remove an asset.
  drupal/dialog:
    css:
      theme:
        dialog.theme.css: false
  
  # Remove an entire library.
  core/modernizr: false
  
  # Replace very specific assets from a contributed module's library.
  # Note: The module's libraries available for overriding can be found in the module's *.libraries.yml file. In this example, you would find the libraries.yml file at the following location: /modules/contrib/webform/webform.libraries.yml 

  webform/webform.element.location.places:
    css:
      component:
        css/webform.element.location.places.css: css/my-themes-replacement-file.css
    js:
      js/webform.element.location.places.js: js/my-themes-replacement-file.js



libraries-extend

libraries-extend provides a way for themes to alter the assets of a library by adding in additional theme-dependent library assets whenever a library is attached.
libraries-extend are specified by extending a library with any number of other libraries.

This is perfect for styling certain components differently in your theme, while at the same time not doing that in the global CSS. I.e. to customize the look of a component without having to load the CSS to do so on every page.

# Extend drupal.user: add assets from custom_theme libraries.
libraries-extend:
  core/drupal.user: 
    - custom_theme/user1
    - custom_theme/user2

Attaching specific css or js files from an existing library to a twig template

You can attach a specific css stylesheet or a js file to your twig template.

In this example, we will attach the pager.css from the Claro theme to a twig template in our own custom theme called mytheme.

# mytheme.libraries.yml
pager:
  css:
    theme:
      /core/themes/claro/css/components/pager.css: {}

# mytheme.info.yml
libraries:
  - mytheme/pager

We've declared a new library called mytheme/pager by adding the above to our theme's libraries.yml and info.yml.

We then attach the new library to our twig template by using the attach_library function

# custom.html.twig
{{ attach_library('mytheme/pager') }}

Additional Javascript Configuration

Asset loading order

As you would expect, the order in which the files are listed is the order in which they will load. By default, all JS assets are now loaded in the footer. JS for critical UI elements that cannot be shown unless their corresponding JS has run can be loaded in the header if needed like so:

js-header:
  header: true
  js:
    header.js: {}

js-footer:
  js:
    footer.js: {}

Set the header property to true, to indicate that the JavaScript assets in that asset library are in the 'critical path' and should be loaded from the header. Any direct or indirect dependencies of libraries declared in this way will also automatically load from the header, you do not need to declare them individually for them to be available. This is the meaning of the phrase 'critical path', once an asset is declared to be in the header it is 'critical' for that asset and all of its dependencies to load first.

Attaching configurable JavaScript:

In some cases, you may want to add JavaScript to a page that depends on some computed PHP information.

In this case, create a JavaScript file, define and attach a library just like before, but also attach JavaScript settings and have that JavaScript file read those settings, via drupalSettings (the successor to Drupal 7's Drupal.settings). However, to make drupalSettings available to our JavaScript file, we have to do the same work as we had to do to make jQuery available: we have to declare a dependency on it.

So that then becomes:

cuddly-slider:
  version: 1.x
  js:
    js/cuddly-slider.js: {}
  dependencies:
    - core/jquery
    - core/drupalSettings

and

function fluffiness_page_attachments_alter(&$page) {
  $page['#attached']['library'][] = 'fluffiness/cuddly-slider';
  $page['#attached']['drupalSettings']['fluffiness']['cuddlySlider']['foo'] = 'bar';
}

Where 'bar' is some calculated value. (Note that cacheability metadata is necessary here also!)

Then cuddly-slider.js will be able to access settings.fluffiness.cuddlySlider.foo (and it will === 'bar'):

(function ($, Drupal, drupalSettings) {

  'use strict';

  Drupal.behaviors.mybehavior = {
    attach: function (context, settings) {
      
      console.log(settings.fluffiness.cuddlySlider.foo);
      
    }
  };

})(jQuery, Drupal, drupalSettings);

Adding attributes to script elements

If you want to add attributes to a script tag, you need to add an attributes key to the JSON following the script URL. Within the object following the attributes key, add the attribute name that you want to appear on the script as a new key. The value for this key will be the attribute value. If that value is set to true, the attribute will appear on its own without a value on the element.

For example:

https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap:
  type: external
  attributes:
    defer: true
    async: true
    data-test: map-link

This would result in the following markup:

<script src="https://maps.googleapis.com/maps/api/js?key=myownapikey&signed_in=true&libraries=drawing&callback=initMap" async defer data-test="map-link"></script>

Inline JavaScript

Inline JavaScript is highly discouraged. It's recommended to put the JS you want to use inline in a file instead, because that allows JavaScript to be cached on the client side. It also allows JavaScript code to be reviewed and listed.

Inline JavaScript that generates markup

This is discouraged and generally not needed. Put the javascript in a file. Examples of this are ads, social media sharing buttons, and social media listing widgets. These do use inline JavaScript. But they are just a special kind of content/markup since they're not about decorating the site's content or making it interactive, instead, they are about pulling in external content through JavaScript.

You want to put these in either a custom block or even directly in a Twig template.

E.g.:

<script type="text/javascript"><!--
ad_client_id = "some identifier"
ad_width = 160;
ad_height = 90;
//--></script>
<script type="text/javascript" src="http://adserver.com/ad.js"></script>
<a class="twitter-timeline" href="https://twitter.com/wimleers" data-widget-id="307116909013368833">Tweets by @wimleers</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+"://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>

Inline JavaScript that affects the entire page

Using any inline JavaScript is highly discouraged. Examples of inline JavaScript that affects the entire page are analytics (e.g. Google Analytics) and hosted font services. Inline JavaScript that affects the entire page can be in either of two categories: front-end/styling, or logical.

In the case of front-end/styling (e.g. hosted font services), the JS belongs in the theme. Put the JS directly in your html.html.twig file. In the case of fonts, this will also allow you to put it right in the place that gives you the best (and fastest) end user experience, because it allows you to prevent a FOUT (Flash Of Unstyled Text) while the font is still loading (fonts loaded through JS must be listed in the HTML <HEAD> before the CSS)!

In the other case, it belongs in the module, and for that, please see "Adding stylesheets (CSS) and JavaScript (JS) to a Drupal module".

Inline JavaScript that is in an integration module

Using any inline JavaScript is highly discouraged. If you can use one of the examples above, please consider those before attempting to do this.

Two things to consider when providing a field that accepts inline JavaScript provided by a site user:

  1. The field, form, or page that accepts this inline JavaScript must have permission attached.
    Example: MODULE.routing.yml
    MODULE.settings:
      path: /admin/config/services/MODULE
      defaults:
        _title: 'MODULE settings'
        _form: \Drupal\MODULE\Form\MODULESettings
      requirements:
        _permission: 'administer site configuration'
    
  2. The value if stored in a config object should alert the render system about its CacheableMetadata, so that when it changes, the element's render cache is properly cleared/expired.
    Example: MODULES.module
    <?php
    
    /**
     * @file
     * Integrates MODULE in a Drupal site.
     */
    
    use Drupal\Core\Render\Markup;
    
    /**
     * Implements hook_page_bottom().
     */
    function MODULE_page_bottom(array &$page_bottom) {
      $settings = \Drupal::config('MODULE.settings');
      $user = \Drupal::currentUser();
      $page_bottom['MODULE'] = [
        '#markup' => Markup::create($settings->get('js_code')),
        '#cache' => [
          'contexts' => ['user'],
          'tags' => ['user:' . $user->id()],
        ],
      ];
      // Add config settings cacheability metadata.
      /** @var Drupal\Core\Render\Renderer $renderer */
      $renderer = \Drupal::service('renderer');
      $renderer->addCacheableDependency($page_bottom['MODULE'], $settings);
    }
    

CDN / externally hosted libraries

You might want to use JavaScript that is externally on a CDN (Content Delivery Network) — e.g. web fonts are usually only available using an external URL. This can be done by declaring the library to be external (by specifying type: external). It is also a good idea to include some information about the external library in the definition.

(Note that it is in general not a good idea to load libraries from a CDN; avoid this if possible. It introduces more points of failure both performance- and security-wise, requires more TCP/IP connections to be set up, and usually is not in the browser cache anyway. However, 3rd party libraries should not be hosted on Drupal.org as part of your repo – see Policy on 3rd party libraries on Drupal.org for clarification of policy.)

angular.angularjs:
  remote: https://github.com/angular
  version: 1.4.4
  license:
    name: MIT
    url: https://github.com/angular/angular.js/blob/master/LICENSE
    gpl-compatible: true
  js:
    https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }

If you want your external file to be requested with the same protocol as the page is requested with, specify a protocol-relative URL:

  js:
    //ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true }

Or if you want to add CSS, here is an example of integrating Font Awesome:

font-awesome:
  remote: https://fortawesome.github.io/Font-Awesome/
  version: 4.5.0
  license:
    name: MIT
    url: https://fortawesome.github.io/Font-Awesome/license/
    gpl-compatible: true
  css:
    theme:
      https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css: { type: external, minified: true }

Example for Bootstrap CDN CSS with custom attributes.

bootstrap-cdn:
  remote: getbootstrap.com
  version: 4.0
  license:
    name: MIT
    url: https://github.com/twbs/bootstrap/blob/main/LICENSE
  css:
    theme:
      'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css':
        type: external
        minified: true
        attributes:
          crossorigin: anonymous
          integrity: "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"

More information

Help improve this page

Page status: Needs review

You can: