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
npm install lenisNo 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:
'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:
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:
// 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:
// 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.

