Skip to content

Instantly share code, notes, and snippets.

@tai-fukaya
Last active September 18, 2025 08:30
Show Gist options
  • Select an option

  • Save tai-fukaya/fc1a0248f7995491a487471d3858f32b to your computer and use it in GitHub Desktop.

Select an option

Save tai-fukaya/fc1a0248f7995491a487471d3858f32b to your computer and use it in GitHub Desktop.
Liquid Glass React
'use client'
import { PropsWithChildren, useEffect, useId, useRef, useState } from 'react'
import styles from './JheyLiquidButton.module.scss'
const generateSourceImage = (width: number, height: number, radius: number) => {
const svg = `
<svg
viewBox="0 0 ${width} ${height}"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
<linearGradient id="red" x1="100%" y1="0%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#000"></stop>
<stop offset="100%" stop-color="red"></stop>
</linearGradient>
<linearGradient id="blue" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#000"></stop>
<stop offset="100%" stop-color="blue"></stop>
</linearGradient>
</defs>
<rect x="0" y="0" width="${width}" height="${height}" fill="black"></rect>
<rect
x="0"
y="0"
width="${width}"
height="${height}"
rx="${radius}"
fill="url(#red)"
></rect>
<rect
x="0"
y="0"
width="${width}"
height="${height}"
rx="${radius}"
fill="url(#blue)"
style="mix-blend-mode: exclusion"
></rect>
<rect
x="0"
y="0"
width="${width}"
height="${height}"
rx="${radius}"
fill="hsl(0 0% 50% / 0.6)"
style="filter: blur(5px)"
></rect>
</svg>`
return svg
}
const JheyLiquidButton = ({ children }: PropsWithChildren) => {
const svgId = useId()
const ref = useRef<HTMLButtonElement>(null)
const [sourceImage, setSourceImage] = useState<string>(
generateSourceImage(100, 100, 0)
)
useEffect(() => {
const el = ref.current
if (!el) return
const sourceImage = generateSourceImage(
el.clientWidth,
el.clientHeight,
Math.min(el.clientWidth, el.clientHeight) / 2
)
setSourceImage(sourceImage)
}, [ref])
return (
<button className={styles.button} ref={ref}>
<div
className={styles.liquidGlassEffect}
style={{
backdropFilter: `url(#${svgId})`,
}}
/>
<div className={styles.liquidGlassTint} />
<div className={styles.liquidGlassShine} />
<div className={styles.buttonInner}>{children}</div>
<svg className={styles.filterSvg} xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id={svgId} color-interpolation-filters="sRGB">
<feImage
x="0"
y="0"
width="100%"
height="100%"
result="map"
preserveAspectRatio="none"
href={`data:image/svg+xml,${encodeURIComponent(sourceImage)}`}
></feImage>
<feDisplacementMap
in="SourceGraphic"
in2="map"
id="redchannel"
xChannelSelector="R"
yChannelSelector="B"
result="dispRed"
scale="-40"
></feDisplacementMap>
<feColorMatrix
in="dispRed"
type="matrix"
values="1 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 1 0"
result="red"
></feColorMatrix>
<feDisplacementMap
in="SourceGraphic"
in2="map"
id="greenchannel"
xChannelSelector="R"
yChannelSelector="B"
result="dispGreen"
scale="-42"
></feDisplacementMap>
<feColorMatrix
in="dispGreen"
type="matrix"
values="0 0 0 0 0
0 1 0 0 0
0 0 0 0 0
0 0 0 1 0"
result="green"
></feColorMatrix>
<feDisplacementMap
in="SourceGraphic"
in2="map"
id="bluechannel"
xChannelSelector="R"
yChannelSelector="B"
result="dispBlue"
scale="-44"
></feDisplacementMap>
<feColorMatrix
in="dispBlue"
type="matrix"
values="0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 1 0"
result="blue"
></feColorMatrix>
<feBlend in="red" in2="green" mode="screen" result="rg"></feBlend>
<feBlend in="rg" in2="blue" mode="screen" result="output"></feBlend>
<feGaussianBlur in="output" stdDeviation="0.2"></feGaussianBlur>
</filter>
</defs>
</svg>
{/* <div
className={styles.debugSvg}
dangerouslySetInnerHTML={{ __html: sourceImage }}
></div> */}
</button>
)
}
export default JheyLiquidButton
.liquidGlassWrapper {
position: relative;
display: flex;
/* overflow: hidden; */
font-weight: 600;
cursor: pointer;
box-shadow: 0 6px 6px rgba(0, 0, 0, 0.2), 0 0 20px rgba(0, 0, 0, 0.1);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 2.2);
.liquidGlassEffect {
position: absolute;
inset: 0;
z-index: 0;
overflow: hidden;
isolation: isolate;
}
.liquidGlassTint {
position: absolute;
inset: 0;
z-index: 1;
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(3px) saturate(120%);
}
.liquidGlassShine {
position: absolute;
inset: 0;
z-index: 2;
overflow: hidden;
box-shadow: inset 1px 1px 0.5px 0 rgba(255, 255, 255, 0.5),
inset -0.5px -0.5px 0.5px 0.5px rgba(255, 255, 255, 0.5);
}
.debugSvg {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: auto;
transform: translateY(110%);
}
.filterSvg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
touch-action: none;
}
}
.button {
@extend .liquidGlassWrapper;
width: 100%;
padding: 16px;
border-radius: 100px;
> div {
border-radius: 100px;
}
.buttonInner {
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment