Atmosphere✨ Premium

Underwater Caustics

Two repeating-conic-gradient layers animated in opposite directions create moiré aquatic caustics — CSS rising bubbles included, zero JavaScript.

CSSGradientAnimation

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →

3 usage examples

Hero / Landing — Dive booking site — Underwater Caustics example 1

① Hero / Landing — Dive booking site

WhenFull-page header on a travel or nautical activity booking site — where atmosphere matters more than content density.
WhySlowly animated caustics create underwater immersion without heavy images: under 1 KB of CSS, no external resources, no dependencies.
SettingsSlow layers to 6 s and 9 s (durations in .caustics-layer and :nth-child(2)); raise conic gradient alpha from .06 to .09 for more sparkle.
Product component — Hydration / health summary widget — Underwater Caustics example 2

② Product component — Hydration / health summary widget

WhenDashboard of a wellness or fitness app displaying a goal achieved — hydration, daily steps, sleep score.
WhyAnimated water in the background ties the widget visually to its theme without an extra icon; <code>opacity: .8</code> keeps the card text readable.
SettingsSpeed up to 2.5 s and 4 s for more energy; cap container height at 160 px and keep only 2 bubble <span> elements to lighten.
Swimming app — Session summary — Underwater Caustics example 3

③ Swimming app — Session summary

WhenProfile page or post-session screen of a swimming or water-sport app, displayed immediately after the session ends.
WhyThe caustic background creates a direct visual link between the data (distance, pace, laps) and water, without extra icons or images — the numbers feel like they're floating in the pool.
SettingsKeep default durations 4 s / 6 s for a slow moiré that mimics pool reflections; raise cone alpha to .08 (rgba(100, 200, 255, .08)) for caustics more legible behind data tiles.

How it works

The effect relies on two stacked CSS layers (.caustics-layer), each carrying a pair of repeating-conic-gradient. The first anchors its cones at at 25% 25% and at 75% 75%, tiled at 80 × 80 px and 100 × 100 px. The second shifts them to from 30deg at 60% 40% and from 60deg at 40% 60%, on 90 × 90 px and 70 × 70 px tiles. The different from angle and at position offset the angular phase of both grids, together producing a diamond lattice (4–6 % alpha) that mimics pool caustic patterns.

Both layers share the same causticsMove animation — ±10 px translation, ±2° rotation — but with different durations (4 s and 6 s) and opposite directions (animation-direction: normal vs reverse). This frequency offset (2/3 ratio, 12-s beat) creates an animated moiré: the two tile grids slide against each other continuously, generating the characteristic underwater rippling without JavaScript or canvas.

The background is a linear-gradient on .caustics-container — three stops: #006994 at the surface, #046 at mid-depth, #034 at the bottom — acting as the water column. Both layers are position: absolute; inset: 0 and cover the container at any size. overflow: hidden prevents bleed during translation.

Four bubbles (.caustics-bubbles span) are pure CSS circles — border-radius: 50%, near-transparent fill (rgba(255,255,255,.05)), 1 px solid rgba(255,255,255,.2) border — animated by bubbleRise (ease-in, 220 px rise, scale(1.3)). Four distinct durations (5 s, 6 s, 7 s, 5.5 s) and staggered 0–3 s delays prevent any visible repeat cycle.

Accessibility

  • prefers-reduced-motion not implemented: the code contains no @media (prefers-reduced-motion) rule. The causticsMove and bubbleRise animations run even when the user has enabled "Reduce Motion" in their OS. Add before shipping: @media (prefers-reduced-motion: reduce) { .caustics-layer, .caustics-bubbles span { animation: none; } }.
  • The .caustics-container carries no aria-hidden. Since the effect is purely decorative, add aria-hidden="true" to exclude it from the accessibility tree.
  • No role or aria-label is defined on the container. In real usage, wrap the effect and overlay text in a role="img" with a descriptive aria-label.
  • No keyboard or pointer interaction — 100% decorative effect. No focus managed, no DOM events. Accessibility depends on overlay content positioned above (higher z-index).
  • The .caustics-label (white rgba(255,255,255,.85) on blue background #006994) achieves an estimated contrast ratio ≥ 4.6:1 — passes WCAG AA for normal text.

Browser compatibility

Requires repeating-conic-gradient (CSS Gradients Level 4). Supported in all modern browsers since 2019–2020. Zero JavaScript, zero external dependencies.

Chrome 69+✓ Full
Firefox 83+✓ Full
Safari 12.1+✓ Full
Edge 79+✓ Full
Mobile iOS 12.2+✓ Full
Android Chrome✓ Full

Browsers without <code>repeating-conic-gradient</code> support (IE, old Edge) render only the <code>linear-gradient</code> background — a clean blue gradient, no JS errors, no broken layout.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<div class="caustics-container">
  <div class="caustics-layer"></div>
  <div class="caustics-layer"></div>
  <div class="caustics-bubbles">
    <span></span>
    <span></span>
    <span></span>
    <span></span>
  </div>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
linear-gradient on .caustics-container #006994, #046, #034 Water background colors — change the 3 stops to adjust perceived depth (bright aqua, midnight blue, seaweed green).
animation-duration: 4s (.caustics-layer) 4s Layer 1 speed. 4 s = dynamic, 8 s = slow and contemplative. Keep a non-integer ratio with layer 2 for a richer moiré.
animation-duration: 6s (.caustics-layer:nth-child(2)) 6s Layer 2 speed (reverse direction). The 4s/6s ratio creates a 12-s beat — change it to lengthen or shorten the visual cycle period.
rgba(100, 200, 255, .06) in repeating-conic-gradient .06 / .05 / .04 Alpha of the bright conic segments. Raise to .15 for very pronounced caustics, lower to .02 for a near-invisible background texture.
background-size: 80px 80px (layer 1) 80px / 100px Conic tile size — smaller = fine grain, larger = broad light pools. Change both values of the same layer together to keep the composite look.
opacity: .8 on .caustics-layer .8 Global transparency of each layer. Drop to .4 for subtle, raise to 1 for a saturated render.
animation-duration on .caustics-bubbles span 6s, 5s, 7s, 5.5s Rise duration per bubble. Mixed values = natural async rise. Identical values = regular, hypnotic rhythm.
translateY(-220px) in @keyframes bubbleRise -220px Vertical travel distance of bubbles. Match to the actual container height so bubbles exit precisely at the top.

FAQ

No — Underwater Caustics is 100% CSS. There is no JavaScript in the source code. Copy the HTML and CSS into your page and the effect starts immediately. Compatible with React, Vue, Angular, and Svelte: no JS component to initialize, no dependency to import.
repeating-conic-gradient is supported since Chrome 69 (2018), Firefox 83 (2020), and Safari 12.1 (2019). On Internet Explorer or unsupported browsers, only the linear-gradient background renders — a clean blue gradient, no errors, no broken layout.
Position your content with position: absolute; z-index: 2 inside the same container (position: relative; overflow: hidden). Add a scrim if needed — an inset: 0 absolute div at z-index: 1 with background: linear-gradient(rgba(0,0,0,.4), rgba(0,0,0,.3)) — to ensure WCAG AA text contrast.