A reader sent me in a GIF showing off a cool effect they saw on Google on mobile. (Presumably the homepage you see when you launch Chrome on Android?) There is a search input in the middle of the page that scrolls with the page, but as it’s about to scroll off the page, it becomes affixed to the header. Let’s cover that, because, you know…
OH: I'm a mobile web developer, so I basically spend all day getting requests for content to stick to the top after some scrolling
— Daniel Wilson (@dancwilson) September 16, 2014
It’s a cool effect particularly if used to make UX better and not to affix some dumb intrusive ad. Here’s the GIF I based the idea on. Little choppy, but the idea is there:

Two States
Like most good tricks, there isn’t much to it. All we do is think of (and design for) the two different possible states:
- Search bar in its scrollable position
- Search bar in its fixed header position
We toggle between them simply by changing a class name. There is no trickery with having two search forms that reveal themselves in different scenarios. That’s good, as we don’t want to smurf around with keeping those in sync. Much easier to just move a single one around.
State One

(I’m going to use SCSS here because the nesting is nice for managing states.)
.top-header {
position: fixed;
top: 0;
left: 0;
width: 320px;
height: 60px;
}
.search { /* Container just in case we want more than just the search input to come along */
position: absolute;
top: 155px;
left: 20px;
right: 20px;
input {
width: 265px;
transition: width 0.2s;
-webkit-appearance: none; /* Autoprefixer doesn't catch this */
}
}
.top {
height: 250px; /* Space in here for search */
padding-top: 40px;
position: relative;
}
State Two

Assuming we’ve put a class of “fix-search” on a parent element.
.top-header {
...
.fix-search & {
background: #eee;
}
}
.search { /* Container just in case we want more than just the search input to come along */
...
.fix-search & {
position: fixed;
top: 10px;
input {
width: 250px;
}
}
}
Switching States
The trick here is applying that class at just the right moment. In our little demo, we can just test for when that perfect moment would be and hard code that into some JavaScript watching for scrolling. jQuery style:
var wrap = $("#wrap");
wrap.on("scroll", function(e) {
if (this.scrollTop > 147) {
wrap.addClass("fix-search");
} else {
wrap.removeClass("fix-search");
}
});
That’s all it takes to switch between the two states we’ve set up. If the page has scrolled down 147 pixels or more, it will have that class applied. If not, it doesn’t. Even if you go down and come back up the class will go away (because this little function gets called on every scroll event).
Demo
See the Pen Search Box in Content Moves to Fixed Header by Chris Coyier (@chriscoyier) on CodePen.
Debouncing
In the grand tradition of mentioning scroll debouncing whenever any demo binds an event to a scroll event: you should consider debouncing when binding functions to scroll events, because if you don’t, it’ll get called a zillion times and could be slow.
CSS
This is the kind of thing that would be sweet to do in CSS alone. No great solutions pop to mind just yet, but I’m continually amazed by crazy things people use CSS to do, so if something comes along I’ll update this.
Perhaps someday we’ll be able to do scroll position media queries?
Fixed Position Support
Note that this demo relies on fixed positioning, which has a sketchy history on mobile. While I’m tempted to say it has “pretty good” support these days, you should make the judgement yourself. Some reading:
- Can I Use… on fixed positioning
- Fixed Positioning in Mobile Browsers by Brad Frost
- Issues with position fixed & scrolling on iOS by Remy Sharp
This is just one (not particularly reusable) example
There are a lot of magic numbers in this demo. Anytime you are setting heights there should be some warning flags happening up in your brain. It doesn’t mean never do it, it just means be warned. In this demo, if that centered image in the header changed height, it would look weird pretty much no matter what. This isn’t the most flexible and forgiving of layouts as it is. Even if you fixed the header to look right after a change, the JavaScript has corresponding magic numbers for when to change state.
Perhaps some version of using waypoints (or the concept of it) could make a more bulletproof system.
well… I feel unupdated.
Does javascript fires/triggers (don’t mown how you name it, but you’d get the idea) only when the scroll has ended on mobile ?
On iOS 7 and less yep, the event is only triggered at the end of the scroll. Not on Chrome for Android, and not on iOS 8 (have to test for the last one)
Pas vu passer :/. En tout cas très cool d’apprendre ça
Ceci dit son codepen marche nickel sur un ios7 installé pas plus tard que hier
Remy has a couple of good functions for debouncing and throttling, the latter is useful for resizing for example.
Here is the link
position: sticky will do it hopefully but support not good yet: http://caniuse.com/#feat=css-sticky
I was forced to use
position:sticky;
on a project recently, because the scroll event was fired once the scroll ended (https://developer.apple.com/library/safari/documentation/appleapplications/reference/SafariWebContent/HandlingEvents/HandlingEvents.html).Worked very well on iOS7, but not on iOS6.
Looks good. Here’s a fork that splits the resizing of the input width and the fixed position pieces so that the input “dodges” the menu button a bit better.
Three-State Version
I like it. Forked you: same idea, but with an intermediate
top
position so I could ease the transition to its fixed position. Likewise with the top bar background color.Great post Chris. One variation I noticed on the Android Chrome browser was that once the window is scrolled up at all, the search bar fades then jumps to the top. I might play with your code a bit to recreate this effect. Love the page!
To keep the code more compact you could replace
with
/Olaf
Chris sez:
I’d definitely love to see a CSS solution. There are lots of websites out there using scroll-detection Javascript for purely decorative effects that should be in CSS, or for performance goals like lazy loading.
A media-query approach would have to wait until element-level media queries happens. My preference would be a
:onscreen
pseudoclass. If the element is within the viewport, it has that pseudoclass, and styles are triggered. Before it scrolls on screen, before the page as a whole is on screen (e.g., for a web page opened in a hidden tab) or when the element scrolls out of view, those styles are skipped.For your sticky search bar, you would want to attach the pseudoclass to the scroll-away header section:
For lazy loading background images, you could likewise use a previous sibling coming into view, although you’d want to maintain the image whenever it was in view:
For an animation effect when something scrolls into view, you would just use the pseudoclass on that element:
(Perhaps the pseudoclass could have a parameter, similar to nth-of-type parameters, so that you could limit whether the effect happened every time the element came into view or only the first few times?)
And while we’re dreaming, how about a
scroll
option for CSS transitions, so that you don’t need complicated Javascript to make same-page links scroll nicely?Designers want these effects. Designers are creating these effects, with Javascript. Creating a declarative way to define these effects in CSS would allow browsers to optimize for them.
Well, i tried something for remove magic numbers, but this is not that simple.
Could you not eliminate the magic number by using
offset().top
…Also…is this not the same technique that we have seen with sticky navs…that fix themselves to the top of the screen once the user has scrolled down the page a bit…I don’t quite get how this is a big new thing…but I appreciate the post…very informative…
I would go ahead and assume anything on this site isn’t a “big new thing”.
This is a good overview of implementing a basic sticky effect. If you don’t want to write the code yourself, there are numerous plugins for this. I’ve had good success with the afore-mentioned Waypoints (which can be used as part of rolling your own, but also has an add-on to do this for you), as well as the sticky class in ZURB Foundation and the Sticky jQuery plugin, but a quick googling will undoubtedly turn up alternatives in all your favorite flavors.
Smurfing around is how some of the best design innovations have come into being. I hope that reference is never lost through time.
Unfortunately
position: sticky
isn’t a thing yet.This transistion is part of googles new design spec called “Material Design”. Chrome is one of the first android apps to support this. There is quite a few cool transitions and animations. It is also available for web components at polymer-project.org
For those that were talking about
position: sticky;
, here’s my fork of Chris’s pen using only CSS. Note that it only works in Firefox 32 which is the stable version at this time.I definitely prefer a version without magic numbers. A while back I made a pattern for this effect that I’ve used a few times now. The things I was solving for were: no magic numbers, the original element should be able to remain in the document flow, the page content shouldn’t jump when that original element is made a fixed position element, and whatever changes prevent the jump should be localized. When working on a big project, the more localized the style changes the better.
I do this by pairing the eventual sticky element with an “anchor” element in the markup. When the sticky element “sticks”, I read that element’s height and give it to the anchor to prevent the content jump. When unsticking, the anchor height goes back to 0 (or auto, since it should be empty).
Here’s an example:
I’ve only done it for sticking things to the top of the page so far but it seems it would be rather trivial to adapt it to stick to the bottom, left, or right.
Nice idea Chris, but have you tested it across all major browsers including different type of screen resolutions?
This feels like something that would not work properly if native momentum scroll kicks in. (I might be wrong. Haven’t tested it.)
So if my “touchend” (end of scroll) occures below the magic 147px (or whatever your number is) but the momentum scrolls the window all the way to the top the “fix-search”-class will not be removed.
Am I right with that? And if so does anyone have a solution for this?
Interesting post, is it tested on all browsers?
This is how I tackled the problem in removing the magic numbers. Rather than using a pre-defined height in the JQuery snippet, I used
window.innerHeight
along side of a intro div with the styling ofheight: 100vh;
. I put the example up on CodePen.See the Pen On Scroll Fix Content by David Schroeder (@simpleminded) on CodePen.
One issue with this though is I haven’t fully tested it be responsive. The problem using VH units is if you’re also making it 100% wide, it shrinks with the problem causing that box to also shrink down. You’d at that point have to use predefined numbers to make sure your content fits back inside of that box.
Awesome post! I’ve seen this on sites before and didn’t feel like trying it at the time because it seemed overly complicated. I think now’s the time to give it a try!