Problem/Motivation

js.module.css includes some classes for older browsers as of 2023 #3404218: Table filter creates jank (layout shift) on page load.

https://caniuse.com/?search=scripting shows pretty broad support across multiple releases now.

Steps to reproduce

Proposed resolution

  1. Remove .js .js-hide and .js .js-show rules from js.module.css
  2. Decide whether rules/selectors within @media (scripting: enabled) media query need updating for specificity. Options:
    • Leave as is .js-hide.js-hide and .js-show
    • Use !important on .js-hide rule:
        .js-hide {
          display: none !imporant;
        }
        .js-show {
          display: block;
        }
      
    • add [class] to selector:
        .js-hide[class] {
          display: none;
        }
      
        .js-show[class] {
          display: block;
        }
      

For any CSS changes, need to regression test:

  • Views wizard per #10
  • Table filters per #3404218: Table filter creates jank (layout shift) on page load
    1. Navigate to the page in question (in this case /admin/modules), and open developer tools. Go to the network tab and set throttling to "Fast 3G") and ensure that "Disable cache" is checked.
    2. Reload the browser and notice how the "filter" pops in and shifts the layout.
    3. Apply the patch.
    4. Test again and note how the "filter" is there on page load and does not "pop in".
    5. Test the page without JavaScript (you can disable this in dev tools)
    6. Test the page on an older browser that doesn't support this new feature. The behavior should not change.
    7. Test the page on other browsers to ensure it works as expected.

Remaining tasks

User interface changes

NA

Introduced terminology

NA

API changes

NA

Data model changes

NA

Release notes snippet

NA

Issue fork drupal-3514748

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

catch created an issue. See original summary.

catch’s picture

Status: Active » Needs review

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Seems pretty straight forward.

catch’s picture

Title: Simplify js.module.css » Remove legacy browser support from js.module.css

nod_ made their first commit to this issue’s fork.

  • nod_ committed f9be36f0 on 11.x
    Issue #3514748 by catch: Remove legacy browser support from js.module....
nod_’s picture

Status: Reviewed & tested by the community » Fixed

Committed f9be36f and pushed to 11.x. Thanks!

lendude’s picture

StatusFileSize
new111.25 KB

This seems to have broken/started showing some buttons that need to be hidden in the Views wizard:

buttons showing that shouldn't

Should open a follow up or is this a revert and fix?

  • nod_ committed 99423203 on 11.x
    Revert "Issue #3514748 by catch: Remove legacy browser support from js....
nod_’s picture

Status: Fixed » Needs work

reverted for now, thanks for finding the issue!

catch’s picture

Cross-posted with nod_, it's a CSS specificity issue - Claro's .button display: inline-block; is overriding the .js-hide.

Pushed a commit to https://git.drupalcode.org/project/drupal/-/merge_requests/11855#note_49... which seems to be enough.

catch’s picture

Status: Needs work » Needs review
lendude’s picture

Not confident enough in my css skills to mark RTBC, but checked, and it fixes the issue in the Views wizard at least

godotislate’s picture

Status: Needs review » Needs work
StatusFileSize
new549.26 KB

The views wizard issue is fixed, but the change in MR 11855 causes a regression from #3404218: Table filter creates jank (layout shift) on page load. The filter is popping in again on /admin/modules when I use Chrome inspector to disable cache and throttle to 3G. This doesn't happen on HEAD.

Screen recording is attached.

catch’s picture

Status: Needs work » Needs review
Issue tags: +Needs manual testing

That's a good additional test case...

The reason .js .js-hide doesn't work is because the .js class is added by... JavaScript so it completely undermines the point of the original MR. Obvious once you realise it, but wasn't when I tried it out to fix the views issue.

I could not think of another good way to add additional specificity, so went for !important

This works for both test cases, but let's be honest I haven't written much CSS either for money or for free since about 2009 so there might be a better way.

Explicitly tagging for manual testing just in case I messed up the steps to reproduce too.

godotislate’s picture

Manually tested both the views wizard and modules filter and they both look good now. I'm slightly concerned about the use of !important, but I'm not familiar enough with what's going on there to offer any alternatives w/r/t specificity. +1 for RTBC if others are good with the !important.

bernardm28’s picture

To overcome some experience builder past bugs, we would add an attribute to increase the specificity.
Say.

.js-show[class] {
    display: block;
  }

https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
That could make it more specific than Claro and avoid using !important

bernardm28’s picture

Before this fix, without the !important
before image

After the fix with [class]
after the fix

godotislate’s picture

Looking back at comments from #3404218: Table filter creates jank (layout shift) on page load:

Original patch in #2 had this:

@media (scripting: enabled) {
  /* Extra specificity to override previous selector. */
  [class] .js-hide {
    display: none;
  }

  .js-show {
    display: block;
  }
}

Suggestion was made for this instead in #8:

Why not .js-hide.js-hide like we do usually when we need to increase specificity?

bernardm28’s picture

Both are valid solutions, but DrupalCMS is leaning more on attributes [class].
https://git.drupalcode.org/project/drupal_cms/-/merge_requests/374/diffs...
So I would say preference leans towards class attributes.

godotislate’s picture

From the IS, the goal is to remove the additional classes in the selectors since @media (scripting: enabled) is well supported. In which case, seems to me that the selectors within the media query should work well enough as is (and I've just tested to confirm), and the only thing needed is to remove the rules outside the media query.

That reduces the diff size, and the use of .{CLASS}.{SAMECLASS} selectors is used often in core, so I think that part can be left as is.

I have to say, this one in media-library.pcss.css is pretty amusing:
.media-library-item .ajax-progress.ajax-progress.ajax-progress

kwiseman’s picture

The .js-hide[class] approach seems like the most concise, specific option without using !important, and I'm also in favor of it. However, that approach would select .js-hide with any other class, not just its duplicate, but I'm not sure how much of a problem that is if it's working for Drupal CMS.

markconroy’s picture

I think this is a good example of when we would use !important.

In this instance, it's !important that when the class .js-hide is present that the item concerned is hidden.

Perhaps we can use !important just for the hiding part and not for the showing part. For .js-show we may not want display: block all the time (sometimes we might want flex/grid/inline-block/etc) so !important should not be used for that.

After that I think @godotislate "double class" idea is probably best. .js-hide.js-hide. If we use .js-hide[class] it might affect other classes if they are present.

smustgrave’s picture

Issue summary: View changes
Status: Needs review » Needs work

Sorry to be that guy, since this did cause an issue before can we update the summary fully. Tried to get it started.

godotislate’s picture

Issue summary: View changes
godotislate’s picture

Issue summary: View changes
catch’s picture

Tagging this for front end framework manager review, assuming the options are functionally (almost?) identical, this feels like more of a style preference/consistency decision.

nicxvan’s picture

Status: Needs work » Needs review

I think this is technically supposed to be needs review.

ahsannazir’s picture

StatusFileSize
new607.39 KB

I don't see any layout shift due to Filter shows a little late. I followed the steps as per IS
Attaching screen capture

catch’s picture

catch’s picture

Maybe I'm missing something, but I don't see why we can't use media-scripting: none to hide .js-show, which means we don't need to worry about overriding specificity and things either. Opened a new MR just in case this is silly, have not run manual CSS regression test yet but will try to do that next time I'm at computer properly.

https://git.drupalcode.org/project/drupal/-/merge_requests/14393

jwilson3’s picture

I like the approach in #35, as its forward thinking. That being said, the media scripting functionality is still relatively new (MQ Level 5) adopted in modern browsers around 2023. Checking caniuse media scripting shows we meet the "Baseline" at around 89% of global browsers. But for the other 11%, the way the MR is written might have a regression. On browsers that don't support media scripting, both media query blocks get ignored/dropped, and .js-show becomes visible by default. (i.e., "assume old browsers have JS enabled") Is this intentional?

Also, I can't make sense of the test failure, or why we have a test like this:

Asserting StylesheetBytes 979 is greater or equal to 1000 and is smaller or equal to 2000
Failed asserting that 979 ( is equal to 1000 or is greater than 1000 ) and ( is equal to 2000 or is less than 2000 ).
catch’s picture

But for the other 11%, the way the MR is written might have a regression. On browsers that don't support media scripting, both media query blocks get ignored/dropped, and .js-show becomes visible by default. (i.e., "assume old browsers have JS enabled") Is this intentional?

Yes this is OK. Browser requirements for core are listed here: https://www.drupal.org/docs/getting-started/system-requirements/browser-... and it's well covered by the browsers we support. It's also fine I think to default to assuming js is enabled these days since disabling JavaScript is rare now compared to 15-20 years ago.

Test failure is a performance test, it's designed to measure changes like this, so that we gradually reduce our page weight (and the other things it measures) over time, or only increase them when necessary. https://www.drupal.org/docs/develop/automated-testing/performance-tests updating it now.

That test was at around 7kb when it was added, and now under 1kb through incrementally going through #2880237: [meta] Refactor system/base library.

catch changed the visibility of the branch 3514748-specifity to hidden.

jwilson3’s picture

Nice. I wanted to check the tugboat preview environment, but it is marked failed. I tried a rebuild which also failed.

godotislate’s picture

Status: Needs review » Needs work
StatusFileSize
new69.55 KB

Tested MR 14393 locally, and it fails the regression test per #10. This is on MacOS Tahoe Chrome 143.0.7499.193. The buttons do not appear when I test on main.

Views wizard with buttons that should be hidden

catch’s picture

That's the same specificity issue discussed above, pushed a commit for .js-hide[class] which works for me locally.

catch’s picture

Status: Needs work » Needs review
godotislate’s picture

Latest commit fixes the View wizard, and there's no regression on the module filter in the IS. Though it seems we've circled back to whether .js-hide.js-hide (existing Drupal core convention) vs .js-hide[class] (new Drupal CMS convention? see #20) is preferred.

catch’s picture

I personally think .js-hide.js-hide looks like someone made a typo, where .js-hide[class] is more intentional. However I have no idea what's the 'better' CSS (and there's also !important too).

If we don't care that much, we could go ahead and open a follow-up to settle on a convention to use across core. Would be nice to start saving the bandwidth, even if it's miniscule it's more or less every request to every Drupal site.

jwilson3’s picture

Normally, I would never argue for the use of !important, but .js-hide/.js-show cascade warfare seems different than other places.

The cascade problems arise when we're doing things like display:block but we want that element to have (for example) display: flex and have to resort to CSS specificity hacks.

But the refactor from catch has cleverly allowed us to removed all display: block instances.

ISTM that now that .js-hide/.js-show is used exclusively for hiding content in certain scenarios, it should take precedence over everything else and therefore display: none !important could be safe to use here, no? What am I missing?

godotislate’s picture

I think we're just back at the outstanding "Needs frontend framework manager review".

catch’s picture

@jwilson3 yes that's right - we've got rid of display:block so the only issue we have now is we need our rules to win in all cases.

I'm actually going to switch to !important because that's exactly the behaviour we want, even though the feature is discouraged it's a real feature and feels like the right place to use it. All three approaches work so we can always change it later.

General note that in the process of thinking about this issue, I think we can make it so this file is only added conditionally too, doesn't affect the contents of the CSS, only how we organise it #3568172: Move .js-show and .js-hide libraries to their own library and load them when js is on the page for the follow-up.

markconroy’s picture

@catch I know I mentioned in #15 above that !important seems right here, but often we want to change something from display: none; to display: flex; (or grid, etc) (rather than block), and that !important could come back to bite us.

I'm not saying we should not do it, but just wanted to raise it as a consideration.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

jwilson3’s picture

No strong opinion either way, but note: this MR removes all display: block usage and standardizes on display: none (optionally reinforced with !important). This is so that now, out of the box, display: flex (or grid or block) applied to a div that has js-show will Just Work ™ as you intended!

If on the other hand someone wants to (for whatever crazy reason) explicitly do the inverse of what is coded (i.e. show something marked as js-hide when JS is enabled, or hide something marked as js-show when JS is disabled), the correct approach is to either A) swap js-hide with js-show and vice-versa or B) remove the js-hide/js-show utility class and replace it with your own class and styles. (I hope this makes sense to everyone, it boils down to logic tied to classnames matching display intent).

Using display: flex !important to override those utilities works, but it’s effectively opting out via a specificity hack, so the burden is intentionally on the developer when going against the pattern.

Without !important reinforcement in the MR, the minor risk is that if something is coded with high specificity eg :root .mything { display: flex } the js-hide/js-show logic will never take effect.

Edit: updated my comment for clarity. 2026/4/1

smustgrave’s picture

Haven't been able to get ahold of a frontend manager. Not sure how to best move this one forward.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new553 bytes

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

catch’s picture

Status: Needs work » Needs review

@smustgrave this can be RTBC without an FEFM review (assuming people think it's RTBC, but for me I think we have enough consensus, or at least lack of strong opinions, on the current solution), it just needs FEFM sign-off before it gets committed.

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community
Issue tags: -Needs manual testing

In that case think it’s good to go.

catch’s picture

iirc this removes a style recalculation - because the .js class is added by JavaScript, but apparently I failed to add that information to the issue and it's some time since I checked this. I think this makes sense anyway just to trim the CSS weight, but if my memory is correct it should result in a small browser rendering improvement on top of that.

nod_’s picture

+1 from me, !important is made for this kind of things.

  • godotislate committed b7156dd5 on main
    refactor: #3514748 Remove legacy browser support from js.module.css
    
    By...

  • godotislate committed 5b744138 on 11.x
    refactor: #3514748 Remove legacy browser support from js.module.css
    
    By...
godotislate’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

Committed and pushed b7156dd to main and 5b74413 to 11.x. Thanks!

Should this go to 11.3.x? Moving to Patch to be ported in case we want it.

catch’s picture

Version: main » 11.x-dev
Status: Patch (to be ported) » Fixed

I think I would leave this just in 11.x/11.4, it's low risk but also not a bug fix. There's maybe a tiny chance it affects how someone is overriding these classes, probably not though.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

alexpott’s picture

Status: Closed (fixed) » Needs work

This has caused a regression in the paragraphs module when a modal dialog is used to add new paragraphs.As this is a task I propose that we revert this change from 11.x, 11.4.x and main and try again.

chr.fritsch’s picture

catch’s picture

Discussed this in slack with @alexpott. Paragraphs is implicitly relying on the exact CSS behaviour of the legacy support, so that with the js-hide class shows when it becomes display:block even though the js-hide class is still on it. I think this is a bug in paragraphs, but also there could be more examples out there.

It feels unlikely we'll be able to come up with CSS behaviour that can cover any previous implicit reliance on the old CSS, so I think the easiest thing to do here is to revert this from 11.x and 11.4.x but leave it in main - making it an major-only change. Gives paragraphs and any other modules time to adapt.

alexpott’s picture

Status: Needs work » Needs review

I've prepped an MR against 11.x that also applies cleanly to 11.4.x to revert this.

chr.fritsch’s picture

Status: Needs review » Reviewed & tested by the community

I can confirm that paragraphs is now not broken anymore

  • catch committed be47f6ad on 11.4.x
    task: #3514748 (11.x revert) Remove legacy browser support from js....

  • catch committed c6324354 on 11.x
    task: #3514748 (11.x revert) Remove legacy browser support from js....
catch’s picture

Version: 11.x-dev » main
Status: Reviewed & tested by the community » Fixed

Committed/pushed to 11.x and 11.4.x

Moving this back to main since that's now the only place that's changed.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.