scroll-behavior

Makes scrolling triggered by in-page links and similar actions smooth. Scrolling you used to animate step by step in JavaScript can be replaced with a CSS declaration.

Modern approach

Set it on html, and both in-page links and scrollTo() scroll smoothly. Since the browser handles the scroll, the URL’s # and :target update just as they normally do with a link.

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

Legacy approach

JavaScript stopped the click on an in-page link and moved the scroll position a little at a time. Because the click is stopped, the URL’s # doesn’t change and :target doesn’t match.

for (const link of document.querySelectorAll('a[href^="#"]')) {
  link.addEventListener('click', (event) => {
    const id = decodeURIComponent(link.hash.slice(1))
    const target = document.getElementById(id)
    if (!target) return
    event.preventDefault()
    const start = window.scrollY
    const distance = target.getBoundingClientRect().top
    const duration = 500
    let startTime
    const step = (time) => {
      if (startTime === undefined) startTime = time
      const progress = Math.min((time - startTime) / duration, 1)
      window.scrollTo(0, start + distance * progress)
      if (progress < 1) requestAnimationFrame(step)
    }
    requestAnimationFrame(step)
  })
}

Things to watch when replacing

  • Set it on html. Setting it on body has no effect on scrolling the whole page.
  • Only scrolling triggered by links, scrollTo(), and similar APIs becomes smooth. Scrolling with the mouse wheel or keyboard doesn’t change.
  • Existing scrollTo() and scrollIntoView() calls also become smooth, so the page hasn’t reached the destination right after the call. Any code that reads scrollY right after the call will behave differently.
  • Remove the old code when you replace it. If you leave it in, every per-frame scrollTo() call also becomes smooth, so the scroll lags and then jumps.
  • The browser decides how long the scroll takes and how fast it moves, so you can’t set them. If the old technique set a duration, the motion won’t be the same. The specification also allows browsers to ignore this property.
  • It’s wrapped in prefers-reduced-motion so that people who have turned on the OS setting to reduce motion jump to the destination instantly.
  • If the destination ends up hidden under a fixed header, set scroll-margin-top on the destination. If you subtracted the header height in JavaScript, that calculation can move to CSS too.

Fallback

Browsers without support simply jump to the destination instantly, so the links still work. All five target browsers support it, so you normally don’t need to keep the old technique. Only when smooth scrolling is essential even in older browsers, run the old code when the property isn’t supported.

if (
  !('scrollBehavior' in document.documentElement.style) &&
  matchMedia('(prefers-reduced-motion: no-preference)').matches
) {
  // The legacy code
}

Browser Support

  • Chrome

    v61+
    2017-09-05
  • Edge

    v79+
    2020-01-15
  • Safari

    v15.4+
    2022-03-14
  • Safari (iOS)

    v15.4+
    2022-03-14
  • Firefox

    v36+
    2015-02-24

Search by feature name or code

e.g. aspect-ratio, :has(), padding-top

Try a different word or a property name.

Could not load search.