Liquid search · matches light up with glass that flows between them
No. 07The Surface TensionAutumn 2026
Water doesn't like edges, and neither should glass.
Bring two drops of water close and the surface between them thins, bends and gives way. The new glass behaves the same: a button peels off its bar, and melts back in.
Light bends where glass is curved and passes straight where it is flat. That is all a lens is: a little water, a little light, and an edge that refuses to be sharp.
Under the glass is still the page itself — live, selectable text, bent by a few hundred lines of optics.
Search the article
Each match gets a <Vaso>, and one <VasoGroup> unions them, so neighbours melt together. Positions are plain CSS transforms with a transition; the group follows its members every frame. The bar is two <Vaso> trading widths the same way.
Code
search.tsx
import { Vaso, VasoGroup } from'vaso'// One glass per match; the group merges the ones that touch<VasoGroupsmooth={14} depth={0.9} style={{ position: 'absolute', inset: 0 }}>
{matches.map((m) => (
<Vasokey={m.key}
style={{
position: 'absolute',
width: m.width,
height: m.height,
borderRadius: 999,
transform: `translate(${m.x}px, ${m.y}px)`,
transition: 'transform 0.5s, width 0.4s',
}}
/>
))}
</VasoGroup>// The bar: animate the two widths in CSS, the neck between them is free<VasoGroupsmooth={34} depth={1.2} light="pointer"><VasoclassName="toolbar"style={{ position: 'absolute' }} /><VasoclassName="search"style={{ position: 'absolute' }} /></VasoGroup>
Crystal balls
Each ball is a round <Vaso> with a strong depth. A small physics loop moves the balls with transforms, and on phones the tilt feeds gravity.
pressable swells the glass and forms a small lens under the finger; ripple sends a ring through it from every tap; light="pointer" turns the rim towards the cursor. The drag is an ordinary transform on a wrapper.
n = sin θ₁ / sin θ₂ · n = sin θ₁ / sin θ₂ · n = sin θ₁ / sin θ₂ · n = sin θ₁ / sin θ₂ · n = sin θ₁ / sin θ₂ · n = sin θ₁ / sin θ₂ ·
Watern 1.33 · round dome
Crystaln 2.1 · cut bevel
Icefrosted · turbulent
Bubblethin film · iridescent
Drag a lens
Preview. Each lens comes from the lab engine: the index of refraction and the surface profile shape the displacement map, ice mixes in turbulence and the bubble gets a thin-film rim. A material prop is next; until then, depth, blur and dispersion get you close.
Code
lens.tsx
import { Vaso } from'vaso'// Closest today: shape the lens with depth, blur and dispersion<Vasowidth={160} height={160} radius={80} depth={2.6} dispersion={1.4} />// crystal<Vasowidth={160} height={160} radius={80} depth={1.2} blur={4} />// ice<Vasowidth={160} height={160} radius={80} depth={0.8} dispersion={0.2} />// water
Adaptive inkPreview · the bars pick their ink from what scrolls beneath
Dunes at noon
Night mode
Dark page, light ink.
Alpine lakeUnder the canopy
Paper white
Bright page, dark ink.
AuroraWaterfall
Hysteresis
No flicker on busy photos.
Ocean cliffsDunes at noon
Night mode
Dark page, light ink.
Alpine lakeUnder the canopy
Paper white
Bright page, dark ink.
AuroraWaterfall
Hysteresis
No flicker on busy photos.
Ocean cliffs
‹Library⋯
Scroll the feed
The scroll thumb is a plain <Vaso>. The bars read the lightness of what scrolls under them and flip their ink with two thresholds, so a busy photo never makes them flicker. The bars themselves are a preview.
Code
scrollbar.tsx
import { Vaso } from'vaso'// Two thresholds: flip only on a clear change
if (ink === 'dark' && lightness < 0.42) ink = 'light'
else if (ink === 'light' && lightness >0.6) ink = 'dark'<divstyle={{ position: 'absolute', right: 5, transform: `translateY(${y}px)` }}><Vasoradius={10} depth={1.6} blur={0.2}><divclassName="thumb" data-ink={ink} /></Vaso></div>
Segmented control · the pill bounces into place
The pill is one <Vaso> moved by an underdamped spring. While it travels it stretches toward its target and refracts a little harder, then bounces into place.
Vaso was blessed by the SVG filter and became a powerful glass.
Width is just a prop. While it keeps changing, vaso stretches the last displacement map instead of drawing a new one, and draws a sharp one once the size settles.