Loaders✨ Premium

DNA Helix

8 indigo/fuchsia dots at 12 px in 4 pairs, X-opposition oscillation 1 s (250 ms stagger between levels) — 60 × 60 px container in rotateY 2 s. Pure CSS.

CSS3D

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. Loaders has 29 effects, including 4 free. Explore the category →

3 usage examples

Sequence alignment in progress — bioinformatics tool — DNA Helix example 1

① Sequence alignment in progress — bioinformatics tool

WhenBioinformatics or genomics interface processing an alignment or annotation; the loader fills the space of a pending result.
WhyThe shape of the effect matches the subject being processed literally. No standard loader communicates "DNA sequence" as directly; a circular spinner would be generic where this helix is semantic. The binary structure (indigo/fuchsia = two strands) reinforces the analogy.
SettingsSlow dnaRotate to 3s for a sense of long, careful computation; reduce the amplitudes to translateX(±15px) if the available area is narrow (less than 48 px wide).
Patient portal — blood panel being published — DNA Helix example 2

② Patient portal — blood panel being published

WhenPatient area of a medical laboratory: the panel has been validated by the biologist but is not online yet. The helix fills the center of the “Blood panel of 12 Sep” card in place of the results while they are being published.
WhyA patient waiting for a blood test is waiting for lab work, not for a network reply: the double helix says “your sample is in the lab's hands” where a circular spinner would say “the server is thinking”. The two strands crossing at the center every 0.5 s stand for the sample/result pairing, and the 3D rotation gives the card a living object at its heart — at 120 px on screen the shape reads as a molecule, which no progress bar or geometric loader can suggest.
SettingsLoader enlarged through a transform: scale(2) wrapper (120 px on screen, code untouched — the % top values follow); dnaRotate kept at its 2s; waves slowed to 1.6s with delays 0 / .4s / .8s / 1.2s — same ratio as the default, the 4 levels still cover exactly one cycle. role="status" on the block holding the helix and the “Results are being published” text, aria-hidden="true" on .loader-dna.
Consumer DNA test — “Your ancestry report is being prepared” — DNA Helix example 3

③ Consumer DNA test — “Your ancestry report is being prepared”

WhenTracking page of a direct-to-consumer DNA kit: the sample is being sequenced and the ancestry report is a few weeks away. The helix is the page's status illustration, next to the 4 processing steps.
WhyHere the helix does not illustrate the wait, it is the product: the customer mailed their saliva to have their DNA read, and the page shows that DNA being read. The container's 3D rotation presents the molecule from every angle — sequencing made visible — and the two differently colored strands carry on their own the idea of an ancestry with two lineages. No other loader can be both the status indicator and the service's logo; that is why it is shown large (144 px) rather than tucked in a corner.
SettingsDefault settings kept (dnaRotate 2 s, waves 1 s, .25s delays, indigo #6366f1 / fuchsia #d946ef): this is the signature shape. transform: scale(2.4) wrapper → 144 px on screen, inside a 180 px disc. A page the customer keeps open: add @media (prefers-reduced-motion: reduce) { .loader-dna, .loader-dna span { animation: none } } — the 8 static circles remain legible. role="status" + aria-label on the disc, aria-hidden="true" on .loader-dna.

How it works

The component is a .loader-dna element of 60 × 60 px set to position: relative and transform-style: preserve-3d. It holds exactly 8 empty span elements arranged in 4 pairs: the first two at top: 0, the next at top: 25% (15 px), 50% (30 px) and 75% (45 px). Both spans in a pair share the same vertical position. Odd spans (:nth-child(2n+1)) get background: #6366f1 (indigo); even spans get #d946ef (fuchsia). All are round (border-radius: 50%), absolutely positioned at left: 50%, and a base transform: translateX(-50%) centers them — a value overridden by the animations the moment they start.

Two animations drive the lateral movement. dnaWave1 starts at translateX(-20px) scale(.8) at 0% and 100%, peaks at translateX(20px) scale(1) at 50%: the dot slides from left (smaller, at 80% scale) to right (full size). dnaWave2 is the exact reverse. These two keyframes alternate across odd/even pairs: at any moment, one dot moves left while its neighbor at the same level moves right — the two strands cross at the center of the container every 0.5 s. The four levels are offset by 0 s, 0.25 s, 0.5 s, 0.75 s: the delay spread covers exactly one full 1 s cycle distributed across 4 heights, creating a sinusoidal wave that travels down the structure.

A third animation, dnaRotate, applies rotateY(360deg) in 2 s linear infinite on the entire container. Thanks to transform-style: preserve-3d, all 8 children participate in the 3D context. No span has its own translateZ (Z = 0 for all): the depth comes entirely from the container's rotation. A measured consequence: at 90° and 270° of the rotation (0.5 s and 1.5 s into each 2 s cycle), the container is seen edge-on — the spans measure ~1 px wide (measured between 0.68 px and 0.85 px in Chromium). This “wire” state lasts about 80 ms per pass; it is the expected behavior of a flat object seen in profile, not an anomaly. The effect is entirely CSS — the js field is empty, no script is needed.

Accessibility

  • prefers-reduced-motion not handled: all 3 animations (dnaRotate, dnaWave1, dnaWave2) run unconditionally, even when prefers-reduced-motion: reduce is active. Measured in Playwright: animationPlayState: "running" on the container and all 8 spans after emulation. The CSS fix is two lines: @media (prefers-reduced-motion: reduce) { .loader-dna, .loader-dna span { animation: none } }. The integrator must decide on a visual fallback: 8 static stacked circles are enough to convey "something is present".
  • As a loading indicator, the component must signal its state to assistive technologies. The sold code contains no role="status", aria-busy, aria-live, and no alternative text. Integrate as: <div class="loader-dna" role="status" aria-label="Loading">…</div>. If a visible "Loading…" text accompanies the loader, aria-label is redundant — role="status" alone is sufficient.
  • The 8 empty span elements are presentational. Without aria-hidden="true" on the container, some screen readers traverse them and announce 8 empty elements. Add aria-hidden="true" on .loader-dna when the zone already carries role="status" and an accessible label.
  • Color contrast: both colors are non-text graphical components (WCAG 3:1 threshold for UI components). On the demo background #0a0a0f: indigo #6366f1 → 5.4:1 ✓, fuchsia #d946ef → 5.1:1 ✓. On white (#fff): indigo → 3.4:1 ✓, fuchsia → 3.0:1 (borderline — prefer #c026d3 on light backgrounds to reach 3.3:1 without altering the hue).
  • The effect responds only to its CSS animation cycle: no mouse, keyboard or scroll-linked events are captured. The zone is not interactive and must not be focusable (no tabindex in the sold code — do not add one).

Browser compatibility

Pure CSS: transform-style: preserve-3d, @keyframes, animation, border-radius: 50%, :nth-child. No external dependency, no JavaScript.

Chrome 36+✓ Full
Firefox 16+✓ Full
Safari 9+✓ Full
Edge 12+✓ Full
Mobile iOS 9+✓ Full
Android Chrome 36+✓ Full

Without CSS animation support (very old browsers), the 8 circles render at their initial position — 4 overlapping pairs of static dots. Without transform-style: preserve-3d (IE 10 and below), the 3D rotation degrades to a 2D rotation: the effect remains legible but loses its depth. Colors and circular shapes are always rendered.

The code

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

index.html — structure
<div class="loader-dna">
  <span></span>
  <span></span>
  <!-- … 8 spans total — 4 pairs at top 0 / 25% / 50% / 75% -->
</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
Rotation duration (CSS — animation: 2s … dnaRotate) 2 s Speed of one full 3D revolution. 1s = lively rotation, sense of urgency; 4s = slow, deliberate rotation. Independent of the wave duration — the two cycles do not sync (2 s and 1 s: 2:1 ratio, the strands complete 2 crossings per revolution).
Wave duration (CSS — animation: 1s … dnaWave1/dnaWave2) 1 s Speed of one back-and-forth sweep of a strand. Change all 8 animation declarations (spans 1–8) together. 0.6s = nervous strands; 2s = slow wave that accentuates depth. Do not go below 0.4s — the ease-in-out easing needs time to smooth the transitions.
Level stagger (CSS — .25s, .5s, .75s) 250 ms gap (4 levels × 0.25 s = 1 full cycle) Vertical wave step. Lower to .1s: the wave descends faster and the helix looks more compact. Raise to .4s on a 1.6 s wave duration: the 4 levels then cover exactly one cycle (same ratio). Both spans of the same level always keep the same delay.
Lateral amplitude (CSS — translateX(±20px)) ±20 px Range of oscillation from left: 50%. Maximum value without overflow: (container width / 2) − (span width / 2) = 30 − 6 = 24 px. At ±10px: a tighter helix with strands barely crossing; at ±24px: strands at the container edges. Change both keyframes (dnaWave1 and dnaWave2) together.
Strand 1 color (CSS — background: #6366f1) #6366f1 (indigo) Color of odd spans (positions 1, 3, 5, 7). Any valid CSS color value. A glow (box-shadow) is not present in the sold code — add manually if desired.
Strand 2 color (CSS — background: #d946ef) #d946ef (fuchsia) Color of even spans (positions 2, 4, 6, 8). Use a similar hue to strand 1 for a subtle monochrome helix; choose a complementary color to maximize strand contrast.
Container size (CSS — width: 60px; height: 60px) 60 × 60 px The spans' top values are percentages (0 / 25% / 50% / 75%): they automatically adapt to a new height. Change width and height together to keep the square shape. Adjust the lateral amplitude (previous rule) proportionally.

FAQ

This is the expected behavior of the 3D rotation. The dnaRotate animation turns the container 360° around the Y axis over 2 s. At 90° and 270° (0.5 s and 1.5 s into each cycle), the container is seen edge-on — the 8 spans, which have no depth of their own (translateZ: 0), appear as a line of ~1 px wide (measured: 0.68–0.85 px in Chromium). This state lasts about 80 ms per pass before the rotation continues. To avoid the "wire" effect, add translateZ(15px) to odd spans and translateZ(-15px) to even spans: the strands are placed in front of and behind the center plane, and the helix stays visible at every angle.
Cube Flip (fx-0425) and Cube Grid (fx-0430) rotate rectangular shapes on two axes; the 3D is in the geometry of a single object or a grid of independent elements. DNA Helix is the only loader in the category whose 3D comes from rotating the entire container (transform-style: preserve-3d) while the children animate in 2D inside it — the combination of the two motions is what creates the helix illusion. Morphing Shape (fx-0431) and SVG Morphing Loaders (fx-0777) have no 3D rotation at all. DNA Helix is also the only one in this group that is entirely CSS without any SVG element.
Yes, with color adjustments. On white, indigo #6366f1 reaches 3.4:1 (WCAG — UI component ≥ 3:1 ✓) but fuchsia #d946ef drops to 3.0:1 (borderline — prefer #c026d3, 3.3:1). The #0a0a0f background in the sold code belongs to .demo-preview: remove that wrapper and apply your own background. .loader-dna itself has no declared background.