Timeline

2025-12-27

init


Found on Codepen, with some modifications. You can see the effect on this site’s aside cards and nav.

Implementation Approach

The main idea is to use multiple layers — four in total:

1
2
3
4
5
6
7
8
<div class="liquidGlass-wrapper">
<div class="liquidGlass-effect"></div> <!-- blur + distortion -->
<div class="liquidGlass-tint"></div> <!-- semi-transparent white overlay -->
<div class="liquidGlass-shine"></div> <!-- inner glow highlight -->
<div class="liquidGlass-box"> <!-- actual content container -->
<!-- content -->
</div>
</div>

liquidGlass-wrapper

Container shell

1
2
3
4
5
6
7
8
.liquidGlass-wrapper {
box-shadow: 0 6px 6px rgba(0, 0, 0, 0.2), 0 0 20px rgba(0, 0, 0, 0.1) !important;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 2.2) !important;
background: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
border-style: none !important;
}
  • Purpose: Outermost container, responsible for overall shadow and animation transitions.
  • Key points:
    • background: none and backdrop-filter: none prevent it from producing its own blur, delegating blur to the inner .liquidGlass-effect.
    • Shadows simulate the 3D feel of glass.
    • transition makes hover radius and padding changes smoother.

liquidGlass-effect

Core “liquid” blur layer

1
2
3
4
5
6
7
8
9
10
.liquidGlass-effect {
position: absolute;
z-index: 0;
inset: 0;

backdrop-filter: blur(3px);
filter: url(#glass-distortion);
overflow: hidden;
isolation: isolate;
}
  • backdrop-filter: blur(3px): Blurs the background content (e.g. other parts of the webpage) → this is the foundation of “frosted glass”.
  • filter: url(#glass-distortion)This is the key to the “liquid” feel! It references an SVG filter (#glass-distortion), typically defined in the page <svg>, used to apply distortion, ripple, liquid flow effects to the image.
  • isolation: isolate: Ensures this layer creates a new stacking context, preventing the filter from affecting other elements.

glass-distortion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<svg style="display: none">
<filter
id="glass-distortion"
x="0%"
y="0%"
width="100%"
height="100%"
filterUnits="objectBoundingBox"
>
<feTurbulence
type="fractalNoise"
baseFrequency="0.01 0.01"
numOctaves="1"
seed="5"
result="turbulence"
/>
<!-- Seeds: 14, 17, -->

<feComponentTransfer in="turbulence" result="mapped">
<feFuncR type="gamma" amplitude="1" exponent="10" offset="0.5" />
<feFuncG type="gamma" amplitude="0" exponent="1" offset="0" />
<feFuncB type="gamma" amplitude="0" exponent="1" offset="0.5" />
</feComponentTransfer>

<feGaussianBlur in="turbulence" stdDeviation="3" result="softMap" />

<feSpecularLighting
in="softMap"
surfaceScale="5"
specularConstant="1"
specularExponent="100"
lighting-color="white"
result="specLight"
>
<fePointLight x="-200" y="-200" z="300" />
</feSpecularLighting>

<feComposite
in="specLight"
operator="arithmetic"
k1="0"
k2="1"
k3="1"
k4="0"
result="litImage"
/>

<feDisplacementMap
in="SourceGraphic"
in2="softMap"
scale="150"
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</svg>

liquidGlass-tint

Color tint overlay

1
2
3
4
5
6
.liquidGlass-tint {
z-index: 1;
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.25);
}
  • Overlays a semi-transparent white layer on top of the blur layer (can also be light blue, light gray, etc.).
  • Simulates the “reflective base color” of real glass, making text/content more readable.
  • z-index: 1 ensures it sits above the effect layer and below the shine layer.

liquidGlass-shine

Highlight layer

1
2
3
4
5
6
7
8
9
10
.liquidGlass-shine {
position: absolute;
inset: 0;
z-index: 2;

overflow: hidden;

box-shadow: inset 2px 2px 1px 0 rgba(255, 255, 255, 0.5),
inset -1px -1px 1px 1px rgba(255, 255, 255, 0.5);
}
  • Uses inset box-shadow to simulate highlights and subtle reflections at the glass edges.
  • Top-right bright (+2px white light), bottom-left slightly darker (-1px white light) → creates a sense of directional lighting.
  • z-index: 2 placed at the top (but below content).

liquidGlass-box

Content layer

1
2
3
4
5
6
7

.liquidGlass-box {
z-index: 3;
position: relative;
box-shadow: none !important;
}

  • Where actual content goes (e.g. avatar, text).
  • z-index: 3 ensures content is above all glass effect layers, clearly visible.
  • position: relative takes it out of normal flow while staying within the wrapper.

Interaction Effect (Hover Dynamics)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* OTHER STYLES */

.liquidGlass-wrapper,
.liquidGlass-wrapper > div:not(.liquidGlass-box) {
padding: 0.4rem !important;
border-radius: var(--glass-border-radius) !important;
}

.liquidGlass-wrapper:hover {
padding: 0.6rem !important;
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}

.liquidGlass-wrapper > div:not(.liquidGlass-box):hover {
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}
  • On hover:
    • Border radius increases (+1em) → simulates “liquid expansion” or “soft deformation”;
    • Padding increases → overall “breathing” feel;
    • All non-content layers sync border radius changes, maintaining visual consistency.

Applying to Butterfly Examples

Below, we use card_author (the author info card) as an example.

card_author.pug

Modify themes\butterfly\layout\includes\widget\card_author.pug:

1
2
3
4
5
6
7
8
9
10
11
12
13
if theme.aside.card_author.enable
.card-widget.card-info.text-center.liquidGlass-wrapper
.liquidGlass-effect
.liquidGlass-tint
.liquidGlass-shine
.liquidGlass-box
div.card-info-avatar
.avatar-img
img(
src=url_for(theme.avatar.img)
onerror="this.onerror=null;this.src='" + url_for(theme.error_img.flink) + "'"
alt="avatar"
)

liquid_glass.css

Create a custom CSS file themes\butterfly\source\css\liquid_glass.css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/* LIQUID GLASS STYLES */
:root {
--glass-border-radius: 1.2rem; /* default border radius */
}

.liquidGlass-wrapper {
box-shadow: 0 6px 6px rgba(0, 0, 0, 0.2), 0 0 20px rgba(0, 0, 0, 0.1) !important;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 2.2) !important;
background: none !important;
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
border-style: none !important;
}

.liquidGlass-effect {
position: absolute;
z-index: 0;
inset: 0;

backdrop-filter: blur(3px);
filter: url(#glass-distortion);
overflow: hidden;
isolation: isolate;
}

.liquidGlass-tint {
z-index: 1;
position: absolute;
inset: 0;
background: rgba(255, 255, 255, 0.25);
}

.liquidGlass-shine {
position: absolute;
inset: 0;
z-index: 2;

overflow: hidden;

box-shadow: inset 2px 2px 1px 0 rgba(255, 255, 255, 0.5),
inset -1px -1px 1px 1px rgba(255, 255, 255, 0.5);
}

.liquidGlass-box {
z-index: 3;
position: relative;
box-shadow: none !important;
}

/* OTHER STYLES */

.liquidGlass-wrapper,
.liquidGlass-wrapper > div:not(.liquidGlass-box) {
padding: 0.4rem !important;
border-radius: var(--glass-border-radius) !important;
}

.liquidGlass-wrapper:hover {
padding: 0.6rem !important;
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}

.liquidGlass-wrapper > div:not(.liquidGlass-box):hover {
border-radius: calc(var(--glass-border-radius) + 1em) !important;
}

inject

Inject in the theme’s _config.yaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
inject:
head:
- <link rel="stylesheet" href="/css/liquid_glass.css">
bottom:
- |
<svg style="display: none">
<filter
id="glass-distortion"
x="0%"
y="0%"
width="100%"
height="100%"
filterUnits="objectBoundingBox"
>
<feTurbulence
type="fractalNoise"
baseFrequency="0.01 0.01"
numOctaves="1"
seed="5"
result="turbulence"
/>
<!-- Seeds: 14, 17, -->

<feComponentTransfer in="turbulence" result="mapped">
<feFuncR type="gamma" amplitude="1" exponent="10" offset="0.5" />
<feFuncG type="gamma" amplitude="0" exponent="1" offset="0" />
<feFuncB type="gamma" amplitude="0" exponent="1" offset="0.5" />
</feComponentTransfer>

<feGaussianBlur in="turbulence" stdDeviation="3" result="softMap" />

<feSpecularLighting
in="softMap"
surfaceScale="5"
specularConstant="1"
specularExponent="100"
lighting-color="white"
result="specLight"
>
<fePointLight x="-200" y="-200" z="300" />
</feSpecularLighting>

<feComposite
in="specLight"
operator="arithmetic"
k1="0"
k2="1"
k3="1"
k4="0"
result="litImage"
/>

<feDisplacementMap
in="SourceGraphic"
in2="softMap"
scale="150"
xChannelSelector="R"
yChannelSelector="G"
/>
</filter>
</svg>

References