New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define that push/replaceState don't affect to :target handling #639

smaug---- opened this Issue Feb 6, 2016 · 9 comments


8 participants

smaug---- commented Feb 6, 2016

One could argue push/replaceState should affect to :target handling, but given that no browser seems to do that, we should probably just spec the current handling.


This comment has been minimized.


smaug---- commented Feb 6, 2016

We might also want to think of some backwards compatible change to push/replaceState which allow target handling (and also hashchange events?).


This comment has been minimized.

beidson commented Feb 8, 2016

While I agree this should have worked, I also agree that since no browser has ever done it, and all major browsers are in agreement on it not working, that this change is the right course of action.

We might also want to think of some backwards compatible change to push/replaceState which allow target handling (and also hashchange events?).

That would also be ideal.


This comment has been minimized.

laughinghan commented Apr 13, 2016

(@majido: smaug____ suggested in IRC that I ask you for feedback)

a safe breaking change?

From my understanding, the only reason to be wary of changing a behavior that every browser agrees on is the danger of breaking webpages, right? There's nothing intrinsically bad about changing behavior, the badness is that webpages that used to work may no longer work, which is intrinsically bad.

Frankly, it seem far-fetched to me that fixing this would break a webpage. There are only two ways that could happen, that I can think of:

  1. a :target style that affects layout that a webapp expects to be applied on initial page load but not when the app does .pushState()/.replaceState()
  2. javascript on the page reading a computed style from something that could be selected by a :target selector and making a decision based on the computed style, that is correct on initial page load but not when the app does .pushState()/.replaceState()

There are corpora available of stylesheets used on popular websites, right? We could analyze them to determine whether 1. is true, right?

I doubt it's feasible to automatically determine whether 2. happens, but it also seems even less likely than 1., so supposing that 1. turns out in fact to never happen, could we make this change in Nightlies of each browser, and then maybe Betas, and see if anyone complains?

I'd bet no one does, and the Web would simply be a better place.

backwards-compatibly fixing this

Okay so if you wanna be a real hard-ass about this (or I'm wrong and we find a counterexample), how do authors ask browsers to recompute :target styles without adding to history and/or causing the page to jump?

What if we made the fix a global, page-wide opt-in to minimize pain for web authors, like:

history.recomputeTargetSelectors = true;

Update: Looks like there's precedence for this kind of thing: history.scrollRestoration = 'manual', which has since been integrated into the HTML Living Standard and implemented in both Chrome and Firefox.

Other options include:

  • a 4th argument to .pushState()/.replaceState() (on every call? Blech)
  • updating the CSS selector:
h3:target-including-if-changed-by-pushState-this-should-probs-have-a-better-name {
  background: yellow;

Once upon a time we might've linked this to the next DOCTYPE version, but that's out of the question, right?

laughinghan added a commit to mathquill/ that referenced this issue Apr 13, 2016

Fix smooth-scroll to not break the back button
In the Beginning, "Community" in the navbar on the homepage was a normal
link, and clicking it would take you to the Community section and
highlight the heading, and the browser Back button would take you back
to your scroll position at the top of the page where the navbar was.

Then I added smooth-scrolling so that when you click "Community" in the
navbar it would smoothly scroll down to the Community heading, and
highlight it. Problem: if you then hit the browser Back button, the
heading highlight would vanish but you would stay in place, rather than
the expected behavior of going back to the scroll position at the top of
the page where you clicked on the navbar.

Now I'm finally fixing that by doing history.pushState():
(falling back to the old technique if unavailable)

Of course the web platform sucks so we have to contend with:

This comment has been minimized.

aimnadze commented Apr 14, 2016

I agree with @laughinghan and vote for fixing it early than carrying the weight of the bugs until the end of the web.


This comment has been minimized.

laughinghan commented Apr 21, 2016

Inspired by a discussion with smaug____ in IRC, I've found a good reason to believe that virtually no webpages would be broken by the "breaking" change of updating :target selectors upon pushState: though they are currently not updated, if you however hit Back and then Fwd again, then :target rules do get applied (

That means that the only way for a webapp to be broken by :target selectors being updated upon pushState, yet function correctly when you hit Back then Fwd, would be if the webapp's popstate/hashchange handlers had some kind of hysteresis that obscured the broken :target CSS. This seems highly unlikely to me. I have difficulty imagining why a popstate/hashchange handler would make a CSS change that depended on the sequence of Backs and Fwds to get to that history state, not to mention how it could obscure a :target rule that would've been broken if it had been applied upon the first .pushState().


This comment has been minimized.

pimterry commented Oct 10, 2016

+1 for fixing this properly - it's very surprising behaviour.

Conveniently @laughinghan's back/fwd find does provide a nice workaround for the current behaviour, which might be of interest to everybody else trying to actually use :target with browser history, and ending up here. You can double-add to the history with pushState and go back immediately to get expected behaviour:

function pushHashAndFixTargetSelector(hash) {
  history.pushState({}, "", hash);

  // Do it again and go back immediately, to force :target to update.
  history.pushState({}, "", hash);

This gives the expected result. Only side-effect (I think) is that the user's forward button is no longer disabled, although if they hit it, it won't break anything - it's the exact same URL. (Yes, this is a little nasty - we should fix it in the spec properly).

More discussion on actually using this:


This comment has been minimized.

robinbastien commented Mar 9, 2017

@pimterry Ha, thanks! Great workaround. Would be nice if pushState updated :target by default :(


This comment has been minimized.

laughinghan commented Mar 9, 2017

FYI @pimterry @robinbastien I believe you can avoid the odd side effect of the forward button being disabled by instead doing:

function pushHashAndFixTargetSelector(hash) {
  history.pushState({}, "", hash);

  // go back and then come forward again immediately, to force :target to update.
  var onpopstate = window.onpopstate;
  window.onpopstate = function() {
    window.onpopstate = onpopstate;

That's what I did in my original test


This comment has been minimized.

asgh commented Apr 25, 2017

When fixing this, please also consider what happens if you use pushState, followed by window.location.hash = 'foo'.

In Firefox this will update the :target, but in Webkit browsers the visible hash changes, but the :target is not affected until there is any kind of non-pushed change to the url.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment