Home / Snippets / Animation /

Clip-path reveal

Entrance animation that unveils elements using clip-path shapes — circle expand, inset wipe, polygon sweep, and shrink-in.

Circle expand
Grows outward from center
Inset wipe
Wipes in from the left edge
Polygon sweep
Sweeps across with a hard edge
🔲
Inset shrink
Expands from a rounded center
Widely Supported
animationno-js

Quick implementation

/* Circle expand from center */
.clip-reveal {
  animation: clip-reveal-circle 0.8s ease-out both;
}

@keyframes clip-reveal-circle {
  from { clip-path: circle(0% at 50% 50%); }
  to   { clip-path: circle(100% at 50% 50%); }
}

/* Inset wipe from left edge */
.clip-reveal--inset {
  animation: clip-reveal-inset 0.8s ease-out both;
}

@keyframes clip-reveal-inset {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0% 0 0); }
}

@media (prefers-reduced-motion: reduce) {
  .clip-reveal,
  .clip-reveal--inset {
    animation: none;
    clip-path: none;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer building entrance animations for a UI.

Goal: CSS clip-path reveal animations that unveil elements using geometric
shapes — a circle expanding from center, a left-edge inset wipe, a polygon
sweep, and an inset shrink-in. No JavaScript required.

Technical constraints:
- Use @keyframes animating clip-path between two shape values of the same
  type (e.g. circle() to circle(), inset() to inset(), polygon() to polygon()).
  Mixing shape types is not interpolatable and will jump rather than animate.
- Apply animation: <name> 0.8s ease-out both; — "both" fill mode keeps the
  element hidden at clip-path: circle(0%) before the animation starts.
- Use oklch() for any colors — no hex or rgba values.
- Use CSS custom properties (var(--card), var(--text), etc.) for theming.
- Include @media (prefers-reduced-motion: reduce) that sets animation: none
  and clip-path: none on all animated elements.

Framework variant (pick one):
A) Vanilla CSS — provide classes .clip-reveal, .clip-reveal--inset, and
   .clip-reveal--polygon, each with its own @keyframes block.
B) React component — accept a shape prop ("circle" | "inset" | "polygon"),
   a delay prop (number in seconds), and children; apply the correct
   animation class and inline animation-delay via style.

Edge cases to handle:
- clip-path hides content from pointer events and accessibility trees when
  fully clipped — ensure animation starts on mount or when the element enters
  the viewport so it always finishes revealing.
- Stagger multiple items using animation-delay increments rather than
  re-triggering via JavaScript class toggling.
- Safari requires webkit-clip-path for older versions — add the prefixed
  property alongside clip-path for broader compatibility.

Return CSS only (or a React component if variant B is chosen).

Why clip-path reveals feel different

Opacity alone fades an element in uniformly — every pixel lightens at the same rate. clip-path does something fundamentally different: it defines a visible region, and as that region expands, it literally uncovers the element rather than brightening it. The content beneath the clip boundary is fully opaque and fully colored the entire time; only the mask changes. This creates reveals that feel physical — like peeling back a cover, parting a curtain, or zooming in through a lens.

Different shapes create different moods. A circle() expanding from center reads as energetic and focal — attention converges to the element's heart before spreading outward. An inset() wipe from left mirrors natural reading direction and feels editorial, like a newspaper photo revealing itself. A polygon() sweep is hard-edged and mechanical, suited to dashboards and data visualizations. Each is one @keyframes block away.

The logic

CSS can interpolate between two clip-path values only if they are the same shape function with the same number of arguments. circle(0%) to circle(100%) works because both are circle() calls — the browser linearly interpolates the radius. Mixing circle() and polygon() in a single keyframe sequence produces no animation at all; the browser snaps between them.

The inset() function takes four length values representing the top, right, bottom, and left offsets inward from the element's border box. inset(0 100% 0 0) means the right edge is pushed 100% of the element width inward — collapsing the visible area to nothing. Animating the right offset to 0% progressively widens the visible strip from left to right.

The both fill mode ensures the from values apply before the animation fires. Without it, the element appears at full size for one frame before the animation clips it to zero — a visible flash especially noticeable on delayed items.

Performance & accessibility

clip-path is a paint-only property on most browsers — changing it triggers a repaint of the element but does not cause layout recalculation. It does not move other elements, resize boxes, or affect the document flow. This puts it one step below the gold standard of transform and opacity (which run on the compositor thread entirely), but still far safer than animating width, height, or margin, which trigger full layout passes.

On modern Chrome and Firefox, clip-path animations with simple shapes like circle() and inset() are GPU-accelerated via the compositor when the element has a stacking context (created automatically by most animations). Complex polygon() paths with many vertices may fall back to the main thread — keep polygon point counts low for performance-sensitive contexts.

The prefers-reduced-motion: reduce query is critical here because clip-path reveals can be disorienting — a circle expanding from nothing is a rapid change in visual mass. Setting animation: none and clip-path: none inside the query reveals the element immediately in its full state. Without this reset, a user with reduced motion enabled would see the element clipped to circle(0%) and never revealed at all — the both fill mode would freeze it in the hidden state.