You know that feeling when you land on a site and something just feels different? You scroll down and the page moves with you instead of just jumping. It has weight. It has momentum. You scroll back up and it decelerates gently instead of stopping dead. You do not know exactly what it is, but you feel it. That is Lenis.

I added it to a Next.js project I was building and spent about five minutes just scrolling up and down the homepage like an idiot. It is one of those changes that sounds small until you actually feel it in the browser.

Why scroll-behavior: smooth is not enough

If you have heard of scroll-behavior: smooth you might be thinking this is already a solved problem. It is not. That property smooths anchor link jumps. It does nothing for your regular scroll, which is what people are actually doing when they read your site.

Regular scroll on most sites looks like this: instant, mechanical, one-to-one. You move your finger or your wheel and the page moves exactly that amount, immediately. It works. Nobody complains. But compared to a site running Lenis it feels like dragging a file across a desktop versus using an app with actual physics.

The difference is momentum and easing. Lenis intercepts the scroll event, calculates where the page should be based on the user's input, and then animates toward that position using a custom easing curve. The result is scroll that feels like it belongs to the site instead of the operating system.

What Lenis actually is

Lenis is an open source smooth scroll library built by Studio Freight. It is small (around 4kb gzipped), framework-agnostic, and built on top of requestAnimationFrame so it stays in sync with everything else that is animating on the page.

It works by intercepting native scroll events, disabling the default behavior, and replaying it with easing applied. The user still scrolls the same way. The page still moves in the same direction. It just feels better.

Installing it

code
npm install lenis

No peer dependencies. No configuration files. That is the whole installation.

Creating the provider

The cleanest pattern in Next.js App Router is a provider component that wraps the app. This gives you one place to configure Lenis and makes sure it gets cleaned up properly when the component unmounts.

Create components/LenisProvider.tsx:

code
'use client'
 
import { useEffect, useRef } from 'react'
import Lenis from 'lenis'
 
export default function LenisProvider({
  children
}: {
  children: React.ReactNode
}) {
  const lenisRef = useRef<Lenis | null>(null)
 
  useEffect(() => {
    const lenis = new Lenis({
      duration: 1.2,
      easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))
    })
    lenisRef.current = lenis
 
    function raf(time: number) {
      lenis.raf(time)
      requestAnimationFrame(raf)
    }
 
    requestAnimationFrame(raf)
 
    return () => {
      lenis.destroy()
    }
  }, [])
 
  return <>{children}</>
}

A few things to understand about this code before you paste it in and move on.

The duration: 1.2 is how long the scroll animation takes, in seconds. 1.2 feels natural for a content site. Go lower (0.8) if you want something snappier. Go higher (1.8) if you want something more cinematic for a portfolio.

The easing function is the shape of the animation curve. This specific formula starts fast and decelerates gradually, which is what makes the scroll feel like it has weight. You can swap it for any easing function you like.

The return () => { lenis.destroy() } is important. Without it you will get a memory leak if the component ever unmounts. Always clean up.

Adding it to layout.tsx

Open app/layout.tsx and wrap your children:

code
import LenisProvider from '@/components/LenisProvider'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <LenisProvider>
          {children}
        </LenisProvider>
      </body>
    </html>
  )
}

Reload. Scroll. Notice the difference.

Tuning it to your site

The default settings work well for most sites but here are the configurations I have tested across different types of projects:

code
// Snappy — good for dashboards and app-like interfaces
const lenis = new Lenis({
  duration: 0.8,
  easing: (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t)
})
 
// Natural — good for blogs and content sites
const lenis = new Lenis({
  duration: 1.2,
  easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))
})
 
// Cinematic — good for portfolios and creative sites
const lenis = new Lenis({
  duration: 1.8,
  easing: (t) => t < 0.5
    ? 4 * t * t * t
    : 1 - Math.pow(-2 * t + 2, 3) / 2
})

There is no right answer here. Open your site, try each one, pick the one that feels like it belongs.

Does it work with Framer Motion?

Yes, without any extra configuration. Lenis and Framer Motion both operate on requestAnimationFrame and they do not interfere with each other. If you are using whileInView or useScroll from Framer Motion, those will keep working exactly as they did before.

Does it affect performance?

Lenis only runs while the user is actively scrolling. When the page is idle it does nothing. On a typical blog with standard content it has no measurable impact on performance.

If you are building something with hundreds of DOM elements and heavy animations running at the same time, you would want to profile it. For most sites, it is not something you need to think about.

One thing to watch out for

Lenis disables native scroll by intercepting the events. This means if you have any code that directly manipulates window.scrollY or uses scrollTo() you need to use the Lenis API instead:

code
// Instead of window.scrollTo
lenis.scrollTo('#section-id')
 
// Or scroll to a specific position
lenis.scrollTo(500)

If you need access to the Lenis instance outside the provider, you can expose it through a ref or a context. That is a bigger topic for another post.

Worth it?

For a content site, yes. It is ten minutes of setup and the result is something visitors notice even if they cannot name it. There is a reason every well-designed portfolio and agency site uses some version of this. The scroll is the first thing people feel when they land on a page. Making it feel good is not a nice-to-have.

See it in action

I shipped this pattern on Thinqr, a Next.js site where Lenis runs alongside Framer Motion scroll reveals and section transitions. Open it on desktop, scroll slowly, and pay attention to how the page decelerates when you stop. That is the difference this post is about.