# How to Use Blur Filters to Mask Imperfect Crossfade Transitions

> Smooth imperfect crossfade transitions with a 2px blur filter applied only during UI state changes. Eliminate jarring double-image effects and create seamless animations.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-08-02

---

**Apply a subtle 2px blur filter only during the transition period to blend overlapping UI states and eliminate the jarring "double-image" effect in crossfade animations.**

Crossfade animations in modern web interfaces often reveal a brief visual glitch when two UI states overlap simultaneously. Even with mathematically perfect easing curves and durations, the human eye perceives this overlap as a distracting "ghosting" artifact. According to the animation standards in `emilkowalski/skills`, applying a **blur filter exclusively during the transition** softens this visual gap, transforming a mechanical state change into a fluid, natural motion.

## Why Blur Filters Fix Crossfade Imperfections

The core problem with crossfades is **perceptual contrast**. When two elements occupy the same screen space at different opacities, the eye processes them as competing visual signals rather than a single coherent transition.

### The Science Behind the Technique

- **Perceptual blending** – Blur softens the hard edges of both outgoing and incoming elements, reducing the contrast that makes overlap noticeable. The two states merge into a single visual impression rather than fighting for attention.

- **Low visual cost** – A minimal blur radius of `2px` provides sufficient smoothing without degrading the perceived sharpness of your interface. This threshold is explicitly recommended in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) as the default "safe" value.

- **Performance efficiency** – Blur is a **paint-only** GPU operation. It does not trigger layout recalculation or compositing layers, keeping the animation on the fast path. As noted in the standards documentation, larger blur radii exceeding `20px` become GPU-expensive, particularly on Safari, where the cost curve steepens dramatically.

The repository treats this technique as a **"polish" refinement** rather than a primary animation driver, positioning it alongside staggered entrances and spring dynamics in the hierarchy of motion quality【/cache/repos/github.com/emilkowalski/skills/main/skills/review-animations/STANDARDS.md†L145-L148】.

## Recommended Blur Filter Settings for Crossfades

| Property | Value | Justification |
|----------|-------|---------------|
| `filter: blur()` | `2px` | Minimum effective smoothing; maintains UI crispness post-transition. |
| Maximum radius | `< 20px` | Prevents GPU bottlenecking and Safari-specific performance degradation. |
| Duration | `≤ 300 ms` | Syncs with component-level timing; avoids perceptible lag. |

These constraints align with the repository's broader philosophy of **interruptible, transform-based motion** and the emphasis on `transform` and `opacity` as the primary animation properties【/cache/repos/github.com/emilkowalski/skills/main/skills/review-animations/STANDARDS.md†L110-L118】.

## Practical Implementation Examples

### CSS Crossfade with Blur Masking

```css
/* Base crossfade container */
.crossfade {
  opacity: 0;
  transition: 
    opacity 200ms cubic-bezier(0.23, 1, 0.32, 1),
    filter 200ms cubic-bezier(0.23, 1, 0.32, 1);
}

/* Entering state: fade in with blur */
.crossfade.enter-active {
  opacity: 1;
  filter: blur(2px);
}

/* Exiting state: fade out with blur */
.crossfade.leave-active {
  opacity: 0;
  filter: blur(2px);
}

/* Post-transition: restore full sharpness */
.crossfade.enter-done,
.crossfade.leave-done {
  filter: none;
}

```

The `cubic-bezier(0.23, 1, 0.32, 1)` curve matches the repository's standard "strong ease-out" profile, providing rapid initial movement with gentle deceleration.

### Button State Transition with Blur

```css
.button {
  transition: 
    transform 160ms ease-out, 
    filter 160ms ease-out;
}

.button:active {
  transform: scale(0.97);
  filter: blur(2px);
}

.button:not(:active) {
  filter: none;
}

```

This pattern addresses the "ghost" visibility issue when a button's default and pressed states briefly coexist during rapid interactions.

### Web Animations API (WAAPI) Approach

```javascript
const element = document.querySelector('.crossfade');

element.animate(
  [
    { opacity: 0, filter: 'blur(2px)' },
    { opacity: 1, filter: 'blur(0px)' }
  ],
  {
    duration: 200,
    easing: 'cubic-bezier(0.23, 1, 0.32, 1)',
    fill: 'forwards'
  }
);

```

The WAAPI implementation provides programmatic control and better integration with modern React or Vue transition components, while respecting the same timing and easing constraints defined in [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md).

## Performance Considerations for Blur Filters

### GPU and Paint Behavior

- Blur executes entirely on the compositor thread when applied to elements with their own rendering layer.
- No layout or style recalculation occurs during the animation frame.
- Safari shows disproportionate cost increases above `20px` due to its shader-based blur implementation.

### Application Scope

- **Target the transitioning element only** – Never apply blur to parent containers or the full viewport.
- **Remove blur immediately post-transition** – The `filter: none` reset triggers no performance penalty and preserves text legibility.
- **Avoid on high-frequency interactions** – Keyboard shortcuts, rapid toggles, or swipe gestures should not incur blur overhead; the additional paint work harms responsiveness in these contexts.

## When to Apply (and Avoid) Blur Masking

### Appropriate Use Cases

- **Modal and drawer crossfades** – Where backdrop and content layers overlap during entry/exit sequences.
- **Tab panel transitions** – When outgoing and incoming content panels share dimensions.
- **Media gallery navigation** – Image-to-image fades where color or contrast shifts are abrupt.
- **Button state changes** – Particularly for toggle-style controls with distinct visual modes.

### Scenarios to Skip

- **Rapid successive interactions** – Any UI element triggered >5 times per second.
- **Scroll-linked animations** – Blur application during scroll causes jank due to main-thread synchronization.
- **Low-power mode devices** – Respect `prefers-reduced-motion` and disable blur when system resources are constrained.

## Key Source Files in `emilkowalski/skills`

The repository provides comprehensive guidance across multiple skill documents:

| File | Relevance |
|------|-----------|
| [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) | Core animation standards including the "masking imperfect crossfades" rule and max-blur constraints【/L145-L148】. |
| [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) | Design-engineering perspective on transition polishing techniques. |
| [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) | Checklist prioritizing blur as a high-impact polish item. |
| [`skills/improve-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/SKILL.md) | Severity classification placing blur under "low-risk polish" interventions. |

## Summary

- **Blur filters mask crossfade imperfections** by perceptually blending overlapping UI states during the transition window.
- **Use 2px as the default radius**, never exceeding 20px to maintain GPU performance.
- **Apply blur only during active transition states**, removing it immediately upon completion.
- **Respect platform constraints** – Safari shows disproportionate blur costs, and high-frequency interactions should avoid the technique entirely.
- **Reference [`STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/STANDARDS.md)** in `emilkowalski/skills` for authoritative timing curves and severity classifications.

## Frequently Asked Questions

### How much blur should I apply to hide crossfade glitches?

Start with `2px`. This radius provides sufficient edge softening to eliminate the "double-image" effect without perceptibly degrading interface sharpness. The `emilkowalski/skills` repository explicitly validates this value as the default standard, with usability testing showing no legibility impact at typical viewing distances.

### Does blur filter animation cause performance problems?

No—when applied correctly. Blur is paint-only and stays GPU-accelerated. Problems emerge only above `20px` (causing Safari shader bottlenecks) or when applied to large containers rather than specific transitioning elements. The repository's animation audit guidelines classify blur as "low severity" for performance impact.

### Why not use opacity alone for crossfades?

Pure opacity crossfades expose the visual overlap period where two states compete for attention. The human visual system processes this as motion blur or "ghosting," particularly with high-contrast elements. Blur actively **merges** the visual signals rather than merely stacking them, creating a smoother perceptual interpolation.

### Should blur be applied to the entering element, leaving element, or both?

Apply to **both simultaneously**. The technique works by softening edges on all overlapping content, so constraining blur to one state leaves the other with hard edges that still register as visual conflict. The CSS examples above demonstrate symmetrical application to `enter-active` and `leave-active` classes.