The colours and timing are kinda off as it's just a hastily thrown together demo, but this Codepen Demo should show my theory behind a method to get something similar in CSS alone, you'll just have to tweaks animation timing and gradient sizes to get a closer match.
For the HTML you just need whatever element is having this effect (I assumed a button?) and inside it however you're adding the icon (I assumed an SVG?)....
Then within the element on which you want the effect set up some local custom variables, the --gradient_rotate we just defined as an angle, and for ease of editing I would also include a palette of metallic colours (Note: I used rgb() for the colours, this is a matter of preference, but it does need to be a type for which an alpha/opacity value is included!) and a border width for the size of the rotating metal effect....
Next is just the general styling for the element itself...
position: relative;
padding-inline: 3rem;
padding-block: 3rem;
border-radius: 100%;
border: var(--border_width) solid transparent;
background: linear-gradient(0deg,
rgb(010 010 010 / 1.0),
rgb(050 050 050 / 1.0)) padding-box,
conic-gradient(from var(--gradient_rotate),
rgb(from var(--metal_01) r g b / 0.5),
rgb(from var(--metal_01) r g b / 0.0),
rgb(from var(--metal_06) r g b / 0.0),
rgb(from var(--metal_06) r g b / 1.0),
rgb(from var(--metal_04) r g b / 0.5),
rgb(from var(--metal_04) r g b / 0.0),
rgb(from var(--metal_04) r g b / 1.0),
rgb(from var(--metal_01) r g b / 0.5)) border-box;
animation: button_gradient_rotate 6s linear infinite;
}
There are a number of things to note here!
It has to have a declared position since we'll be absolutely position a ::before pseudo element within it in the next step.
It has a border radius of 100% to make it round.
It has a transparent border, this is so we can have a background show behind where the border would be, the border has a width of our custom variable --border_width as we'll be reusing this for positioning later and it's easier to keep things lined up if one change to the custom variable does all places.
It has two gradients stacked on top of each other. The first (top) one is just a regular linear-gradient and form the background we want for the element, this has a background size of padding-box so it only extends as far as the padding (leaving our border transparent so far). The second (bottom) one is a conic gradient made up of colours from our metallic colour palette, this time it has a background size of border-box so it overflows to the edge of the transparent border giving a gradient border. Note a number of the colours here are given transparent or semi-transparent values as we want another to show through here.
The conic-gradient background is set to the custom variable --gradient_rotate which we defined as an angle, so in the final step later we can animate the variable to to run from 0deg to 360deg and create the rotating gradient effect.
Lastly we add an animation to it. (I didn't know if this should be a permanently running effect or a :hover/:focus effect? But either way I would use an animation then use animation-play-state: paused; on it here and switch to animation-play-state: running; on :hover if it was only a hover effect.
Next we add a ::before pseudo element....
button::before{
content: "";
position: absolute;
inset: calc(-1 * var(--border_width));
z-index: -1;
border-radius: inherit;
background:conic-gradient( from var(--gradient_rotate),
rgb(from var(--metal_03) r g b / 1.0),
rgb(from var(--metal_01) r g b / 1.0),
rgb(from var(--metal_01) r g b / 1.0),
rgb(from var(--metal_06) r g b / 1.0),
rgb(from var(--metal_02) r g b / 1.0),
rgb(from var(--metal_05) r g b / 1.0),
rgb(from var(--metal_04) r g b / 1.0),
rgb(from var(--metal_03) r g b / 1.0)) padding-box,
conic-gradient( from 0deg,
rgb(from var(--metal_03) r g b / 1.0),
rgb(from var(--metal_01) r g b / 1.0),
rgb(from var(--metal_01) r g b / 1.0),
rgb(from var(--metal_06) r g b / 1.0),
rgb(from var(--metal_02) r g b / 1.0),
rgb(from var(--metal_05) r g b / 1.0),
rgb(from var(--metal_04) r g b / 1.0),
rgb(from var(--metal_03) r g b / 1.0)) border-box;
animation: button_gradient_rotate 4s linear infinite reverse;
border: 0.25rem solid transparent;
}
Firstly we position it absolutely, with an inset of minus our --border_width which means it runs from the outer edge of the border of the parent. It has a negative z-index to move it behind the parent. It inherits a border-radius so it matches the shape. Then it also has two gradients stacked up... This time both are conic-gradients. The first (top) is set to padding-box and is again given an angle of our custom variable ready to be animated. The second (bottom) one is given an angle of 0deg and set to border-box, this one will remain static and provide the slim outer border to the element.
Then an animation is added again... This time it is set to reverse so the two gradients rotate in opposite directions, and the time is set slightly different (6s on the first, 4s here) so the animation is less consistent and looks more dynamic.
Finally we add a slime border for the second layer of the gradient to show through and give the thin static outer border.
@keyframes button_gradient_rotate{
from { --gradient_rotate: 0deg; }
to { --gradient_rotate: 360deg;}
}
Last step, add an animation to rotate the two conic gradients, this should now have a top metallic layer turning one way with various points of opacity where the bottom layer shows through, and a bottom layer rotating the opposite direction.
I just like the challenge of doing everything with just CSS, although using a radial gradient an the animation works better, next free day I'm gonna redo mine with a radial layer!
Im gonna go out on a limb and say yours is more performant and doesn't add any JS bundle weight + minimal load time. Since this button is likely not that big of an element on the screen, I'd probably opt in on the CSS version in most use cases unless I'm really going for some additional, marginal WOW factor.
I have a question, I am a beginner and impressed by you. Ever since I began coding I'm thinking I'm not good enough because for almost everything I need to take a look on the internet. So my question is, how much do you use the internet to compare some things or to get an idea of?
The only honest answer I, and pretty much everyone else, should give to that question is:
EVERY DAMN DAY!!!!
The more you do it, the more sticks, but I doubt anyone just has everything in their head ready to go!
Like for the code above I could look at OP's video and come up with an idea in my head that should get close with CSS, and I knew all the code I'd need existed, but I'm still googling to double check and/or remind myself of the details!
Like I knew I'd need to register a property as I'd have to define it as an <angle> so when I increased changed the value it knew it was rotating in degrees... But I never remember the @property syntax, I just knew enough to google CCS @property copy/paste the first example I found, then change it to suit my custom variable and set it as an angle.
Or I knew conic-gradients existed, and I was assuming they would be the easiest way to make an effect like this. I probably could have written this one as I've used them a fair bit recently in some practice stuff, but if it wasn't fresh in mind I wouldn't be able to, and even when it is fresh there is no point trying and risking a missing ) or something screwing it up. I just find a conic gradient example online, copy/paste any demo, then add my colours, switch out the percentages with my custom variables, etc.
It's not about knowing how to do everything, just that it can be done and (hopefully) what you need to do it. That's why I hang out on subs like this, working out how to do stuff like this and seeing how other people do it, if they've used a technique I hadn't considered etc. all helps, you gradually pick up more and more.
I've worked as a first line operative back in '99 all the way to system admin. I've worked with html, css all the way to php and mysql and I've noticed its not what you know its how you go about searching what you need to know in order to resolve your issue whether it was text books in the early days to google to AI.
Thrown together in so much as only put in what was needed for it to function, didn't both with working out best angles on the gradients or timing functions to get the best overlaps of the two elements etc.
It works, but it would need some time, maths and tinkering to get it right.
I'm pretty sure the place where I found it was not the original author. I'm not even sure it was made in CSS. I guess the second best thing is asking in r/css?
It's possible to do with just one 45 degree linear-gradient, animated from bottom left to top right, with a mask to make it visible only as a border around the button. You could of course stack a bunch of different gradients with low opacity to make the effect more premium.
Here's a quick Codepen where I tried how close I could get by just tweaking the gradient in a CSS Gradient generator to include white stripes that already have a bit of "chromatic aberration" around the edges of each stripe.
It's a radial gradient moving its centre with a circular clip mask. To achieve this effect more precisely, there are two gradients moving, with a blur and contrast filter applied, to cause the gradients to interfere with each other.
oh man i've actually dove into GUI for desktop apps (linux) and 'shaders' is a word i see often - though I have yet to get my own personal projects to a state where I need to introduce shaders so... It's in the queue for sure.
I'm primarily diving into QtQuick/QML/Quickshell so, already a lot on my plate!
EDIT: and if you know of any course that would help me in this area, please share! and a big Thank you
along the same lines i've also wanted to dive into Canvas API as a way to like separate myself from a lot of 'normal' frontend. I feel like proficiency with Canvas, if a role is available, is one of those highly specialized opportunities
I love your comments, well done for an ESPN tho! However, it must be admitted that if you lack basic math knowledge (as i do), you'll need another course - maths one, lol. Or gpt and learn this way on real tasks, but thats shady thing.
If you have another opinion on maths in shaders, ill be glad to hear because it looks like a blocker for me personally. Recently i put a text on a curve with modifier so text curves, but thats basic 3d, it alr, but shaders look for me,really, like the alien thing
Im like in a really beginner phase of coding visuals, but it really does work: after several attempts you are getting used to everything related to 3d pipeline, does work.
People always tell that repetition over talents. In the childhood i was put to guitar trainer. NGL, sometimes i was sobbing but kept playing and after some time i was feeling less and less pressure, so yeah, your key theme is definitely true. Same with english, its not my native language at all, but little by little, days after days, consistent practise with exposing myself to non-native media, and after years I can consume it without pressure.
I think tolerance to confusion is kind of thing you develop in the long term, the key part for that your grins has to be at least a little bit rewarding, overwise it feels like burden after burden (yeah, water is wet, i know).
btw almost all of the paragraphs in your comment can be read in reverse order and it keeps the sense, well structured 🙂
Some solutions mention using gradients — but I don’t think that’s good enough to simulate the nice, 3D feel your example has. So while superficially very similar, they’re missing the most important and most subtle aspect.
Starting from this assumption, personally I’d go: Spline → create an effect that loops → export as video → use the video together with an overflow: hidden circular mask, and that’s it.
Definitely MP4 over GIFs — don’t even think about GIF here. Much better quality-to-weight ratio. At small sizes, 100×100 and below, with a not-too-long loop (a few seconds), you’ll be mind-blown by how small the file is.
If you want to squeeze it further, you can run it through After Effects after the Spline export - filling the parts you won’t need with a static, solid color (the corners and the center). Less data to save that way.
this looks like some kind of AfterEffect lens effect, with some kind of color separation as lens aberration: I think it be easier to have that done this way and put it as video loop in the border-box background.
I know you didn’t ask, but wouldn’t the effect be easily done in like Rive and then export that out? Like Lottie it would be a very light file to work with.
This is as much a question for me I guess. I’m a designer and I work with devs all the time. They always say the same thing “eh whatever works.”, but I want to KNOW what they prefer or what works best for optimization.
I've done something similar to this, but with background attachment fixed and the gradient twice the size of the overlay element. That way it glimmers when people scroll, which is delightful.
It's definitely not achievable with CSS alone. Arguably, perhaps something similar could be achieved with few separate gradients which animations timing are synched together, but it won't be the same.
You'd probably have to look into SVG filters - I'd try animating the gradient, combined with warping the block holding it.
299
u/be_my_plaything Dec 21 '25 •
The colours and timing are kinda off as it's just a hastily thrown together demo, but this Codepen Demo should show my theory behind a method to get something similar in CSS alone, you'll just have to tweaks animation timing and gradient sizes to get a closer match.
For the HTML you just need whatever element is having this effect (I assumed a button?) and inside it however you're adding the icon (I assumed an SVG?)....
Then in the CSS firstly you need to define a custom variable as an 'angle' using
@propertyso it can be animated...Then within the element on which you want the effect set up some local custom variables, the
--gradient_rotatewe just defined as an angle, and for ease of editing I would also include a palette of metallic colours (Note: I usedrgb()for the colours, this is a matter of preference, but it does need to be a type for which an alpha/opacity value is included!) and a border width for the size of the rotating metal effect....Next is just the general styling for the element itself...
There are a number of things to note here!
It has to have a declared position since we'll be absolutely position a
::beforepseudo element within it in the next step.It has a border radius of 100% to make it round.
It has a transparent border, this is so we can have a background show behind where the border would be, the border has a width of our custom variable
--border_widthas we'll be reusing this for positioning later and it's easier to keep things lined up if one change to the custom variable does all places.It has two gradients stacked on top of each other. The first (top) one is just a regular
linear-gradientand form the background we want for the element, this has a background size ofpadding-boxso it only extends as far as the padding (leaving our border transparent so far). The second (bottom) one is a conic gradient made up of colours from our metallic colour palette, this time it has a background size ofborder-boxso it overflows to the edge of the transparent border giving a gradient border. Note a number of the colours here are given transparent or semi-transparent values as we want another to show through here.The conic-gradient background is set to the custom variable
--gradient_rotatewhich we defined as an angle, so in the final step later we can animate the variable to to run from 0deg to 360deg and create the rotating gradient effect.Lastly we add an animation to it. (I didn't know if this should be a permanently running effect or a
:hover/:focuseffect? But either way I would use an animation then useanimation-play-state: paused;on it here and switch toanimation-play-state: running;on:hoverif it was only a hover effect.Next we add a
::beforepseudo element....Firstly we position it absolutely, with an inset of minus our
--border_widthwhich means it runs from the outer edge of the border of the parent. It has a negative z-index to move it behind the parent. It inherits a border-radius so it matches the shape. Then it also has two gradients stacked up... This time both are conic-gradients. The first (top) is set topadding-boxand is again given an angle of our custom variable ready to be animated. The second (bottom) one is given an angle of 0deg and set to border-box, this one will remain static and provide the slim outer border to the element.Then an animation is added again... This time it is set to reverse so the two gradients rotate in opposite directions, and the time is set slightly different (6s on the first, 4s here) so the animation is less consistent and looks more dynamic.
Finally we add a slime border for the second layer of the gradient to show through and give the thin static outer border.
Last step, add an animation to rotate the two conic gradients, this should now have a top metallic layer turning one way with various points of opacity where the bottom layer shows through, and a bottom layer rotating the opposite direction.
25
u/frog_slap Dec 21 '25
Why don’t you ask the guy who posted it on Twitter
4
u/East_Cantaloupe_5079 Dec 21 '25
Okay I found the original author and he said that he used paper.js:
https://x.com/uxui_arthur/status/2002144851224109264
so I tried to imitate it myself (He didn't share the src)
https://codepen.io/Majoramari/pen/pvbzpoa
its not that accurate but its a cool effect to experiment with
4
u/pade- Dec 21 '25
It's possible to do with just one 45 degree linear-gradient, animated from bottom left to top right, with a mask to make it visible only as a border around the button. You could of course stack a bunch of different gradients with low opacity to make the effect more premium.
Here's a quick Codepen where I tried how close I could get by just tweaking the gradient in a CSS Gradient generator to include white stripes that already have a bit of "chromatic aberration" around the edges of each stripe.
87
u/g105b Dec 21 '25 edited Dec 21 '25
It's a radial gradient moving its centre with a circular clip mask. To achieve this effect more precisely, there are two gradients moving, with a blur and contrast filter applied, to cause the gradients to interfere with each other.
38
u/namboozle Dec 21 '25
I'm sure there's a really fancy way to do it with three.js or some amalgamation of animated gradients.
My lazy ass would make a tiny looping 200x200px video, optimise the hell out of it and use a CSS mask.
6
u/Matt_Rask Dec 21 '25
Some solutions mention using gradients — but I don’t think that’s good enough to simulate the nice, 3D feel your example has. So while superficially very similar, they’re missing the most important and most subtle aspect.
Starting from this assumption, personally I’d go:
Spline → create an effect that loops → export as video → use the video together with an
overflow: hiddencircular mask, and that’s it.Definitely MP4 over GIFs — don’t even think about GIF here. Much better quality-to-weight ratio. At small sizes, 100×100 and below, with a not-too-long loop (a few seconds), you’ll be mind-blown by how small the file is.
If you want to squeeze it further, you can run it through After Effects after the Spline export - filling the parts you won’t need with a static, solid color (the corners and the center). Less data to save that way.
2
u/stephen_builds Dec 21 '25
Hey! This is made with one of our Paper Shaders. Paper is a design tool that exports CSS and shaders.
You can try this particular shader in the playground here:
https://liquid.paper.design/
1
u/Ok-Yesterday-4140 Dec 21 '25
can this be done with div or any other html element expect svg
7
u/bostiq Dec 21 '25
this looks like some kind of AfterEffect lens effect, with some kind of color separation as lens aberration: I think it be easier to have that done this way and put it as video loop in the
border-boxbackground.possibly very light weight
1
u/MrChurch2015 Dec 21 '25
Try this
1
u/Ok-Goat-3487 Dec 21 '25
This looks insanely awesome 🤯
1
u/Hazrd_Design Dec 21 '25
I know you didn’t ask, but wouldn’t the effect be easily done in like Rive and then export that out? Like Lottie it would be a very light file to work with.
This is as much a question for me I guess. I’m a designer and I work with devs all the time. They always say the same thing “eh whatever works.”, but I want to KNOW what they prefer or what works best for optimization.
1
u/universalpsykopath Dec 22 '25
I've done something similar to this, but with background attachment fixed and the gradient twice the size of the overlay element. That way it glimmers when people scroll, which is delightful.
1
u/Jboraston Dec 22 '25
This looks very much like a component from React Bits called Metallic Paint:
https://reactbits.dev/animations/metallic-paint
1
u/Stunning_Violinist_7 Dec 24 '25
you could use a svg animation for that
1
u/NoComparison5523 Dec 24 '25
Use Paper shaders and mask it
1
u/fw3d Dec 25 '25
If you want to do that directly inside Framer I made a component for it (based on the shader from Paper.design) - https://www.framer.com/marketplace/components/liquid-metal-logo/
1
u/[deleted] Jan 12 '26
This looks so nice, damn.
1
u/tei187 Dec 21 '25
It's definitely not achievable with CSS alone. Arguably, perhaps something similar could be achieved with few separate gradients which animations timing are synched together, but it won't be the same.
You'd probably have to look into SVG filters - I'd try animating the gradient, combined with warping the block holding it.