In the last lesson, you read accordion.js line by line and saw exactly how it toggles panels open and closed. But here's a question that lesson deliberately left open: how does that file end up loaded on the page at all? A plain <script src="accordion.js"> tag would work on a normal website — but Drupal never does it that way. Every single JS and CSS file in a Drupal module has to be declared first, in a dedicated YAML file, before it can be attached anywhere. That file is *.libraries.yml, and it's what this lesson is about.
Why declare assets instead of just linking them?
Declaring libraries up front — rather than sprinkling <script> tags around your templates — gives Drupal a complete picture of every asset a page might need before it renders anything. That picture is what makes Drupal's asset pipeline possible: it can combine (aggregate) multiple JS files into one request, cache-bust them intelligently when they change, resolve dependency order automatically, and load only the libraries a given page actually attaches — nothing wasted, nothing missing. None of that is possible if assets are just hardcoded into markup.
What you'll learn in this lesson
- How a
*.libraries.ymlfile declares a named library and the JS/CSS files that belong to it - What the
versionkey does and why forgetting to bump it can serve visitors a stale cached file - How CSS files are organized into SMACSS categories, and why that ordering matters
- How to control load order with
weight— and whydependenciesis almost always the better choice - How you actually get a declared library onto a page (declaring it alone does nothing)
The source file
Path: modules/js_example/js_example.libraries.yml
accordion:
# Starting from Drupal 10.1.2, the version information plays a critical role
# in generating a unique hash for aggregated files. It is important to update
# the version value whenever a referenced file undergoes changes.
version: 4.0.0
js:
js/accordion.js: {}
css:
component:
css/accordion.css: {}
colors:
version: 4.0.0
css:
component:
css/weights.css: {}
js:
# We use weight as example attribute. Its usage is discouraged, though;
# dependencies should instead be used.
# For JavaScript files, its value can be only negative, contrarily to CSS
# files for which its value can be between -50 and +50.
js/colors.rgb.js: { weight: -2 }
js/colors.base.js: { weight: -8 }
js/colors.cmy.js: { weight: -4 }
dependencies:
- core/jquery
- core/drupalSettings
Two libraries declared in one file: accordion (which loads the JS you studied last lesson) and colors. Let's go through what each key controls.
How it works, key by key
Library names as top-level keys
Each top-level key — accordion, colors — defines one distinct library with an arbitrary name you choose. Once declared, a library is referenced elsewhere using the format module_name/library_name — so this file's two libraries become js_example/accordion and js_example/colors. That namespacing is what stops two different modules from colliding if they both happen to name a library, say, main.
Think of a library as a bundle: every JS and CSS file that logically belongs to one feature gets grouped together so they load — and can be cached — as a single unit.
version
version: 4.0.0
Since Drupal 10.1.2, this value feeds directly into the cache-busting hash Drupal generates for aggregated asset files (in production, Drupal combines many JS or CSS files into one bundled file per page). If you edit accordion.js but forget to bump version, the aggregation hash doesn't change — which means browsers that already cached the old aggregated file may keep serving it, and your visitors never see the update. Always bump version whenever you change any file a library references.
js — registering JavaScript files
js:
js/accordion.js: {}
The js key maps file paths — relative to the module's root folder — to an options hash. Here, {} (empty braces) means "use all the defaults." Once the js_example/accordion library is attached to a page, Drupal loads this file there and nowhere else.
css — registering CSS files under SMACSS categories
css:
component:
css/accordion.css: {}
Unlike JS files, CSS files must be nested one level deeper — under a SMACSS category key. Drupal recognizes five of them, each carrying its own implicit load-order weight so that, for example, layout rules always load before component styles, which load before one-off theme overrides:
| Category | Weight | Purpose |
|---|---|---|
base | -200 | CSS resets and element defaults |
layout | -100 | Page layout and grid |
component | 0 | Reusable UI components (most common choice) |
state | 100 | JS-driven state changes (e.g. .is-active) |
theme | 200 | Visual/cosmetic overrides |
Both libraries here use component — the right choice for a self-contained UI widget like an accordion or a color picker.
weight — ordering JS files within one library
js:
js/colors.rgb.js: { weight: -2 }
js/colors.base.js: { weight: -8 }
js/colors.cmy.js: { weight: -4 }
The colors library registers three JS files and uses per-file weight values to control output order — lower (more negative) loads earlier. Reading the numbers: colors.base.js (-8) loads first, then colors.cmy.js (-4), then colors.rgb.js (-2).
dependencies (below), because it expresses why one file must come before another, not just a magic number. Reach for weight only when you truly have no other option. Also note the asymmetry: JavaScript weights must be negative, while CSS weights may range from -50 to +50.dependencies — declaring library dependencies
dependencies:
- core/jquery
- core/drupalSettings
This lists other libraries that must load before this one. Drupal resolves the entire dependency tree automatically — including dependencies of dependencies — so you never hand-order <script> tags yourself.
core/jquery makes jQuery available as jQuery (and its familiar $ alias). core/drupalSettings exposes the drupalSettings JavaScript object — the standard channel Drupal uses to hand server-side PHP values to your client-side code. You can depend on any library from any module or theme with the same module_name/library_name format.
Why accordion has no dependencies key at all
Omitting dependencies entirely is perfectly valid — it simply means the library has no external requirements. Here it's a signal that accordion.js (which you read last lesson) is written in plain JavaScript with no jQuery calls. If it needed jQuery, that dependency would have to be declared explicitly; nothing is ever assumed.
Declaring a library is not the same as loading it
This is the detail most newcomers trip over: writing an entry in *.libraries.yml registers a library with Drupal, but it does not put it on any page. You still have to attach it explicitly, using one of:
- In a render array (PHP):
$build['#attached']['library'][] = 'js_example/accordion'; - In a Twig template:
{{ attach_library('js_example/colors') }} - Via
hook_page_attachments(), when you want an asset on every page site-wide
Only once a library is attached somewhere does Drupal's asset pipeline pull it into that page's aggregated output.
See it for yourself
The accordion widget you tested in the last lesson only works because js_example/accordion is attached to that page — declared here, loaded there.
The same working accordion from the last lesson — "Cicero" open, the others closed — is proof this library declaration did its job: the JS file it points to loaded, executed, and attached its click handlers correctly.
Quick check: if you edited
accordion.cssto change the widget's colors but forgot to bumpversion: 4.0.0, what's the most likely symptom a visitor would report? (Answer: they'd say "the site looks the same, my changes aren't showing up" — because their browser is still serving the old cached, aggregated CSS file under the unchanged hash.)
Key takeaways
- Every module's JS and CSS must be declared in a
*.libraries.ymlfile before it can be used — Drupal never auto-discovers loose asset files sitting in a folder. - Each top-level key is a library name, referenced elsewhere as
module_name/library_name(e.g.js_example/accordion). - Always bump
versionwhen you change an asset file; since Drupal 10.1.2 it directly drives the cache-busting hash for aggregated output. - CSS files must sit under a SMACSS category (
base,layout,component,state, ortheme) — this determines their relative load order across the whole page, not just within your library. - Prefer
dependenciesoverweightfor ordering; if you must useweight, remember JS weights are always negative while CSS weights range from -50 to +50. - Declaring a library only registers it — you still must attach it via
#attached['library'], Twig'sattach_library(), orhook_page_attachments()before it appears on any page.
Coming up next
That's the last piece of front-end plumbing this course covers. In the final topic, Testing, we switch from writing features to proving they actually work — starting with PHPUnit unit tests, the fastest and most isolated kind of test Drupal supports.