user.css, user.js and the Web Asset Manager: adding your own CSS and scripts to Joomla
Add your own CSS and JavaScript to Cassiopeia with user.css and user.js, which survive updates, and load other files through the Web Asset Manager.
Someone wants the buttons a different shade of green and the logo ten pixels smaller. The quick route is to open template.css in the template editor and change it there. It works until the next Joomla update, which ships a fresh copy of Cassiopeia and silently puts the old file back.
Cassiopeia has a proper place for your own styles and scripts, and since Joomla 4 there is a proper way to load extra files from a template or extension: the Web Asset Manager. This article covers both.
For a tracking script, a verification meta tag or a bit of CSS for one page you do not need a file at all. You can put it in a module instead, for example our own free Custom code module, which works with any template.
user.css: the file Cassiopeia looks for
Cassiopeia checks for a file called user.css on every page load. The file does not exist after installation, so nothing happens. Create it, and it is loaded after the template's own stylesheet. Same goes for user.js.
The locations:
media/templates/site/cassiopeia/css/user.css
media/templates/site/cassiopeia/js/user.js
Note the media folder. Since Joomla 4.1 template assets live there, not in templates/cassiopeia/css. A user.css in the old folder is ignored, which is the most common reason people say "user.css does not work".
You do not need FTP for this. Go to System > Site Templates > Cassiopeia Details and Files, open the media folder in the file tree, select css and click New File. Name it user, pick css as type, and you have an empty file in the right spot.
Core updates never touch user.css or user.js, because Joomla does not ship them. That is the whole point.
Why your rule wins
Because user.css loads last, a rule with the same specificity as the one in template.css overrides it. You rarely need !important. If your rule still loses, open the browser's inspector, look at which selector Cassiopeia uses, and copy it.
For colours you often do not need selectors at all. Cassiopeia builds its colour scheme from CSS variables, so this changes buttons, links and the header in one go:
:root {
--cassiopeia-color-primary: #1f5f3f;
--cassiopeia-color-link: #1f5f3f;
--cassiopeia-color-hover: #14402a;
}
Look at the variables in the inspector under :root first. The names have shifted slightly between versions, and a variable that does not exist does nothing, without an error.
Child templates
If you already use a child template of Cassiopeia, put user.css in the child's media folder instead, for example media/templates/site/cassiopeia_mysite/css/user.css. A child template uses its own file when present and falls back to the parent's files otherwise. Keeping your styles in the child also means they travel with it if you copy the child to another site.
Loading other files: the Web Asset Manager
user.css is fine for a few dozen rules. For a separate stylesheet per section, a library like a lightbox, or a script from your own extension, use the Web Asset Manager. In a template's index.php or a layout override:
$wa = $this->getWebAssetManager();
$wa->registerAndUseStyle('mysite.shop', 'media/templates/site/cassiopeia_mysite/css/shop.css');
$wa->registerAndUseScript('mysite.gallery', 'media/templates/site/cassiopeia_mysite/js/gallery.js', [], ['defer' => true]);
In a module or component layout, where $this is not the document, fetch the manager from the application:
use Joomla\CMS\Factory;
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->useScript('bootstrap.collapse');
That last line shows the second advantage. Joomla already registers Bootstrap's components, jQuery (for the extensions that still need it) and its own core scripts under fixed names. Asking for bootstrap.collapse loads the file once, in the right order, no matter how many modules ask for it. Two modules each adding their own <script src> for the same library is how you end up with a menu that opens and immediately closes again.
joomla.asset.json: register once, use by name
Every template has a joomla.asset.json next to its index.php. Cassiopeia's file is where user.css is registered, under the name template.user. You can add your own entries in a child template's file:
{
"name": "cassiopeia_mysite",
"version": "1.0.0",
"$schema": "https://developer.joomla.org/schemas/json-schema/web_assets.json",
"assets": [
{
"name": "mysite.shop",
"type": "style",
"uri": "shop.css",
"dependencies": ["template.active"]
}
]
}
A relative uri is resolved against the template's media folder, so shop.css means media/templates/site/cassiopeia_mysite/css/shop.css. After that, any layout can load it with $wa->useStyle('mysite.shop'). The dependencies line makes sure the template's own stylesheet comes first.
What not to do
- Editing
template.css. Gone after the next update, without a warning. - A Custom module full of
<style>and<script>tags. The editor's text filter strips script tags for most user groups, and an unpublished module takes your styling with it. Nobody looks for CSS in a module six months later. $document->addStyleSheet()in new code. It still works but bypasses the ordering and deduplication above, and it is on the list for removal.- Loading a library from a CDN in a module and again in the template. Register it once in
joomla.asset.jsonand use it by name everywhere.
Changes do not show up
You saved user.css, reloaded, and nothing changed. Before you start doubting your selector, rule out caching. Joomla adds a version string to asset URLs, the browser keeps the old file, and many hosts run a proxy cache in front of the site. Do a hard refresh (Ctrl+Shift+R, or Cmd+Shift+R on a Mac), check in the inspector's Network tab which file was actually loaded, and if the old content still comes back, clear the hosting cache. Then check the path once more: media/templates/site/..., not templates/....
Doctor Joomla is a project of Cloudfaction, a Dutch agency building and maintaining Joomla sites since 1999. The articles are made with the help of AI, so tell us if something does not match your screen. Mail info@cloudfaction.nl if you want someone to look at yours.