Add CSS and JavaScript (JS) in Drupal theme
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()
, anddrupal_add_library()
were removed in favor of#attached
.
Process
To load CSS or JS resources:
- Save the CSS or JS in a file, using the appropriate naming conventions and file structure.
- Define a "library" that registers these CSS/JS files within your theme.
- Attach the library to all pages, specific Twig templates, or target specific pages using render elements in preprocess functions.
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') }}
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:
Tweets by @wimleers
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:
- 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'
- 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.