A pseudo-class for when an element is stickily (spec term) positioned


With position: sticky; now in CSS there are times where we want to transform an element when it hits the position where it’s sticky.

Position Sticky Spec


Let’s do a simple element changing from a circle to a square

#sticker {
 position: sticky;
 top: 15px;
 width: 100px;
 height: 100px;
 background: red;
 border-radius: 50%;

#sticker:sticky {
  border-radius: 0;

Yup we need this, or we still have to rely on Javascript.

Sorry if this has already been discussed? :confused:

Or will we be using Element Queries for this?

I believe Element Queries will only be the same that we use for media queries, I could be wrong?


I’m guessing @tabatkins would know more about the history of position: sticky

:stuck {
  position: static;

…which is why we can’t do :stuck.


A theoretically simple solution would be to simply invalidate position declarations inside rules where the selector subject includes the :stuck pseudo-class, but of course that might have issues in practice. AFAIK no other selectors impose restrictions on which declarations are valid.


I need to write a blog post for this, since I keep having to bring it up, but that doesn’t work. As soon as there is more than one instance of this kind of circularity, you can loop them together and run into the same problem:

:based-on-a {
  b: on;
:based-on-b {
  a: off;

No direct circularity - the rule with :based-on-a doesn’t contain the a property, and same for b, but the exact same loop nonetheless. You have to outlaw all selector-affected properties from appearing in rules using any property-affected selectors, which quickly starts wiping out large swathes of CSS and renders the whole thing useless.


Well @tabatkins the reason I’d say sticky over stuck is:

  1. We have already position: sticky; Which were familiar with so no need of a different word
  2. I’m kind of comparing to :active and :hover

The word :hover in future tense. Instead of past tense :hovered, meaning :stuck is like past tense. Now active because it’s the word active it could be used:

When it was active, when it’s active, it is active.

So yea idk what you wanna do we could use stuck which makes sense but we could also use sticky and not have two words sticky for position: sticky; and :stuck for the pseudo class


What I posted has nothing to do with the name we use.


Oh crap, I read that horribly wrong. I thought it said why not use :stuck. Holly crap how the hell did I read that. Wow my bad that was dumb. :confused:


renders the whole thing useless.

I disagree. If we’d allow only those properties that don’t affect layout to be redefined for the :stuck pseudoclass, we’ll get infinitely more control over what we have now (nothing).

And those properties that don’t affect layout are those that are used for sticky elements in real world use cases. I guess the most popular property to redefine on the stuck state would be a box-shadow.

The layout-changing properties could be useful too, but why can’t we go iteratively and allow just a subset of properties from the start, and think out the solutions to enhance it later?

Developers need position sticky already and need this pseudoclass for them too, saying “there are issues, so you won’t get it in any form” is not productive.


Can we use the behavior for when other pseudo-classes get into infinite loops? For example:

div {
  position: fixed;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;

div:hover {
  height: 0;
  width: 0;

I’m having flashbacks to Web Pages for Dummies where they warn against using font-size for a:hover for this exact reason. Is it still an unsolved problem?


That’s not sufficient; or rather, it’s only sufficient once. As soon as you add a second selector linked to properties, even if relies on a disjoint set of properties, you can force a circularity by having :A set the B property, and have :B respond to that and set the A property. You have to exclude all properties that affect selectors from being used in a style rule using any selector affected by properties. That quickly rules out most/all of CSS, and requires breaking old content that (correctly at the time) used some property that affects the new selector.

The right thing is to just avoid the problem. Find tricksy ways to make it not screw with things, or just leave it and use JS.


No. :hover-based loops are “wide” - they start at style computation, move through layout and rendering, and get all the way to user interaction before they loop back around. The entire page is “done” by the time the loop spins around, so, while the flicker is annoying, it doesn’t prevent the rest of the page from working.

These kinds of property/selector loops are “tight” - they start and end within style computation, and prevent the browser from moving on to layout/rendering/etc at all.


Can you provide a meaningful example for the conflicting behaviour you’re mentioning in regards of this example:

.sticky {
  position: sticky;
  top: 0;
  background: red;
  box-shadow: 0 1px #000;

.sticky:stuck {
  background: lime;
  box-shadow: 0 10px 10px -7px rgba(0, 0, 0, 0.5);

How this (or any other external styles) could trigger any kind of circularity there? What do I miss?

Also, there is no way to avoid this using CSS only, and it far from trivial when trying to emulate using JS (btw, would there be/are there already any events in JS to tell when the element become stuck or not?).


Again, the issue is that we can’t ever introduce a second selector that’s affected by properties. (And we can’t ever add a layout-affecting value to one of the “safe” properties.)

Something that we can only do once, ever, and that will affect our ability to evolve CSS in the future, is probably a bad idea for the language.


we can’t ever introduce a second selector that’s affected by properties

I still think this could be handled in some way (like using only one pass (I don’t remember how this mechanism is called now), as we already do for fit-to-content blocks with more than one line). This looks like “this is hard, we won’t ever try”.

we can’t ever add a layout-affecting value to one of the “safe” properties.

We can, if we’d make those values not to work in this context.


All of the solutions I can think of start getting into procedural territory, and I doubt that’s a road the CSSWG wants to go down. Unless we can mark a declaration as “nonvital” or something, and even then I’m not sure that would actually accomplish the right thing.


What if :sticky selects elements which are within a position: sticky element that has been stuck, but does not match the position: sticky element itself?

#sticker {
  position: sticky;
#sticker div {
  background: red;
  border-radius: 50%;
  height: 100px;
  width: 100px;
#sticker div:sticky {
  border-radius: 0;
  1. would it run into any looping issues?
  2. would it still be useful enough to be worthwhile?


If the main problem is creating tight loops, instead of a CSS class, we could have events. For example: Element.onEnterSticky Element.onLeaveSticky

Now javascript code can still create loops, but wide ones, that don’t block the style computation.

Adding and removing CSS classes on the event handlers would be trivial. Much simpler and therefore less error prone than the alternative of computing manually whether the element is stuck or not.


I’m wondering if we could perhaps also allow :stuck in the JS profile for use with querySelector. This wouldn’t have the tight loop issues of allowing it in a stylesheet and there’s precedent for allowing selectors in the JS only profile already (:matches() I believe).


Any update on this feature? This feature is really important, because we can finally get rid off onScroll event for adding .is-stuck classes.