Skip to content

Rid our frontends of the !important trap #559

Description

@arbrandes

Description

!important is abused throughout our frontend CSS, and it is the single biggest obstacle to making Open edX sites properly themable. Paragon ships it and the apps ship more of it, so a theme cannot win the cascade without reaching for !important of its own, and every round of escalation makes the next one harder. Indigo is the clearest evidence of where that leads: its brand package is full of !important for no reason other than that it had nothing else left to beat Paragon and the apps with. This came up most recently in Branding authn with Indigo in a frontend-base world.

The objective of this epic is to rid the app layer of !important as part of the frontend-base conversion work, and to do the same in frontend-base and Paragon themselves. Downstream brand packages are out of scope: once the layers above them are clean, they have nothing left to fight, and their own cleanup belongs to whoever maintains them.

Why the cascade no longer needs it

frontend-base declares an explicit cascade layer order, @layer paragon, shell, app, site, brand; (ADR 0008). For normal declarations that order does exactly what theming wants: brand beats site, site beats app, app beats shell, shell beats Paragon, and runtime-injected brand CSS, which lands unlayered, beats all of them. Selector specificity stops deciding anything across those boundaries, so a brand no longer needs !important to win.

For !important declarations the CSS spec reverses layer precedence. A single !important in the paragon layer outranks every !important a shell, app, site or brand can write, and unlayered important declarations - which is what the production brand injection path produces - sit at the very bottom of the important stack. So every !important we ship punches a hole in the layer model instead of reinforcing it, and the only counter left to downstream is worse than the problem. Removing the flag is what makes ADR 0008 actually hold.

Scope

Apps, as they convert to frontend-base. A conversion already touches the app's stylesheets, so combing them is cheap to fold in and there is no coordination cost with anyone else. This is the bulk of the work and the highest value per line changed, because app !important blocks operators outright: nothing downstream can override it. In practice the occurrences are concentrated in one or two global stylesheets per app rather than spread across components.

frontend-base itself. The shell has one occurrence, in shell/style.scss. It should go before we ask anyone else to do the same.

Paragon's component styles. Straightforwardly wrong and cheap to fix by lowering selector specificity instead. Roughly 18 occurrences across a handful of components.

Paragon's utility classes. The one case that needs a decision rather than a cleanup, and it should probably be its own discussion and its own release. Bootstrap bakes !important into utilities like .mt-3 and .text-primary deliberately, so they act as a last word: 92 declarations in the SCSS generators, expanding to 510 in the built light theme. Under cascade layers that last word now outranks every brand and site override, which is the opposite of what we want, but dropping the flag changes behaviour for every consumer. The exploratory Paragon without bootstrap prototype is the obvious place for this decision to land, since it is already rebuilding the utility layer from scratch.

Acceptance criteria

Every app converted to frontend-base lands with no !important in its stylesheets, and the shell's own styles and Paragon's component styles are clean. The utility-class question has an explicit, recorded decision either way. And ADR 0008 is amended to note that the layer order only delivers what it promises in the absence of !important.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Status
Todo

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions