Build your first theme 3: libraries, CSS, and breakpoints
In part 3 of the theme tutorial the theme gets its own CSS. You define an asset library, understand how its categories control load order, and declare the breakpoints that part 6 turns into responsive images.
What you’ll build
Section titled “What you’ll build”Three files that give First theme its visual foundation:
css/base.css, the global stylesheet,first_theme.libraries.yml, the asset library that delivers it (and, in part 5, JavaScript),first_theme.breakpoints.yml, machine-readable descriptions of your design’s media queries.
The first_theme/global library that first_theme.info.yml has referenced since part 1 finally exists at the end of this part.
Prerequisites
Section titled “Prerequisites”- First theme as part 2 left it: installed, default, with your
node.html.twigandnode--teaser.html.twigtemplates.
Step 1: Write the stylesheet
Section titled “Step 1: Write the stylesheet”Create css/base.css in the theme (replacing your generated starterkit css/ directory contents is fine; nothing attaches them anymore since you trimmed libraries: in part 1):
/** * @file * Base styles for the First theme, loaded globally via first_theme/global. */:root { --first-theme-max-width: 70rem; --first-theme-text: #1f2933; --first-theme-accent: #0678be;}
body { margin: 0; color: var(--first-theme-text); font-family: system-ui, sans-serif; line-height: 1.6;}
a { color: var(--first-theme-accent);}
.layout-container { max-width: var(--first-theme-max-width); margin-inline: auto; padding-inline: 1rem;}
/* Tablet and up: see first_theme.breakpoints.yml for the shared values. */@media (min-width: 48rem) { .layout-container { padding-inline: 2rem; }}Plain CSS, nothing Drupal-specific: custom properties on :root, element defaults, and a .layout-container width constraint that matches a class the page template already prints. The media query value (48rem) reappears in step 4; keeping the two in sync is your job, because CSS cannot read YAML.
Also create an empty js/first-theme.js now (for example with touch web/themes/custom/first_theme/js/first-theme.js). The library you define next references it, and part 5 fills it in.
Step 2: Define the library
Section titled “Step 2: Define the library”Drupal never loads loose CSS or JS files; it loads asset libraries, named bundles of files plus metadata, so it can deduplicate them across modules and themes, order them, aggregate them, and attach them only where needed. Replace the contents of first_theme.libraries.yml with:
# An asset library bundles CSS and JS so Drupal can attach, aggregate and# cache them. "global" is attached site-wide via first_theme.info.yml.global: css: # The "base" / "layout" / "component" / "theme" categories control # aggregation order (SMACSS). base: css/base.css: {} js: js/first-theme.js: {} dependencies: - core/drupal - core/onceEach key under the library name carries weight:
css:is split into SMACSS categories (Scalable and Modular Architecture for CSS):base,layout,component,state, andtheme. The parser,LibraryDiscoveryParser, maps each category to a numeric weight (baseis -200,themeis +200), and that weight decides the order rules end up in, across every library on the page. Element defaults load first, overrides load last, regardless of which module or theme contributed them.- The empty braces after each file accept per-file options such as
minified: trueormedia: print. Empty is the common case, but the key must be there. js:lists JavaScript files the same way.dependencies:guarantees other libraries load first.core/drupalprovides theDrupalJavaScript object and the behavior system;core/onceprovides theonce()function. Part 5 uses both; declaring dependencies you do not consume yet costs two small, cached files.
Library definitions are cached, so:
ddev drush cache:rebuildNo edit to first_theme.info.yml is needed: its libraries: list has named first_theme/global since part 1, and now the reference resolves. Attaching from the info file is the right tool for site-wide assets; for assets that belong to one template or one condition, themes attach libraries from preprocess functions or hook_page_attachments_alter() instead. Asset libraries covers those patterns.
Step 3: Declare the breakpoints
Section titled “Step 3: Declare the breakpoints”Your CSS already responds to viewport widths, but PHP cannot see media queries inside a .css file. Breakpoints exist to state them in a form the server can use, most importantly so the Responsive Image module can generate a different image size per screen width. Create first_theme.breakpoints.yml:
# Breakpoints let Drupal's responsive image styles and other modules react# to the same screen sizes your CSS uses. Mobile first: smallest has no# media query.first_theme.mobile: label: Mobile mediaQuery: '' weight: 0 multipliers: - 1x - 2xfirst_theme.tablet: label: Tablet mediaQuery: 'all and (min-width: 48rem)' weight: 1 multipliers: - 1x - 2xfirst_theme.desktop: label: Desktop mediaQuery: 'all and (min-width: 75rem)' weight: 2 multipliers: - 1x - 2xThe file format comes from the Breakpoint module’s plugin manager (the breakpoint.manager service):
- Each top-level key is a breakpoint ID, conventionally prefixed with the theme name.
mediaQueryis the literal media query string. The mobile entry’s is empty: mobile-first means the smallest width is the unqualified default, not a query.weightorders breakpoints from smallest to largest.multiplierslists the pixel-density variants (1x,2x) you intend to serve; high-density screens get a double-resolution image at the same layout size.- All of a theme’s breakpoints form a breakpoint group named after the theme, which is what you will select in part 6.
Declaring breakpoints does nothing visible by itself. There is no UI for them and no effect on your CSS; they are data, consumed by whatever asks the breakpoint manager. The Breakpoint module is installed by the standard profile; on a minimal site, ddev drush pm:install breakpoint first.
Troubleshooting
Section titled “Troubleshooting”The CSS file does not load
Section titled “The CSS file does not load”Library definitions are cached: rebuild caches after creating or editing *.libraries.yml. Then check the page source. If the library is attached but the file 404s, the path in the YAML is wrong; paths are relative to the theme directory.
Style changes do not show up
Section titled “Style changes do not show up”Drupal aggregates CSS and JS by default, and your browser caches the aggregates. While developing, turn aggregation off under Administer → Configuration → Development → Performance/admin/config/development/performance by unchecking Aggregate CSS files and Aggregate JavaScript files.
My styles load before the ones they should override
Section titled “My styles load before the ones they should override”Check the category. A rule in base loads before everything; if you are overriding another library’s component styles, the override belongs in theme (or at least the same category). Categories beat file order.
“The first_theme/global library defines a positive weight”
Section titled ““The first_theme/global library defines a positive weight””Library JS files reject positive weight values by throwing this exception; only small negative weights are allowed, and dependencies are the supported way to order JavaScript. Remove the weight and declare a dependency instead.
Next steps
Section titled “Next steps”- Part 4: a card component with props and slots builds the theme’s first single-directory component.
- Asset libraries documents conditional attachment, overrides, and extending other themes’ libraries.
- Images and media shows where breakpoints lead: image styles and responsive image rendering.
Was this page helpful?
Drupal is a registered trademark of Dries Buytaert.