CSS Easing Is About To Get a Lot More Expressive
For years, CSS has offered a handful of easing functions — linear, steps, ease, ease-in, ease-out, ease-in-out, and cubic-bezier(). While cubic-bezier() gives some flexibility, there's a ceiling to what you can achieve. You cannot, for example, create a bounce or spring effect with a single cubic Bézier curve. Those are the kinds of natural-feeling motions that often require JavaScript libraries like GreenSock.
That's changing. The CSS Easing Level 2 specification, currently in Editor's Draft, introduces the linear() easing function. Pioneered by Jake Archibald, this function defines a piecewise linear interpolation between multiple points. In practice, it means you can plot a custom easing curve with as many points as you need, enabling effects that were previously impossible with pure CSS.
Browser support is still limited — Chrome and Firefox support it, and Safari has not yet shipped — but the function is stable enough to explore. MDN describes it well: linear() allows you to approximate more complex animations like bounce and elastic effects by interpolating linearly between a set of defined points.
How Cubic Bézier Falls Short
To understand why this matters, it helps to look at how easing has been handled so far. Tools like cubic-bezier.com and easings.net have made it easy to tweak curves, and Chromium DevTools lets you inspect the applied curve for any transition or animation.
But even with these tools, a cubic Bézier curve is a mathematical construct that cannot represent oscillations, overshoots, or multiple direction changes. For UI movements that mimic physical objects — think of a ball that bounces to rest or a panel that settles with a little overshoot — you need a more expressive format.
What linear() Brings to the Table
Instead of constraining you to a formula, linear() lets you specify the shape of the curve directly. You supply a comma-separated list of values that act like points on a graph. Between those points, the motion interpolates linearly, but with enough points you can approximate the curvature of virtually any easing function.
For example, the easing for a bounce could look like this:
:root {
--bounce-easing: linear(
0, 0.004, 0.016, 0.035, 0.063, 0.098, 0.141 13.6%, 0.25, 0.391, 0.563, 0.765,
1, 0.891 40.9%, 0.848, 0.813, 0.785, 0.766, 0.754, 0.75, 0.754, 0.766, 0.785,
0.813, 0.848, 0.891 68.2%, 1 72.7%, 0.973, 0.953, 0.941, 0.938, 0.941, 0.953,
0.973, 1, 0.988, 0.984, 0.988, 1
);
}
And here's that same easing in action:
See the Pen [Bounce Easing 🏀 [forked]](https://codepen.io/smashingmag/pen/KKbmeeQ) by Jhey.
This opens the door to easing that feels much more organic and unobtrusive. If you've ever read guidance on UI animation, you've likely encountered advice about motion needing to "feel right." Most of that advice is about matching real-world physics. With linear(), those principles become achievable in hand-written CSS without resorting to a JavaScript library.
Generating the Numbers, Not Writing Them
The list of values looks intimidating at first glance — much like cubic-bezier() did for many people. The good news is, you probably won't need to hand-craft these arrays. Archibald built a linear easing generator that takes an easing function (JavaScript or SVG path) and produces the CSS values for you. The bounce demo above used this tool directly.
Bringing GreenSock Eases to CSS
For developers who have come to rely on GreenSock for complex easing, there is a particularly appealing workflow. GreenSock's ease visualizer outputs JavaScript easing functions or SVG paths, which you can feed into the generator to get a CSS linear() equivalent.
One approach involved extracting the SVG paths from GreenSock's visualizer, scaling them down from a 0 0 500 500 viewBox to a normalized 0 0 1 1 coordinate space, and reversing direction. However, a simpler path emerged: GreenSock has a parseEase utility that returns the easing function from a string.
const ease = gsap.parseEase('power1.in')
ease(0.5) // === 0.25
By extracting and adapting parts of Archibald's generator, you can loop over a set of easing functions, process them into points, optimize those points, and output a CSS string for each easing. The key functions that came out of this:
processEase: A modified version ofprocessScriptDatathat takes easing functions and returns point sets.useOptimizedPoints: Reduces the number of points needed based on thesimpliedandroundedsettings, using a simplified Douglas-Peucker algorithm.useLinearSyntax: Converts the optimized points into the finallinear()values.useFriendlyLinearCode: Wraps those values in CSS with a custom property name.
const easings = ''
const simplified = 0.0025
const rounded = 4
const EASES = {
'power-1--out': gsap.parseEase('power1.out')
// Other eases
}
// Loop over the easing keys and generate results.
for (const key of Object.keys(EASES)) {
// Pass the easing function through the linear-generator code.
const result = processEase(key, EASES[key])
const optimised = useOptimizedPoints(result.points, simplified, rounded)
const linear = useLinearSyntax(optimised, rounded)
const output = useFriendlyLinearCode(linear, result.name, 0)
easings += output
}
// Generate an output CSS string.
let outputStart = ':root {'
let outputEnd = '}'
let styles = `
${outputStart}
${easings}
${outputEnd}
`
// Write it to the body.
document.body.innerHTML = styles
The output is a set of CSS variables holding the linear() functions. For instance, elastic and bounce eases would produce variables that can be dropped directly into any transition-timing-function or animation-timing-function declaration.
:root {
--elastic-in: linear( 0, 0.0019 13.34%, -0.0056 27.76%, -0.0012 31.86%, 0.0147 39.29%, 0.0161 42.46%, 0.0039 46.74%, -0.0416 54.3%, -0.046 57.29%, -0.0357, -0.0122 61.67%, 0.1176 69.29%, 0.1302 70.79%, 0.1306 72.16%, 0.1088 74.09%, 0.059 75.99%, -0.0317 78.19%, -0.3151 83.8%, -0.3643 85.52%, -0.3726, -0.3705 87.06%, -0.3463, -0.2959 89.3%, -0.1144 91.51%, 0.7822 97.9%, 1 );
--elastic-out: linear( 0, 0.2178 2.1%, 1.1144 8.49%, 1.2959 10.7%, 1.3463 11.81%, 1.3705 12.94%, 1.3726, 1.3643 14.48%, 1.3151 16.2%, 1.0317 21.81%, 0.941 24.01%, 0.8912 25.91%, 0.8694 27.84%, 0.8698 29.21%, 0.8824 30.71%, 1.0122 38.33%, 1.0357, 1.046 42.71%, 1.0416 45.7%, 0.9961 53.26%, 0.9839 57.54%, 0.9853 60.71%, 1.0012 68.14%, 1.0056 72.24%, 0.9981 86.66%, 1 );
--elastic-in-out: linear( 0, -0.0028 13.88%, 0.0081 21.23%, 0.002 23.37%, -0.0208 27.14%, -0.023 28.64%, -0.0178, -0.0061 30.83%, 0.0588 34.64%, 0.0651 35.39%, 0.0653 36.07%, 0.0514, 0.0184 38.3%, -0.1687 42.21%, -0.1857 43.04%, -0.181 43.8%, -0.1297 44.93%, -0.0201 46.08%, 1.0518 54.2%, 1.1471, 1.1853 56.48%, 1.1821 57.25%, 1.1573 58.11%, 0.9709 62%, 0.9458, 0.9347 63.92%, 0.9349 64.61%, 0.9412 65.36%, 1.0061 69.17%, 1.0178, 1.023 71.36%, 1.0208 72.86%, 0.998 76.63%, 0.9919 78.77%, 1.0028 86.12%, 1 );
--bounce-in: linear( 0, 0.0117, 0.0156, 0.0117, 0, 0.0273, 0.0468, 0.0586, 0.0625, 0.0586, 0.0468, 0.0273, 0 27.27%, 0.1093, 0.1875 36.36%, 0.2148, 0.2343, 0.2461, 0.25, 0.2461, 0.2344, 0.2148 52.28%, 0.1875 54.55%, 0.1095, 0, 0.2341, 0.4375, 0.6092, 0.75, 0.8593, 0.9375 90.91%, 0.9648, 0.9843, 0.9961, 1 );
--bounce-out: linear( 0, 0.0039, 0.0157, 0.0352, 0.0625 9.09%, 0.1407, 0.25, 0.3908, 0.5625, 0.7654, 1, 0.8907, 0.8125 45.45%, 0.7852, 0.7657, 0.7539, 0.75, 0.7539, 0.7657, 0.7852, 0.8125 63.64%, 0.8905, 1 72.73%, 0.9727, 0.9532, 0.9414, 0.9375, 0.9414, 0.9531, 0.9726, 1, 0.9883, 0.9844, 0.9883, 1 );
--bounce-in-out: linear( 0, 0.0078, 0, 0.0235, 0.0313, 0.0235, 0.0001 13.63%, 0.0549 15.92%, 0.0938, 0.1172, 0.125, 0.1172, 0.0939 27.26%, 0.0554 29.51%, 0.0003 31.82%, 0.2192, 0.3751 40.91%, 0.4332, 0.4734 45.8%, 0.4947 48.12%, 0.5027 51.35%, 0.5153 53.19%, 0.5437, 0.5868 57.58%, 0.6579, 0.7504 62.87%, 0.9999 68.19%, 0.9453, 0.9061, 0.8828, 0.875, 0.8828, 0.9063, 0.9451 84.08%, 0.9999 86.37%, 0.9765, 0.9688, 0.9765, 1, 0.9922, 1 );
}
Practical Tools and Fallbacks
For those who want to skip the engineering work, there's a ready-made tool that lets you pick an animation type, apply a linear() ease, and set speed. It also provides the compiled CSS and a fallback pattern.
See the Pen [GreenSock Easing with CSS linear() ⚡️ [forked]](https://codepen.io/smashingmag/pen/zYywLXB) by Jhey.
If a browser does not support linear(), you can provide fallback values via the @supports rule:
:root {
--ease: ease-in-out;
}
@supports(animation-timing-function: linear(0, 1)) {
:root {
--ease: var(--bounce-easing);
}
}
For experiments, another demo accepts GreenSock ease strings directly — try something like elastic.out(1, 0.1) — and returns the corresponding CSS linear() function.
See the Pen [Convert GSAP Ease to CSS linear() [forked]](https://codepen.io/smashingmag/pen/RwEVBmM) by Jhey.
Tailwind Support
The workflow works for Tailwind as well. Extending Tailwind with the new eases is just a matter of adding them to the configuration, which gives you utility classes such as animation-timing-bounce-out and ease-bounce-out.
/** @type {import('tailwindcss').Config} */
const plugin = require('tailwindcss/plugin')
const EASES = {
"power1-in": "linear( 0, 0.0039, 0.0156, 0.0352, 0.0625, 0.0977, 0.1407, 0.1914, 0.2499, 0.3164, 0.3906 62.5%, 0.5625, 0.7656, 1 )",
/* Other eases */
}
const twease = plugin(
function ({addUtilities, theme, e}) {
const values = theme('transitionTimingFunction')
var utilities = Object.entries(values).map(([key, value]) => {
return {
[`.${e(`animation-timing-${key}`)}`]: {animationTimingFunction: `${value}`},
}
})
addUtilities(utilities)
}
)
module.exports = {
theme: {
extend: {
transitionTimingFunction: {
...EASES,
}
},
},
plugins: [twease],
}
CSS is closing the gap between what designers expect from motion and what can be expressed without JavaScript. The linear() function is still maturing within the specification, but the capabilities it unlocks for natural-feeling UI movement are significant. Once you've defined an ease you like, it's effectively a set-and-forget asset — one that works across both animations and transitions.



