When CSS in a Drupal sub-theme does not apply, the cause is almost always one of four things — the file never loaded, it loaded in the wrong order, a more specific selector won, or you are looking at a cached copy. Working through them in that order takes about five minutes. Guessing takes an afternoon, and usually ends with an !important that quietly breaks something else six months later.
This is the order I actually use, with the Drupal-specific traps that the generic "clear your cache" advice leaves out.
Step 1: prove the file reached the browser at all
Open DevTools, go to the Network tab, filter to CSS and reload. You are looking for your sub-theme's stylesheet by filename. Two outcomes matter:
- The file is not in the list. Drupal never attached the library. Skip to step 2.
- The file is there but returns 404. The library is attached and the path in your
.libraries.ymlis wrong relative to the theme root. This is the single most common typo, and it is silent — Drupal does not validate that a declared CSS file exists.
Do this before anything else. Half of all "my CSS is not working" tickets end here, and every step below is wasted effort if the file never arrived.
Step 2: is the library attached, and is it attached to this page?
A sub-theme stylesheet reaches a page one of two ways: it is listed under libraries: in the sub-theme's .info.yml, in which case it loads globally, or it is attached conditionally with #attached from a preprocess function or attach_library() in Twig, in which case it loads only where that code runs.
The solo_subtheme starter that ships inside the Solo theme is a minimal worked example of the global case — one library, one CSS file, one JS file, declared in the sub-theme's .info.yml. If you built your sub-theme by hand and skipped the libraries: key, nothing you put in .libraries.yml will ever be requested.
One thing that trips people up on CSS-only changes: the sub-theme also has to be the active theme. Installing it under Appearance is not the same as setting it as default, and a sub-theme that is installed but not default will not contribute a single stylesheet to the front end.
Step 3: it loaded, but something else wins
Select the element in the Elements panel and read the Styles pane from the top: the winner is first, everything struck through lost. Two distinct things can be beating you, and they need different fixes.
Specificity
If the losing rule is your file and the winning rule is a base theme's, and the winner has a longer selector, this is ordinary specificity. Lengthen your selector to match or exceed it — scope it to a body class, a region wrapper, a component class. Reaching for !important here is a decision to lose the next argument too, because the only way to beat an !important is another one.
Load order
If your selector is identical to the one beating it and you still lose, the other file simply loaded later. In Drupal this is not arbitrary: assets are sorted by aggregation group first and only then by weight, and all theme CSS sits in a different, later group from all module CSS. The practical consequence is that a theme's stylesheet always lands after every module's stylesheet regardless of the SMACSS category either of them declared — and inside your own theme, the category you nest a file under changes its weight by up to 400. I have written that mechanism up in full in SMACSS-based CSS categorisation in Drupal theming, because getting it right at the .libraries.yml level removes this entire class of problem permanently.
Step 4: three places CSS can come from in a Solo site
If your sub-theme is built on Solo, there are three separate delivery mechanisms, and confusing them wastes real time because each one appears somewhere different in DevTools.
| Source | How it is delivered | Where you find it |
|---|---|---|
Sub-theme .libraries.yml | A normal Drupal library, aggregated with the rest of the theme CSS | Network tab, as a file |
| Solo's CSS Injector setting | An inline <style> element written into the page head | Elements panel, on an element with id="solo-css-injector". It will never appear in the Network tab. |
| Solo's CSS Dynamic setting | Written to a generated file under the public files directory and attached as a library | Network tab, as solo-css-dynamic.css |
The CSS Dynamic field carries a trap worth knowing about. Solo only attaches that library if the setting is non-empty and the generated file actually exists on disk. If the public files directory is not writable, the file is never written, the library is never attached, and there is no error message anywhere — the field just keeps your CSS and nothing happens. If you pasted CSS into that box and the page did not change, check that the file exists before you check anything else.
The CSS Injector walkthrough shows both fields in the settings form if you want to see where they live.
Step 5: caches, in the right order
Clear Drupal's cache with drush cr, then hard-reload the browser. Do it in that order: clearing the browser first and Drupal second means the browser re-caches the stale aggregate you were trying to get rid of.
For active theming work, turn on Drupal's development settings instead of clearing caches every thirty seconds. On current Drupal core there is an admin page for it at /admin/config/development/settings with a Twig development mode toggle, plus separate checkboxes for Twig debugging output and disabling the Twig cache. That page writes the same state the old services.yml edit used to, without you having to touch a file or remember to change it back.
drush cr
drush state:set system.css_js_query_string $(date +%s)
The second command changes the query string Drupal appends to aggregated assets, forcing every browser and CDN in front of your site to treat them as new files.
Step 6: the things that are not CSS problems
- File permissions. A stylesheet the web server cannot read returns 403, not 404. Read the actual status code rather than assuming.
- Multisite. Check you edited the theme under the right site directory. The right file in the wrong site directory produces exactly the symptoms of a caching problem, and none of the cache fixes work.
- Aggregation. Turn it off at Configuration → Development → Performance while debugging, and back on before you finish.
Common questions
Should I edit the base theme's CSS directly instead?
No. The next update overwrites it, and you will not remember what you changed. A sub-theme exists so your changes survive updates; that is the entire point of the pattern. If you are using Solo, its starter sub-theme gives you the correct structure in about a minute.
Is !important ever the right answer?
Occasionally, and only against markup you do not control — an inline style emitted by a contributed module, or a third-party widget. Against your own base theme it is a symptom, not a fix.
How do I know which template produced this markup?
Enable Twig debugging on the development settings page above. Drupal then writes HTML comments naming the template file and every suggestion it considered, directly into the page source.
When to stop debugging and get help
If you have been through all six steps and the cascade still surprises you, the problem is usually architectural rather than a single rule — overlapping libraries, a base theme fighting a contributed module, or a sub-theme that was never quite set up correctly. That is a couple of hours of Drupal theming work, not a week of yours.
If you would rather hand the whole front end over, request a quote and describe what you are fighting with. If you are still choosing a base theme to build on, what the Drupal admin covers and where code starts is the more useful page to read first.