Extra Block Types (EBT) - New Layout Builder experience❗

Extra Block Types (EBT) - styled, customizable block types: Slideshows, Tabs, Cards, Accordions and many others. Built-in settings for background, DOM Box, javascript plugins. Experience the future of layout building today.

Demo EBT modules Download EBT modules

❗Extra Paragraph Types (EPT) - New Paragraphs experience

Extra Paragraph Types (EPT) - analogical paragraph based set of modules.

Demo EPT modules Download EPT modules

Scroll

Add CSS and JavaScript (JS) in Drupal theme

13/04/2025, by Ivan

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

In Drupal 8, stylesheets (CSS) and JavaScript (JS) are loaded using the same system for both modules (code) and themes: asset libraries.

For clarity, these instructions are intended ONLY for working in themes and do not apply to modules.

Drupal follows a high-level principle: resources (CSS or JS) are only loaded if you tell Drupal to load them. Drupal does not load all resources on every page because that would reduce front-end performance.

Differences from Drupal 7

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

  • The THEME.info.yml file replaces the THEME.info file (with the same data).
  • The stylesheets property (for adding CSS) in THEME.info was removed and replaced by *.libraries.yml, where * is the name of the theme or module.
  • The scripts property (for adding JS) in THEME.info was also removed and replaced by *.libraries.yml, where * is the name of the theme or module.
  • Only CSS/JS needed by the page will be loaded. For example, jQuery is no longer automatically loaded unless explicitly included in *.libraries.yml. If your theme requires jQuery or other resources to be loaded on all pages, add them to *.libraries.yml and then include the library in THEME.info.yml.
  • In Drupal 7, libraries had to be defined using hook_library_info(). This has been replaced by the *.libraries.yml file.
  • In Drupal 8, drupal_add_css(), drupal_add_js(), and drupal_add_library() were removed in favor of #attached.

Process

To load CSS or JS resources:

Defining a Library

Define all your asset libraries in a *.libraries.yml file inside your theme folder. If your theme is called "fluffiness", the file should be named fluffiness.libraries.yml. Each "library" is an entry that details CSS and JS files, for example:

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

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

Note: While this example shows adding a single CSS and JS file + jQuery, many more options are available when defining libraries. See "Library definition: options and details".

Including jQuery in Your Library

Remember, Drupal 8 no longer loads jQuery on all pages by default. For example, if cuddly-slider depends on jQuery, you must declare a dependency on the core library that includes it. Declare dependencies using the format extension/library, such as core/jquery. If another library requires cuddly-slider, it would declare fluffiness/cuddly-slider. You cannot declare an individual file as a dependency, only libraries.

To make jQuery available to cuddly-slider, update the example above:

# 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 referenced in the format extension/library. For core libraries, the extension is core, otherwise it is the module or theme name. For example, if new_library depends on core jQuery, and also on my_library from both a theme and a module, declare:

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

Module and theme names provide a namespace for libraries with the same name.

Attaching a Library to All Pages

Most themes use a global-styling library for stylesheets (CSS files) that need to be loaded on every page where the theme is active. Similarly, JavaScript can be loaded through a global-scripts resource library.

# fluffiness.libraries.yml
# (Multiple libraries can be defined here; these appear below the cuddly-slider library defined 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: {}

To make these libraries available across all pages, add them to your theme's .info.yml file (in this example: fluffiness.info.yml):

# fluffiness.info.yml
name: Fluffiness
type: theme
description: 'A cuddly theme that offers extra fluffiness.'
core: 8.x
libraries:
  - fluffiness/global-styling
  - fluffiness/global-scripts
base theme: classy
regions:
  header: Header
  content: Content
  sidebar_first: 'Sidebar first'
  footer: Footer

Attaching a Library via a Twig Template

You can attach an asset library within a Twig template by using the attach_library() function in any *.html.twig file, for example:

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

Attaching a Library to a Subset of Pages

In some cases, you may not want your library to be active on all pages—only on a subset. For example, the library may only be needed when a specific block or node type is displayed.

Your theme can do this by implementing the THEME_preprocess_HOOK() function in its .theme file, replacing THEME with your theme’s machine name and HOOK with the name of the theme hook.

For example, to attach JavaScript to a maintenance page, use the hook maintenance_page as follows:

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

You can apply similar logic for other theme hooks, such as block or node, and include conditions to detect specific block instances, node types, etc.

Important Note! You must provide cache metadata that corresponds to the conditions used! The example above is unconditional, so no cache metadata is required. A common use case is attaching an 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';
  }
}

Library Definition: Options and Details

Adding Properties to Included CSS/JS

Properties are added in curly braces after each file listed in your theme’s THEMENAME.libraries.yml file.

CSS Properties

The following properties are optional and apply to each CSS asset individually.

Property Description Example
attributes Optional HTML attributes. Common for external assets like Bootstrap CDN.
{ attributes: { crossorigin: anonymous } }
browsers Load the asset conditionally based on browser type. Uses conditional comments (not supported in IE10+).
{ browsers: { IE: 'lte IE 9', '!IE': false } }
group Groups assets. Default is SMACSS group. Rarely used directly. Rarely needed
media Specifies the media type.
{ media: print }
minified Marks the file as already minified. Default is false.
{ type: external, minified: true }
preprocess Whether the asset should be aggregated. Default is true.
{ preprocess: false }
type Specifies the asset source. Default is file.
{ type: external, minified: true }
weight Adjusts the load order relative to other assets in the same group (range: -50 to +50). Default is 0.
{ weight: 1 }

JavaScript Properties

The following properties are optional and apply to each JS asset.

Property Description Example
attributes Additional script tag attributes.
{ type: external, attributes: { async: true } }
browsers Conditionally load based on browser. Uses conditional comments (not supported in IE10+).
{ browsers: { IE: 'lte IE 9', '!IE': false } }
preprocess Whether the script should be aggregated. Default is true.
{ preprocess: false }
type Script source. Default is file.
{ type: external, minified: true }
weight Use dependencies instead when possible. Adjusts the order (negative value preferred).
{ weight: -1 }

Overriding and Extending Libraries

You should use *.info.yml to override libraries defined in *.libraries.yml. These can be either overridden or extended using libraries-override or libraries-extend. Overrides declared in *.info.yml will also be inherited by sub-themes.

The stylesheets-remove property used in *.info.yml is deprecated and will be removed in Drupal 9.0.x. The stylesheets-override property has already been removed.

libraries-override:

Guidelines for creating overrides:

  • Use the original namespace of the module (or core) for the library name.
  • Use the full path of the overridden asset as the key.
  • The path should be absolute.

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 base library namespace, and /core/themes/stable/css/contextual/contextual.module.css is the full path to the overridden asset, which is being removed (false).

Only the final part represents the file path; the rest is namespaced. The css: and component: entries match the library’s internal structure.

Be cautious: filesystem path dependencies mean changes in file structure can break these overrides. There is an open issue to remove this dependency using stream wrappers.

More examples of libraries-override usage:

libraries-override:
  # Replace an entire library.
  core/drupal.collapse: mytheme/collapse

  # Replace a CSS asset.
  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 specific assets from a contributed module’s library.
  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 allows themes to modify a library’s behavior by adding additional assets each time the library is attached.

This is ideal for styling specific components without adding to global CSS—keeping performance high while providing flexibility.

# Extend core's drupal.user library with assets from classy theme.
libraries-extend:
  core/drupal.user:
    - classy/user1
    - classy/user2

Advanced JavaScript Customization

Asset Load Order

As expected, the order in which files are listed determines their load order. By default, all JS assets are loaded in the footer. For critical UI elements that must render immediately, you can load JavaScript in the header like this:

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

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

Set header: true to indicate that the JavaScript in this library is critical and should be loaded in the page header. Any direct or indirect dependencies will also load in the header. This is what “critical path” means—the asset and all its dependencies load early.

Attaching Custom JavaScript

Sometimes you want to add JavaScript that relies on values computed in PHP.

To do this, create the JavaScript file, define and attach the library, and then pass settings using drupalSettings (the successor of Drupal.settings from Drupal 7). You also need to declare a dependency on core/drupalSettings.

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

And in PHP:

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

Now in cuddly-slider.js, you can access settings.fluffiness.cuddlySlider.foo and get 'bar':

(function ($, Drupal, drupalSettings) {
  'use strict';

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

})(jQuery, Drupal, drupalSettings);

Adding Script Tag Attributes

To add attributes to a script tag, define an attributes key in the JSON definition. Use attribute names as keys. If a value is true, the attribute will render without a value.

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 generates:


Inline JavaScript

Inline JavaScript is strongly discouraged. Use a separate JS file to enable browser caching and better maintainability.

Markup-Generating Inline JavaScript

This is rarely needed. Examples include ad scripts, social sharing buttons, or social media widgets. These are more like special content and not meant to enhance UI behavior. Put them in a custom block or directly in a Twig template.

Example:





Page-Wide Inline JavaScript

Any inline JS that affects the entire page is also discouraged. Examples: analytics (like Google Analytics), hosted fonts.

Use html.html.twig for UI-related inline JS such as hosted font loading. Place JS in the <head> to avoid FOUT (Flash of Unstyled Text).

More details in this article: Async Typekit & Micro-FOUT.

For logic-driven inline JS, use a module. See Adding CSS/JS in a Drupal 8 module.

Inline JavaScript in Integration Modules

Inline JavaScript should be avoided wherever possible. Use one of the previously described approaches if you can.

However, when providing a field that accepts user-supplied inline JavaScript (e.g., for ad embeds or tracking scripts), consider two things:

  1. The field, form, or page that accepts this input must be permission-restricted.

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'
  1. If stored in configuration, the value must notify the render system of its CacheableMetadata to properly invalidate rendering when the value changes.

Example: MODULE.module

 Markup::create($settings->get('js_code')),
    '#cache' => [
      'contexts' => ['user'],
      'tags' => ['user:' . $user->id()],
    ],
  ];
  // Add config settings cacheability metadata.
  /** @var \Drupal\Core\Render\RendererInterface $renderer */
  $renderer = \Drupal::service('renderer');
  $renderer->addCacheableDependency($page_bottom['MODULE'], $settings);
}

CDN / External Libraries

Sometimes you want to include JavaScript hosted externally, e.g., via a CDN—common with fonts, analytics, or framework libraries. You can do this by declaring the library as external using type: external. It's also good practice to include metadata.

Note: Avoid relying on CDN-hosted assets unless absolutely necessary. This can introduce stability and security issues, performance delays, and dependency on external networks. Also, third-party libraries should not be stored in your project repository on Drupal.org—see Drupal's third-party library 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

To make the external file protocol-relative (to match the page's HTTP or HTTPS), use:

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

To include external CSS, for example 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: Bootstrap CDN CSS with Custom Attributes

bootstrap-cdn:
  remote: getbootstrap.com
  version: 4.0
  license:
    name: MIT
    url: https://github.com/twbs/bootstrap/blob/master/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

Drupal’s online documentation is © 2000-2020 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution-ShareAlike 2.0. PHP code is distributed under the GNU General Public License.