Glassmorphism✨ Premium

Bubble Glass Grid

Twelve 51 px glass bubbles in a four-column grid: the hovered bubble swells to ×1.35 as a rounded square (peaking at ×1.38 with a spring), its two neighbors in HTML order go to ×1.1 thanks to the :has() selector — pure CSS, zero JavaScript, on a declared background that the glass blurs.

CSSGridHover

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Glassmorphism has 47 effects, including 6 free. Explore the category →

3 usage examples

Choosing ice cream flavors — an ice cream parlour's ordering kiosk — Bubble Glass Grid example 1

① Choosing ice cream flavors — an ice cream parlour's ordering kiosk

WhenThe customer builds a cone on the kiosk: twelve flavors as bubbles, from vanilla to cassis, and the cone fills up on the left with each choice.
WhyThe swelling bubble points at the targeted flavor and its neighbors step slightly aside: on a touch screen used standing up, you see right away which cell you are about to touch. Under each bubble the flavor's color shows through blurred, like a scoop seen through the glass of the tub.
SettingsGrid widened to 270 px (63 px cells), placed on the right; grid background replaced by one sharp dot per cell in the flavor's color, offset by 7 px and 6 px so its edge runs under the bubble. White label with a drop shadow in each bubble (centered display: flex, min-width: 0 so “Strawberry” does not widen its column).
Mood of the day — the journal of a mental health app — Bubble Glass Grid example 2

② Mood of the day — the journal of a mental health app

WhenIn the evening the user opens the journal and picks the mood that sums up the day among twelve words, from “serene” to “mixed”.
WhyThe soft swell and the slight shift of the neighbors make a round, almost breathing gesture that suits a delicate moment; a grid of hard boxes would feel like an administrative form. The colors blurred under the glass stay moods, never loud labels.
SettingsSame 270 px grid, dusk background, one dot per mood (teal, yellow, coral, blue, slate gray…) and an emoji above the word. Short words: the 63 px bubble holds about ten characters at 10.5 px.
Nail polish swatches — a nail salon's online shop — Bubble Glass Grid example 3

③ Nail polish swatches — a nail salon's online shop

WhenAutumn collection page: twelve shades as bubbles, the shopper hovers the colors, the bottle of the chosen shade shows on the left with the price.
WhyThe swell isolates the shade being looked at without opening a window, and the domed glass with its highlight recalls the shine of a gel polish. Neighbors stepping aside let two close shades be compared side by side.
Settings270 px grid on an almost black brown background so the light shades (nude, rose) stand out; shade number under the name; CSS bottle (cap and rounded body) in the left column.

How it works

One grid, twelve divs, four rules. .lg-bubble-grid sets grid-template-columns: repeat(4, 1fr), gap: 6px and width: 220px: each bubble is 50.5 px (measured 51 × 51 in Chromium, Firefox and WebKit), three rows, a 256 × 200 px grid including its frame. Each .lg-bubble is a glass disc (aspect-ratio: 1, border-radius: 50%): a white radial gradient from 25% to 5% lit at 35% / 30%, a white rim at 15%, backdrop-filter: blur(8px), a drop shadow and an inner highlight. On hover, transform: scale(1.35), border-radius: 40% — the disc becomes a square with very round corners —, a stronger violet-tinted highlight and z-index: 2 to move in front of its neighbors.

The signature lives in one rule with two selectors: .lg-bubble:hover + .lg-bubble targets the next bubble, .lg-bubble:has(+ .lg-bubble:hover) the previous one — a “previous sibling selector” that only :has() makes possible. Both go to scale(1.1) and border-radius: 45%. Measured while hovering the sixth bubble: the fifth and seventh at ×1.1, the sixth at ×1.35, the others still. Neighborhood follows HTML order, not geometry: hover the fourth (end of the first row) and it is the fifth, first of the next row, that reacts; the bubbles above and below do not move. The .5s cubic-bezier(.34, 1.56, .64, 1) transition overshoots its target: measured, ×1.384 around 280 ms then back to ×1.35 before 500 ms, in all three engines. The grid “breathes” around the pointer without a line of script.

The grid declares its own background, because glass only exists if it has something to blur: set over a flat color (a page's, or a demo frame's #0a0a0f), the bubbles would just be gray discs, as an 8 px blur over a flat color does not show. Hence box-sizing: content-box, padding: 18px, border-radius: 26px and four sharp-edged dots (pink, blue, yellow, violet) on an indigo gradient. Between the bubbles the dot edges stay sharp; under each bubble they soften: that contrast is what makes the glass read. Bubbles keep their 51 px, the grid is 256 px wide with its padding.

Accessibility

  • Keyboard and touch: twelve divs with no tabindex or role, and an effect limited to :hover. With a keyboard nothing is reachable; with a finger, a tap triggers hover, which sticks until the next tap elsewhere. For a choice grid, use <button>s (or styled <input type="radio">s) and add :focus-visible to every selector: .lg-bubble:is(:hover, :focus-visible), .lg-bubble:is(:hover, :focus-visible) + .lg-bubble and .lg-bubble:has(+ .lg-bubble:is(:hover, :focus-visible)).
  • No prefers-reduced-motion: under emulation, the bubble still rises to ×1.38 then settles at ×1.35 in 0.5 s. The motion only starts from a user gesture, but the bounce is what bothers sensitive users: @media (prefers-reduced-motion: reduce) { .lg-bubble { transition-timing-function: ease; transition-duration: .15s } } keeps the swell without the spring.
  • Screen readers: the bubbles in the sold code are empty, nothing is announced. As soon as they carry a label (flavor, mood, shade), give the grid role="group" and an aria-label, and each bubble real text rather than a color alone — color cannot be the only information (WCAG 1.4.1).
  • Contrast: the glass is white at 5-25% over the grid background; text placed in a bubble reads against the blurred color underneath. The scenes on this page use white text with text-shadow: 0 1px 4px rgba(0,0,0,.55): it holds on mid and dark tones, it weakens on the lightest ones (vanilla, coconut) — switch the text to dark on those cells or darken the dot.

Browser compatibility

CSS only: grid, aspect-ratio, backdrop-filter, :has(), transitions. Zero JavaScript, zero dependencies. Measured in Chromium 151, Firefox 153 and WebKit 26: same sizes (51 px bubbles), same swell (×1.35, peak ×1.384), same neighbors at ×1.1. backdrop-filter is declared with and without prefix (-webkit-backdrop-filter), which covers Safari before version 18. The sensitive point is :has(): Chrome and Edge 105, Safari 15.4, Firefox 121. A browser that does not know it drops the whole rule, both selectors included — measured by replacing :has() with an unknown selector, the rule is not kept in any of the three engines: the hovered bubble still swells, but no neighbor reacts.

Chrome 105+✓ Full
Firefox 121+✓ Full
Safari 15.4+✓ Full
Edge 105+✓ Full
Mobile iOS 15.4+✓ Full (a tap acts as hover)
Android Chrome 105+✓ Full (a tap acts as hover)

Without :has() (Chrome < 105, Firefox < 121, Safari < 15.4), the neighbor rule is dropped entirely: only the hovered bubble swells. To keep at least the next neighbor, split the rule in two, one per selector. Without backdrop-filter, bubbles remain translucent discs over the grid background, unblurred. Without aspect-ratio (before 2021), bubbles collapse to their content height: give them a fixed height.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="demo-preview">
  <div class="lg-bubble-grid">
    <div class="lg-bubble"></div>
    <div class="lg-bubble"></div>
    … (12 bubbles)
  </div>
</div>
Unlock the full code — $9.90/mo incl. tax

The full HTML + CSS + JS of Bubble Glass Grid, ready to paste — plus all 840 effects. Or $299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Grid size (CSS — .lg-bubble-grid { width: 220px }) 220 px of content, 50.5 px bubbles Bubble = (width − 3 × gap) ÷ 4. 270 px gives 63 px bubbles, 400 px gives 96 px.
Number of columns (CSS — grid-template-columns: repeat(4, 1fr)) 4 columns, 12 bubbles over 3 rows Neighbors are HTML neighbors: with 3 columns, bubble 3 triggers bubble 4, first of the next row. Add min-width: 0 to the bubbles if they carry text, otherwise a long word widens its column.
Swell (CSS — .lg-bubble:hover { transform: scale(1.35); border-radius: 40% }) ×1.35, rounded square at 40% scale(1.2) for a tight grid, border-radius: 50% to keep the disc. Beyond ×1.5 the bubble covers its neighbors widely: increase the gap.
Neighbor reaction (CSS — .lg-bubble:hover + .lg-bubble, .lg-bubble:has(+ .lg-bubble:hover)) previous and next neighbor at ×1.1, radius 45% To make neighbors recede instead of growing: scale(0.92). To reach the second neighbor too: .lg-bubble:hover + .lg-bubble + .lg-bubble and .lg-bubble:has(+ .lg-bubble + .lg-bubble:hover).
Spring (CSS — transition: .5s cubic-bezier(.34, 1.56, .64, 1)) 0.5 s, peak ×1.384 around 280 ms The 1.56 creates the overshoot. cubic-bezier(.34, 1.2, .64, 1) softens it, ease removes it. The same transition plays on the way back: the bubble you leave bounces too.
Background behind the glass (CSS — .lg-bubble-grid { background: … }) 4 sharp dots on an indigo gradient, 18 px frame rounded at 26 px Replace it with a photo (url(…) center / cover) or one dot per cell as in the scenes. A flat background makes the blur invisible: keep sharp edges or detail under the bubbles.
Glass blur (CSS — backdrop-filter: blur(8px)) 8 px 4 px let the drawing under the bubble show through, 14 px keep only its color. Change the value with and without the -webkit- prefix.

FAQ

No. The hovered bubble swells and rounds into a square, its two neighbors in HTML order grow slightly, and the enlarged bubble moves in front (z-index: 2): it covers its neighbors, it does not fuse with them. A real merging effect needs a “gooey” SVG filter (blur then alpha threshold) applied to the whole grid, and that filter destroys the glass: the threshold wipes out the translucent gradient and the highlight. The two effects do not combine.
Because CSS only knows siblings in document order: + means the next one, :has(+ …) the previous one. The grid draws rows, but the HTML is a sequence of twelve elements; for bubble 6 the neighbors are 5 and 7, and for bubble 4 they are 3 and 5 — 5 being at the start of the next row. To target the bubble below in a 4-column grid, skip three siblings: .lg-bubble:hover + * + * + * + .lg-bubble, and .lg-bubble:has(+ * + * + * + .lg-bubble:hover) for the one above. It works as long as the column count does not change.
backdrop-filter blurs what is behind the element; over a flat color the result is the same flat color. That is why the grid declares its own background (sharp dots on a gradient): if you remove it to place the grid over your page, the page needs detail behind the bubbles — photo, illustration, pattern. Also check that no ancestor of the grid has a filter or an opacity below 1: they create a new root for the blur, which then no longer sees the page background.