Toolbar chrome in #cms-top is not protected from project CSS: replace .cms-reset with an id-anchored 'all: revert'
Problem
.cms-reset is the only thing protecting toolbar chrome from project CSS, and it is far too weak:
- Compiled, it is
.cms-reset a— specificity (0,1,1). Any theme rule with two classes beats it. - It resets a hand-written list of ~15 properties on a hand-written list of elements (no
span,button,h4–h6,img,svg; notext-transform,letter-spacing,white-space,visibility). - Components rely on it for properties they never set themselves.
_toolbar.scsscompiles todiv.cms .cms-toolbar-item-navigation li a{color:…;padding:0 10px;…}— specificity (0,2,3), but it never setstext-decoration, delegating that to the (0,1,1) reset. So the strong rule and the weak rule govern different properties of the same element, and only the weak one is exposed.
Real-world hit on django-cms.org: an accessibility rule restoring underlines for links in running text,
:is(p, li, td, dd, blockquote, figcaption) a:not(.btn, .nav-link, …) { text-decoration: underline; }is (0,1,2) and underlines every item in the toolbar menus, because those are <ul><li><a>. Nothing in django CMS pushes back. The whole compiled cms.base.css contains exactly one !important.
Proposal
Replace the allowlist reset with a real one, anchored on the id:
#cms-top *:not(svg, svg *) { all: revert; }- (1,0,1) — beats essentially any project rule that does not use ids or
!important. allcloses the property and element allowlist holes in one go.svgis excluded becauseall: revertwould wipe presentation attributes on the icon sprites.
This outranks django CMS's own component rules at (0,2,x), so they have to be lifted above it. That looks like a one-line change, since everything is already nested under a single wrapper in cms.base.scss:
// div.cms needs to beat .cms-reset a selectors
div.cms { → #cms-top {giving #cms-top .cms-toolbar-item-navigation li a = (1,1,3) > (1,0,1). I checked that all chrome — toolbar, messages, tooltips, sideframe, modal — renders inside #cms-top via the toolbar_top/toolbar_bottom blocks in toolbar_with_structure.html, so re-anchoring should be safe. The one div class="cms" outside #cms-top is cms/templates/cms/noapphook.html.
Notes
- Cascade layers do not help here: unlayered author styles beat layered ones for normal declarations, so wrapping
cms.base.cssin@layerwould make it lose to every unlayered project stylesheet. - Shadow DOM on
#cms-topwith:host { all: initial }is the only complete isolation, butcms.base.js, plugin-contributed toolbar items and the structure board all assume light DOM — a major-version project, not a fix. - Cheap independent hardening, worth doing regardless: stop letting the reset be load-bearing. Adding
text-decoration: noneto.cms-toolbar-item-navigation li afixes the reported symptom at (0,2,3) with zero risk.
Version: django CMS 5.1.1.
Source: django-cms/django-cms