Managing CSS and JavaScript files in Drupal 8 with Libraries
Drupal 8 revolutionizes the theming experience with many significant improvements to make theming easier, and give themers the flexibility and control they've never had before. One of those major improvements is to the library management system, which controls the attaching of CSS and JavaScript files.
In this post we will cover how to create and control libraries from a theme. This will include SMACSS categorization for CSS files, dependencies, how to conditionally attach libraries, manipulating libraries that come from anywhere in a site (core, modules, or base themes,) and targeting individual files for removal or replacement. All this without needing a single line of PHP.
Creating Libraries
The scripts and stylesheets properties used in previous versions of Drupal no longer exist. In Drupal 8 we manage all of these files using libraries defined in a .libraries.yml file, added to your theme.
Each library defined in this file includes a unique name, any number of CS or JS files, dependencies, and other information needed to define the properties of the library or assets.
Here is an example of a library file:
# In mythemename.libraries.yml
# Give your library a name.
my-library-name:
version: "1.0.x"
css:
# The SMACSS category.
base:
# The path to the css file.
assets/css/base.css: {}
theme:
assets/css/print.css: { media: print }
js:
assets/js/myglobal.js: {}
dependencies:
- core/jquery
# In the following example, we add a Google font (Lato).
lato:
css:
base: '//fonts.googleapis.com/css?family=Lato': { external: true }
The file is in YAML format, which Drupal 8 uses for all configuration files. It should be named using the name of your theme, just like your .info.yml file. Let’s take a look at each line to get a better understanding of all the properties.
Naming
# Give your library a name.
my-library-name:
Each library is given a custom name. This name can be anything, but must be unique to the module or theme that supplies it. This should be easy to keep track of since the libraries that belong to the same module or theme are defined in the same file. The name does not have to be unique to the entire website, because libraries are referenced using both the library name and source. For example, mythemename/my-library-name, but we’ll get to that later when we start attaching and manipulating libraries.
Version
version: "1.0.x"
The version is completely optional. It is only used for identification purposes, along with other properties like license, remote, and a few others that we won’t go into. You may want to add a version for a library you use with multiple projects to keep track of which version is used at any given time. It is also helpful when creating a library that uses CSS or JS from an external source. For example, if you copy code for a slideshow, you may want to add the source version here to keep track of it.
CSS
css:
# The SMACSS category.
base:
# The path to the css file.
assets/css/base.css: {}
theme:
assets/css/print.css: { media: print }
All CSS files are included below the css: line. In this case there are two CSS files, base.css and print.css. Each is listed below a category, base and theme, respectively. These are SMACSS categories, which Drupal 8 uses for all CSS files. See the documentation page on CSS file organizaton.
SMACSS
SMACSS is a method for organizing files and CSS rules. You don’t have to follow it in your theming, but you do need to understand how it works and what categories exist, because it is used everywhere in Drupal 8 that you encounter CSS files. If you’ve never used SMACSS before, just know that there are five categories: base, layout, component, state, and theme.
When libraries are processed and stylesheets added to a page, they are added in the order of these categories. So a file in the base category will always come before a file in the theme category. The one caveat to this is all module libraries are grouped together and then all theme libraries, so regardless of category, a theme’s stylesheets will always come after the ones supplied by modules.
Back to our example...
Additional Properties
theme:
assets/css/print.css: { media: print }
The line below the SMACSS category is where we add the CSS file. The path, relative to the root of the theme, is used to identify the file. Following the file are a pair of curly braces where additional properties can be set. In this case, the media property is set to print. The available values for media are screen, print, or all. If no value is set, all is used.
JavaScript
js:
assets/js/myglobal.js: {}
Like CSS, we add JavaScript files below the js: line, with each file on a separate line. Unlike CSS, there is no SMACSS category to worry about. But, just like CSS, you can set additional properties inside the curly braces following the file name. For example, adding minified: true will tell Drupal this file has already been minified, so don’t try to minify it again when aggregating the files.
One thing to note is Drupal 8 adds all JavaScript files to the footer of a page. If you need to have your JavaScript loaded in the header, add header: true to the library.
my-library-name:
header: true
...
js:
assets/js/myglobal.js: {}
...
If the header property is set to true, any dependencies defined in the library will also get loaded in the header.
Dependencies
dependencies:
- core/jquery
The dependencies: line is where you add any libraries your library requires. This must be other libraries already defined by your theme, some module, or Drupal core. Notice that the library is referenced using its name, and where it came from. In this case, core. (core refers to a literal core.libraries.yml file. Libraries defined by core modules will use their names; block, node, field, views, system, etc.) This is how we avoid name conflicts.
External Files
lato:
css:
base:
'//fonts.googleapis.com/css?family=Lato': { external: true }
In the second example, we see how to link to external stylesheets. Simply supply the external URL for retrieving the file, and set the external property to true.
Attaching Libraries
There are three basic ways for a theme to attach libraries; in the .info.yml file, directly in a template file, and in a preprocess function. Let's go over each of those methods.
Global
To attach assets globally so they are added to every page of the website, add them in your theme’s .info.yml file.
libraries:
- core/normalize
- mythemename/my-library-name
Twig
A great way to attach a library conditionally is to do so directly in a template file. Drupal 8 has a special function called attach_library() just for this purpose.
{# In a Twig template file. #}
{{ attach_library('mythemename/my-library-name') }}
The advantage here is in matching the same conditions of the template file. For example, if you use this method in your node.html.twig, the CSS and JS files will only get added when a node is rendered. If you do it in your node--content-type.html.twig the files will only get added when a node of that particular content type is rendered. You can imagine the flexibility when doing this in specific field or views templates.
PHP
Lastly, libraries can be attached in preprocess.
<?php
function mythemename_preprocess_page(&$variables) {
$variables['#attached']['library'][] = 'mythemename/my-library-name';
}
?>
Here you can add whatever logic needed to match the conditions you want before adding the library to the 'library' array.
Manipulating Libraries
Since all CSS and JS files in Drupal 8 are now in libraries, themes have complete control over those files, regardless of whether they are part of the theme or not. The two main ways to manipulate libraries are with libraries-extend and libraries-override.
Libraries-extend
Libraries-extend is a property used in your theme’s info file. It attaches your library to any existing library. The real power here is that the inclusion of your library will now match the library that was extended. If there is any special logic behind when and how that library is attached, your library goes along for the ride without you having to do anything to recreate that logic yourself.
# In mythemename.info.yml
libraries-extend:
# Classy's forums library is only included when the forums.html.twig
# template is used. This will add my theme's 'forums' library at the same
# time.
classy/forums:
- mythemename/forums
In the above example, a forums library is created as part of our example theme, and attached to Classy’s forums library. Any time Classy’s library gets attached, which is only when a forum is rendered, the example theme’s library also gets attached.
Libraries-override
Libraries-override is an even more powerful property that can be used in your theme’s info file. It gives you complete control over any library, to manipulate in anyway you see fit.
Let’s take a look at some examples.
Remove a File
# In mythemename.info.yml
libraries-override:
# The library name.
core/jquery.ui:
# CSS files are always labeled as such. This format is required.
css:
# The SMACSS category is required.
theme:
# The path to the file. It is not a path relative to your theme.
assets/vendor/jquery.ui/themes/base/theme.css: false
You’ll notice the structure is exactly the same as when you define a library in your .libraries.yml file. You specify the library name, SMACSS category, and original path to the CSS file. The only difference being the library name must also include the source. In this case, core is prepended to the library name, because the jquery.ui library is defined by Drupal core.
On the line with the path to the CSS file, note that this path is the same as defined by the library. It is not a path relative to your theme, or the website root. It is exactly the same as defined by the jquery.ui library. The path is used as a key to identify the CSS file, so it has to match. If you don’t know what the path is, just find the .libraries.yml that defined the library, and copy it.
Lastly, in this example we’ve added false after the file. This tells Drupal to remove that CSS file any time the library is used. When we say, “no more PHP”, this is it. Gone are the days of preprocessing or alters, doing string searches, and unsetting array elements.
Replace a File
# In mythemename.info.yml
libraries-override:
system/maintenance:
css:
theme:
# Replace the System module's maintenance CSS file with a custom one.
css/system.maintenance.css: css/maintenance.css
Here we have targeted one particular CSS file added by the System module, in a library called maintenance. Following the system.maintenance.css we supply the path to our own CSS file. This path is relative to the theme’s directory. And since we are supplying a file to an already existing library, this file does not have to be part of any other library defined by the theme.
When doing this yourself you’ll also notice that the new file gets placed in the exact same place the original file was linked in the head of a page. Whether the original file was first or seventh, the new file will be the same. This ensures the cascade of rules in all the stylesheets is not disturbed.
Replace and Remove Whole Libraries
# In mythemename.info.yml
libraries-override:
# Replace Classy's messages library with a custom one.
classy/messages:
mythemename/messages
# Remove Classy's search results library completely.
classy/search-results: false
In this example, we are doing two things. First, replace Classy’s messages library with one from the example theme. This will prevent any of the files used in the Classy library from getting used, and replace them with the files in the example theme’s library. Note that your theme’s library does not have to match the original library. You can have more, or fewer, files, and call them whatever you want. This just substitutes your library for the original one.
Second, the false placed after Classy’s search-results library removes it completely. This is similar to how we removed an individual CSS file in the previous example, but in this case we remove the entire library.
Voila!
As you can see, given the all-in approach Drupal 8 has taken with libraries, and the power of libraries-extend and libraries-override, themers now have total control!