Native Smooth Scroll with pure CSS and JS

Native Smooth Scroll with pure CSS and JS

Do you want a smooth scroll? Forget JQuery, we're past that. Let me introduce you to our tools for a native smooth scroll.

LinkCSS scroll-behavior

The CSS scroll-behavior property accepts one of three values – or two, actually, since one of those was deprecated.

  1. scroll-behavior: auto is the default instant scrolling behavior that we're already used to.
  2. scroll-behavior: instant is the same as auto, which is why it was deprecated. If you want it, just use auto.
  3. scroll-behavior: smooth applies a smooth transition when a scroll event is programmatically triggered.

I say "programmatically triggered" because it's not going to smooth scroll the mouse wheel.

Some ways of programmatically triggering a scroll event are through:

-    Window.scrollTo()
-    Window.scrollBy()
-    Element.scrollTo()
-    Element.scrollBy()
-    Element.scrollIntoView()
-    Element.scrollLeft = x
-    Element.scrollTop = y

We'll explore these methods individually.

Link(Note) Window.scroll() and Element.scroll()

Maybe you've noticed that I haven't mentioned the scroll() method.

That's because Window.scroll() and Element.scroll() are effectively the same methods as Window.scrollTo() and Element.scrollTo(). To avoid duplicate content, I'll just refer to scrollTo(). In practice, you can use either, just choose one and be consistent.

LinkWindow.scrollTo() and Element.scrollTo()

This method is ideal for scrolling to absolute coordinates. If you have the x and y coordinates for where you want to scroll the user to, you can simply call window.scrollTo(x, y) and it'll respect the CSS scroll-behavior of the page.

The same applies to scrollable elements. You simply call element.scrollTo(x, y) and it'll respect the CSS scroll-behavior of the element.

There's also a new signature for this method, which uses an object instead of two numeric arguments, and with this new signature, we can explicitly set our scroll behavior.

// For window
  left: x,
  top: y,
  behavior: 'smooth'

// For element
const el = document.querySelector(...);
  left: x,
  top: y,
  behavior: 'smooth'

LinkElement.scrollLeft and Element.scrollTop

Setting the element .scrollLeft and .scrollTop properties is the same as calling Element.scrollTo() with the coordinates. It'll respect the CSS scroll-behavior of the element.

const el = document.querySelector(...);
const x = 100;
const y = 500;

// Setting .scrollLeft and .scrollTop with smooth scroll = 'smooth';
el.scrollLeft = x;
el.scrollTop = y;

// Is the same as calling Element.scrollTo()
el.scrollTo({ left: x, top: y, behavior: 'smooth' });

Link(Note) Negative Element.scrollLeft

If the direction of your element's text is rtl, scrollLeft = 0 marks the rightmost position of the horizontal scroll, and the value decreases as you go to the left.

For a scrollable element with 100px of width, 500px of scrollable width and direction rtl, the leftmost position is scrollLeft = -400.

<div id="scrollable" style="width: 100px; overflow: auto" dir="rtl">
  <div style="width: 500px; height: 100px; background: green"></div>
<p id="output"></p>

const scrollable = document.querySelector('#scrollable');
const output = document.querySelector('#output');

const updateOutput = () => {
  output.textContent = `scrollLeft: ${scrollable.scrollLeft}`;

scrollable.addEventListener('scroll', updateOutput);

LinkWindow.scrollBy() and Element.scrollBy()

This method has the exact same signatures as Window.scrollTo() or Element.scrollTo(). It accepts either x and y as two numeric arguments or a single argument being an object with the optional left, top, and behavior properties.

The difference here is that we're not passing absolute coordinates, but relative values instead. If we scrollBy({ top: 10 }), we're scrolling 10 pixels down from where we currently are, not 10 pixels down from the beginning of the page.

// For window
window.scrollBy({ top: 10 }); // Scroll 10px down
window.scrollBy({ left: 20 }); // Then 20px to the right
window.scrollBy({ top: 50 }); // And then 50px down

// For element
const el = document.querySelector(...);
el.scrollBy({ top: 10 }); // Scroll 10px down
el.scrollBy({ left: 20 }); // Then 20px to the right
el.scrollBy({ top: 50 }); // And then 50px down

Link(Note) Window.scrollByLines() and Window.scrollByPages()

Firefox goes a little further by implementing methods to scroll by a number of lines or pages. It's a non-standard feature that only works on Firefox, so you probably don't want to use it in production.

You can achieve a similar effect in all major browsers by converting 100vh (for a page) and 1rem (for a line) to pixels and passing that value to Window.scrollBy().

const toPixels = require('to-px'); // From NPM

const page = toPixels('100vh');
window.scrollBy(0, page); // window.scrolByPages(1)
const line = toPixels('1rem');
window.scrollBy(0, line); // window.scrolByLines(1)


Most of the time though, we don't care about any hardcoded coordinates, we just want to scroll the user to a specific element on the screen. This can easily (and more explicitly) be done with Element.scrollIntoView().

This method can be called with no arguments, that'll scroll the page (respecting the CSS scroll-behavior) until the element is aligned at the top (unless the element is at the bottom of the page, in that case, it'll scroll as much as possible).

<div style="height: 2000px">Some space</div>
<div id="target" style="background: green">Our element</div>
<div style="height: 500px">More space</div>

const target = document.querySelector('#target');

You can further customize the element's placement in the view by passing a boolean or an object.

const el = document.querySelector(...);

// Default, aligns at the top

// Aligns at the bottom

// Aligns vertically at the center
el.scrollIntoView({ block: 'center' });

// Vertical top and horizontal center
el.scrollIntoView({ block: 'start', inline: 'center' });

// Vertical bottom and horizontal right
el.scrollIntoView({ block: 'end', inline: 'end' });

// Vertical center and smooth
el.scrollIntoView({ block: 'center', behavior: 'smooth' });

// Vertical nearest
el.scrollIntoView({ block: 'nearest' });

When using an object to define the element's placement, note that block refers to the vertical placement and inline refers to the horizontal placement. Also, the 'nearest' placement can be the top/left or the bottom/right, whichever is nearest, and it can also be nothing if the element is already in view.

LinkBrowser Support

{Regarding browser support} As of this writing, all major browsers – except Safari – support smooth scroll and the scrolling methods described in this article.

If you need more reassurance, there's a very good smooth scroll polyfill that I use in my projects. It polyfills scroll(), scrollTo(), scrollBy(), scrollIntoView() and the CSS scroll-behavior. It does not support smooth scrolling by setting scrollLeft/scrollTop and it does not support the scrollIntoView() options (it always aligns the element at the top).

If you have more curiosity, I highly suggest studying the polyfill's source code, it's less than 500 lines in total. I made a PR to slightly improve the docs, you might find the code easier to understand with it.


References for everything I've said and an article with demos for you to play with are in the references below.

Have a great day and see you soon.


  1. CSS scroll-behavior: smooth - Mozilla Developer Network
  2. Element.scrollTop- Mozilla Developer Network
  3. Element.scrollLeft - Mozilla Developer Network
  4. Smooth scroll polyfill - GitHub iamdustan/smoothscroll
  5. Smooth scroll polyfill supported methods - I am Dustan
  6. scroll-behavior: instant renamed to auto - CSS Working Group
  7. block and inline options for scrollIntoView() - Stack Overflow
  8. Smooth scroll browser support - Can I Use
  9. CSS specification for scroll-behavior
  10. Smooth scroll proposal for scrollTo()

Join our Newsletter and be the first to know when I launch a course, post a video or write an article.

This field is required
This field is required