Patterns Medium
Box Reveal
Content revealed by a colored box that slides across then exits, creating a dramatic wipe-reveal effect.
Open in Lab
MCP
css javascript vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--box-color: #818cf8;
--box-duration: 0.7s;
--box-ease: cubic-bezier(0.77, 0, 0.175, 1);
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0a0a0a;
color: #e2e8f0;
min-height: 300vh;
overflow-x: hidden;
}
.hero {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
text-align: center;
padding: 2rem;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
color: rgba(148, 163, 184, 0.8);
font-size: 1.125rem;
}
.scroll-hint {
margin-top: 2rem;
color: rgba(148, 163, 184, 0.5);
font-size: 0.875rem;
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(8px);
}
}
.content-section {
max-width: 800px;
margin: 0 auto;
padding: 4rem 2rem;
display: flex;
flex-direction: column;
gap: 4rem;
}
/* Box Reveal wrapper */
.box-reveal {
position: relative;
display: inline-block;
overflow: hidden;
}
.box-reveal .box-reveal-content {
opacity: 0;
transition: opacity 0.01s;
}
.box-reveal::after {
content: "";
position: absolute;
inset: 0;
background: var(--box-color);
transform: scaleX(0);
transform-origin: left center;
z-index: 2;
}
/* Phase 1: box slides in from left */
.box-reveal.is-visible::after {
animation: boxSlideIn var(--box-duration) var(--box-ease) forwards, boxSlideOut
var(--box-duration) var(--box-ease) var(--box-duration) forwards;
}
/* Phase 2: content appears at midpoint */
.box-reveal.is-visible .box-reveal-content {
animation: contentReveal 0.01s var(--box-duration) forwards;
}
@keyframes boxSlideIn {
0% {
transform: scaleX(0);
transform-origin: left center;
}
100% {
transform: scaleX(1);
transform-origin: left center;
}
}
@keyframes boxSlideOut {
0% {
transform: scaleX(1);
transform-origin: right center;
}
100% {
transform: scaleX(0);
transform-origin: right center;
}
}
@keyframes contentReveal {
to {
opacity: 1;
}
}
/* Demo content styles */
.reveal-group {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.reveal-title {
font-size: clamp(1.5rem, 4vw, 2.5rem);
font-weight: 800;
color: #f1f5f9;
letter-spacing: -0.02em;
}
.reveal-subtitle {
font-size: 1.25rem;
font-weight: 600;
color: var(--box-color);
}
.reveal-text {
color: rgba(148, 163, 184, 0.8);
line-height: 1.8;
font-size: 1rem;
max-width: 560px;
}
.reveal-card {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 2rem;
}
.reveal-card h3 {
font-size: 1.25rem;
font-weight: 700;
color: #f1f5f9;
margin-bottom: 0.5rem;
}
.reveal-card p {
color: rgba(148, 163, 184, 0.8);
line-height: 1.7;
font-size: 0.95rem;
}
.card-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
}
.divider {
height: 1px;
background: rgba(255, 255, 255, 0.06);
margin: 1rem 0;
}// Box Reveal — two-step wipe animation triggered by IntersectionObserver
(function () {
"use strict";
const DEFAULTS = {
threshold: 0.2,
rootMargin: "0px 0px -20px 0px",
staggerDelay: 200,
};
function initBoxReveal() {
const elements = document.querySelectorAll(".box-reveal");
if (!elements.length) return;
// Group by parent for stagger
const groups = new Map();
elements.forEach((el) => {
const parent = el.parentElement;
if (!groups.has(parent)) {
groups.set(parent, []);
}
groups.get(parent).push(el);
});
// Apply stagger delays
groups.forEach((children) => {
children.forEach((el, i) => {
const customDelay = el.dataset.delay;
const delay = customDelay ? parseInt(customDelay, 10) : i * DEFAULTS.staggerDelay;
if (delay > 0) {
el.style.animationDelay = `${delay}ms`;
el.style.setProperty("--stagger", `${delay}ms`);
// Override the after pseudo-element animation delay
const duration = parseFloat(
getComputedStyle(el).getPropertyValue("--box-duration") || "0.7"
);
const durationMs = duration * 1000;
el.style.setProperty("--box-slide-in-delay", `${delay}ms`);
}
});
});
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const el = entry.target;
const stagger = el.style.getPropertyValue("--stagger");
if (stagger) {
setTimeout(
() => {
el.classList.add("is-visible");
},
parseInt(stagger, 10)
);
} else {
el.classList.add("is-visible");
}
observer.unobserve(el);
}
});
},
{
threshold: DEFAULTS.threshold,
rootMargin: DEFAULTS.rootMargin,
}
);
elements.forEach((el) => observer.observe(el));
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initBoxReveal);
} else {
initBoxReveal();
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Box Reveal</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<section class="hero">
<h1>Box Reveal</h1>
<p>A colored box slides in and out to reveal content</p>
<span class="scroll-hint">Scroll down to see the effect</span>
</section>
<section class="content-section">
<div class="reveal-group">
<div class="box-reveal">
<span class="box-reveal-content reveal-subtitle">Introducing</span>
</div>
<div class="box-reveal">
<h2 class="box-reveal-content reveal-title">Box Reveal Effect</h2>
</div>
<div class="box-reveal">
<p class="box-reveal-content reveal-text">
A dramatic two-step animation where a colored block sweeps across
the element, revealing polished content underneath. Perfect for
hero headlines and feature callouts.
</p>
</div>
</div>
<div class="divider"></div>
<div class="reveal-group">
<div class="box-reveal" style="--box-color: #f472b6;">
<h2 class="box-reveal-content reveal-title">Custom Colors</h2>
</div>
<div class="box-reveal" style="--box-color: #f472b6;">
<p class="box-reveal-content reveal-text">
Change the reveal color with a simple CSS variable. Match your brand,
create emphasis, or use different colors for different sections.
</p>
</div>
</div>
<div class="card-row">
<div class="box-reveal" style="--box-color: #34d399;">
<div class="box-reveal-content reveal-card">
<h3>Fast</h3>
<p>Pure CSS animations with no JavaScript overhead during the transition phase.</p>
</div>
</div>
<div class="box-reveal" style="--box-color: #fbbf24;">
<div class="box-reveal-content reveal-card">
<h3>Flexible</h3>
<p>Works with any content — text, cards, images, or entire sections.</p>
</div>
</div>
</div>
<div class="divider"></div>
<div class="reveal-group">
<div class="box-reveal" style="--box-color: #a78bfa;">
<span class="box-reveal-content reveal-subtitle">Staggered Reveals</span>
</div>
<div class="box-reveal" style="--box-color: #a78bfa;">
<h2 class="box-reveal-content reveal-title">Cascading Wipe</h2>
</div>
<div class="box-reveal" style="--box-color: #a78bfa;">
<p class="box-reveal-content reveal-text">
Elements within the same group automatically stagger their reveals,
creating a beautiful sequential wipe effect down the page.
</p>
</div>
</div>
</section>
<script src="script.js"></script>
</body>
</html>import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
interface BoxRevealProps {
children: ReactNode;
color?: string;
duration?: number;
delay?: number;
threshold?: number;
className?: string;
style?: CSSProperties;
}
export function BoxReveal({
children,
color = "#818cf8",
duration = 700,
delay = 0,
threshold = 0.2,
className = "",
style = {},
}: BoxRevealProps) {
const ref = useRef<HTMLDivElement>(null);
const [phase, setPhase] = useState<"hidden" | "covering" | "revealed">("hidden");
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setTimeout(() => {
setPhase("covering");
setTimeout(() => {
setPhase("revealed");
}, duration);
}, delay);
observer.unobserve(el);
}
},
{ threshold, rootMargin: "0px 0px -20px 0px" }
);
observer.observe(el);
return () => observer.disconnect();
}, [threshold, delay, duration]);
const durationSec = `${duration}ms`;
const ease = "cubic-bezier(0.77, 0, 0.175, 1)";
const wrapperStyle: CSSProperties = {
position: "relative",
display: "inline-block",
overflow: "hidden",
...style,
};
const contentStyle: CSSProperties = {
opacity: phase === "hidden" || phase === "covering" ? 0 : 1,
transition: "opacity 0.01s",
};
const boxStyle: CSSProperties = {
position: "absolute",
inset: 0,
background: color,
zIndex: 2,
transform: phase === "hidden" ? "scaleX(0)" : phase === "covering" ? "scaleX(1)" : "scaleX(0)",
transformOrigin: phase === "hidden" || phase === "covering" ? "left center" : "right center",
transition: `transform ${durationSec} ${ease}`,
};
return (
<div ref={ref} className={className} style={wrapperStyle}>
<div style={contentStyle}>{children}</div>
<div style={boxStyle} aria-hidden="true" />
</div>
);
}
// Demo usage
export default function BoxRevealDemo() {
return (
<div
style={{
background: "#0a0a0a",
minHeight: "300vh",
fontFamily: "system-ui, -apple-system, sans-serif",
color: "#e2e8f0",
}}
>
<section
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: "1rem",
textAlign: "center",
padding: "2rem",
}}
>
<h1
style={{
fontSize: "clamp(2rem, 5vw, 3.5rem)",
fontWeight: 800,
letterSpacing: "-0.03em",
background: "linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
}}
>
Box Reveal
</h1>
<p style={{ color: "rgba(148, 163, 184, 0.8)", fontSize: "1.125rem" }}>
A colored box slides in and out to reveal content
</p>
<span
style={{ marginTop: "2rem", color: "rgba(148, 163, 184, 0.5)", fontSize: "0.875rem" }}
>
Scroll down to see the effect
</span>
</section>
<section
style={{
maxWidth: 800,
margin: "0 auto",
padding: "4rem 2rem",
display: "flex",
flexDirection: "column",
gap: "4rem",
}}
>
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
<BoxReveal color="#818cf8" delay={0}>
<span style={{ fontSize: "1.25rem", fontWeight: 600, color: "#818cf8" }}>
Introducing
</span>
</BoxReveal>
<BoxReveal color="#818cf8" delay={200}>
<h2
style={{
fontSize: "clamp(1.5rem, 4vw, 2.5rem)",
fontWeight: 800,
color: "#f1f5f9",
letterSpacing: "-0.02em",
}}
>
Box Reveal Effect
</h2>
</BoxReveal>
<BoxReveal color="#818cf8" delay={400}>
<p style={{ color: "rgba(148, 163, 184, 0.8)", lineHeight: 1.8, maxWidth: 560 }}>
A dramatic two-step animation where a colored block sweeps across the element,
revealing polished content underneath.
</p>
</BoxReveal>
</div>
<div style={{ height: 1, background: "rgba(255,255,255,0.06)" }} />
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
<BoxReveal color="#f472b6">
<h2
style={{ fontSize: "clamp(1.5rem, 4vw, 2.5rem)", fontWeight: 800, color: "#f1f5f9" }}
>
Custom Colors
</h2>
</BoxReveal>
<BoxReveal color="#f472b6" delay={200}>
<p style={{ color: "rgba(148, 163, 184, 0.8)", lineHeight: 1.8, maxWidth: 560 }}>
Change the reveal color with a simple prop. Match your brand, create emphasis, or use
different colors for different sections.
</p>
</BoxReveal>
</div>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))",
gap: "1.5rem",
}}
>
<BoxReveal color="#34d399">
<div
style={{
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.08)",
borderRadius: 16,
padding: "2rem",
}}
>
<h3
style={{
fontSize: "1.25rem",
fontWeight: 700,
color: "#f1f5f9",
marginBottom: "0.5rem",
}}
>
Fast
</h3>
<p style={{ color: "rgba(148,163,184,0.8)", lineHeight: 1.7, fontSize: "0.95rem" }}>
Pure CSS animations with no JavaScript overhead during the transition.
</p>
</div>
</BoxReveal>
<BoxReveal color="#fbbf24" delay={200}>
<div
style={{
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.08)",
borderRadius: 16,
padding: "2rem",
}}
>
<h3
style={{
fontSize: "1.25rem",
fontWeight: 700,
color: "#f1f5f9",
marginBottom: "0.5rem",
}}
>
Flexible
</h3>
<p style={{ color: "rgba(148,163,184,0.8)", lineHeight: 1.7, fontSize: "0.95rem" }}>
Works with any content — text, cards, images, or entire sections.
</p>
</div>
</BoxReveal>
</div>
<div style={{ height: 1, background: "rgba(255,255,255,0.06)" }} />
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
<BoxReveal color="#a78bfa">
<span style={{ fontSize: "1.25rem", fontWeight: 600, color: "#a78bfa" }}>
Staggered Reveals
</span>
</BoxReveal>
<BoxReveal color="#a78bfa" delay={200}>
<h2
style={{ fontSize: "clamp(1.5rem, 4vw, 2.5rem)", fontWeight: 800, color: "#f1f5f9" }}
>
Cascading Wipe
</h2>
</BoxReveal>
<BoxReveal color="#a78bfa" delay={400}>
<p style={{ color: "rgba(148, 163, 184, 0.8)", lineHeight: 1.8, maxWidth: 560 }}>
Elements within the same group automatically stagger their reveals, creating a
beautiful sequential wipe effect down the page.
</p>
</BoxReveal>
</div>
</section>
</div>
);
}<script setup>
import { ref, computed, onMounted, onUnmounted } from "vue";
const props = defineProps({
color: { type: String, default: "#818cf8" },
duration: { type: Number, default: 700 },
delay: { type: Number, default: 0 },
threshold: { type: Number, default: 0.2 },
});
const wrapperRef = ref(null);
const phase = ref("hidden");
let observer = null;
const durationMs = computed(() => `${props.duration}ms`);
const ease = "cubic-bezier(0.77, 0, 0.175, 1)";
const contentOpacity = computed(() =>
phase.value === "hidden" || phase.value === "covering" ? 0 : 1
);
const boxTransform = computed(() =>
phase.value === "hidden" ? "scaleX(0)" : phase.value === "covering" ? "scaleX(1)" : "scaleX(0)"
);
const boxOrigin = computed(() =>
phase.value === "hidden" || phase.value === "covering" ? "left center" : "right center"
);
onMounted(() => {
const el = wrapperRef.value;
if (!el) return;
observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setTimeout(() => {
phase.value = "covering";
setTimeout(() => {
phase.value = "revealed";
}, props.duration);
}, props.delay);
observer.unobserve(el);
}
},
{ threshold: props.threshold, rootMargin: "0px 0px -20px 0px" }
);
observer.observe(el);
});
onUnmounted(() => {
if (observer) observer.disconnect();
});
</script>
<template>
<div class="demo">
<section class="hero">
<h1>Box Reveal</h1>
<p>A colored box slides in and out to reveal content</p>
<span class="scroll-hint">Scroll down to see the effect</span>
</section>
<section class="sections">
<div class="group">
<div ref="wrapperRef" class="wrapper">
<div :style="{ opacity: contentOpacity, transition: 'opacity 0.01s' }">
<span :style="{ fontSize: '1.25rem', fontWeight: 600, color: props.color }">Introducing</span>
</div>
<div
class="box"
aria-hidden="true"
:style="{
background: props.color,
transform: boxTransform,
transformOrigin: boxOrigin,
transition: `transform ${durationMs} ${ease}`
}"
/>
</div>
<div class="wrapper">
<div :style="{ opacity: contentOpacity, transition: 'opacity 0.01s' }">
<h2 class="reveal-title">Box Reveal Effect</h2>
</div>
<div
class="box"
aria-hidden="true"
:style="{
background: props.color,
transform: boxTransform,
transformOrigin: boxOrigin,
transition: `transform ${durationMs} ${ease}`
}"
/>
</div>
<div class="wrapper">
<div :style="{ opacity: contentOpacity, transition: 'opacity 0.01s' }">
<p class="reveal-desc">
A dramatic two-step animation where a colored block sweeps across
the element, revealing polished content underneath.
</p>
</div>
<div
class="box"
aria-hidden="true"
:style="{
background: props.color,
transform: boxTransform,
transformOrigin: boxOrigin,
transition: `transform ${durationMs} ${ease}`
}"
/>
</div>
</div>
<div class="divider" />
<div class="group">
<h2 class="reveal-title">Custom Colors</h2>
<p class="reveal-desc">
Change the reveal color with a simple prop. Match your brand,
create emphasis, or use different colors for different sections.
</p>
</div>
<div class="grid">
<div class="card">
<h3>Fast</h3>
<p>Pure CSS animations with no JavaScript overhead during the transition.</p>
</div>
<div class="card">
<h3>Flexible</h3>
<p>Works with any content — text, cards, images, or entire sections.</p>
</div>
</div>
<div class="divider" />
<div class="group">
<span style="font-size: 1.25rem; font-weight: 600; color: #a78bfa;">Staggered Reveals</span>
<h2 class="reveal-title">Cascading Wipe</h2>
<p class="reveal-desc">
Elements within the same group automatically stagger their reveals,
creating a beautiful sequential wipe effect down the page.
</p>
</div>
</section>
</div>
</template>
<style scoped>
.wrapper {
position: relative;
display: inline-block;
overflow: hidden;
}
.box {
position: absolute;
inset: 0;
z-index: 2;
}
.demo {
background: #0a0a0a;
min-height: 300vh;
font-family: system-ui, -apple-system, sans-serif;
color: #e2e8f0;
}
.hero {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
text-align: center;
padding: 2rem;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
color: rgba(148, 163, 184, 0.8);
font-size: 1.125rem;
}
.scroll-hint {
margin-top: 2rem;
color: rgba(148, 163, 184, 0.5);
font-size: 0.875rem;
}
.sections {
max-width: 800px;
margin: 0 auto;
padding: 4rem 2rem;
display: flex;
flex-direction: column;
gap: 4rem;
}
.group {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.divider {
height: 1px;
background: rgba(255,255,255,0.06);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
}
.card {
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 16px;
padding: 2rem;
}
.card h3 {
font-size: 1.25rem;
font-weight: 700;
color: #f1f5f9;
margin-bottom: 0.5rem;
}
.card p {
color: rgba(148,163,184,0.8);
line-height: 1.7;
font-size: 0.95rem;
}
.reveal-title {
font-size: clamp(1.5rem, 4vw, 2.5rem);
font-weight: 800;
color: #f1f5f9;
letter-spacing: -0.02em;
}
.reveal-desc {
color: rgba(148, 163, 184, 0.8);
line-height: 1.8;
max-width: 560px;
}
</style><script>
import { onMount, onDestroy } from "svelte";
let wrapperEl;
let phase = "hidden";
export let color = "#818cf8";
export let duration = 700;
export let delay = 0;
export let threshold = 0.2;
let observer;
onMount(() => {
observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setTimeout(() => {
phase = "covering";
setTimeout(() => {
phase = "revealed";
}, duration);
}, delay);
observer.unobserve(wrapperEl);
}
},
{ threshold, rootMargin: "0px 0px -20px 0px" }
);
observer.observe(wrapperEl);
});
onDestroy(() => {
if (observer) observer.disconnect();
});
$: durationMs = `${duration}ms`;
$: ease = "cubic-bezier(0.77, 0, 0.175, 1)";
$: contentOpacity = phase === "hidden" || phase === "covering" ? 0 : 1;
$: boxTransform =
phase === "hidden" ? "scaleX(0)" : phase === "covering" ? "scaleX(1)" : "scaleX(0)";
$: boxOrigin = phase === "hidden" || phase === "covering" ? "left center" : "right center";
</script>
<style>
.wrapper {
position: relative;
display: inline-block;
overflow: hidden;
}
.box {
position: absolute;
inset: 0;
z-index: 2;
}
.demo {
background: #0a0a0a;
min-height: 300vh;
font-family: system-ui, -apple-system, sans-serif;
color: #e2e8f0;
}
.hero {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
text-align: center;
padding: 2rem;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
color: rgba(148, 163, 184, 0.8);
font-size: 1.125rem;
}
.hero .scroll-hint {
margin-top: 2rem;
color: rgba(148, 163, 184, 0.5);
font-size: 0.875rem;
}
.sections {
max-width: 800px;
margin: 0 auto;
padding: 4rem 2rem;
display: flex;
flex-direction: column;
gap: 4rem;
}
.group {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.divider {
height: 1px;
background: rgba(255,255,255,0.06);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.5rem;
}
.card {
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 16px;
padding: 2rem;
}
.card h3 {
font-size: 1.25rem;
font-weight: 700;
color: #f1f5f9;
margin-bottom: 0.5rem;
}
.card p {
color: rgba(148,163,184,0.8);
line-height: 1.7;
font-size: 0.95rem;
}
</style>
<div class="demo">
<section class="hero">
<h1>Box Reveal</h1>
<p>A colored box slides in and out to reveal content</p>
<span class="scroll-hint">Scroll down to see the effect</span>
</section>
<section class="sections">
<div class="group">
<div class="wrapper" bind:this={wrapperEl}>
<div style="opacity: {contentOpacity}; transition: opacity 0.01s;">
<span style="font-size: 1.25rem; font-weight: 600; color: {color};">Introducing</span>
</div>
<div
class="box"
aria-hidden="true"
style="background: {color}; transform: {boxTransform}; transform-origin: {boxOrigin}; transition: transform {durationMs} {ease};"
></div>
</div>
<!-- Additional reveal blocks for demo are simplified here -->
<div class="wrapper" style="position: relative; display: inline-block; overflow: hidden;">
<div style="opacity: {contentOpacity}; transition: opacity 0.01s;">
<h2 style="font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: 800; color: #f1f5f9; letter-spacing: -0.02em;">
Box Reveal Effect
</h2>
</div>
<div
class="box"
aria-hidden="true"
style="background: {color}; transform: {boxTransform}; transform-origin: {boxOrigin}; transition: transform {durationMs} {ease};"
></div>
</div>
<div class="wrapper" style="position: relative; display: inline-block; overflow: hidden;">
<div style="opacity: {contentOpacity}; transition: opacity 0.01s;">
<p style="color: rgba(148, 163, 184, 0.8); line-height: 1.8; max-width: 560px;">
A dramatic two-step animation where a colored block sweeps across
the element, revealing polished content underneath.
</p>
</div>
<div
class="box"
aria-hidden="true"
style="background: {color}; transform: {boxTransform}; transform-origin: {boxOrigin}; transition: transform {durationMs} {ease};"
></div>
</div>
</div>
<div class="divider"></div>
<div class="group">
<h2 style="font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: 800; color: #f1f5f9;">
Custom Colors
</h2>
<p style="color: rgba(148, 163, 184, 0.8); line-height: 1.8; max-width: 560px;">
Change the reveal color with a simple prop. Match your brand,
create emphasis, or use different colors for different sections.
</p>
</div>
<div class="grid">
<div class="card">
<h3>Fast</h3>
<p>Pure CSS animations with no JavaScript overhead during the transition.</p>
</div>
<div class="card">
<h3>Flexible</h3>
<p>Works with any content — text, cards, images, or entire sections.</p>
</div>
</div>
<div class="divider"></div>
<div class="group">
<span style="font-size: 1.25rem; font-weight: 600; color: #a78bfa;">Staggered Reveals</span>
<h2 style="font-size: clamp(1.5rem, 4vw, 2.5rem); font-weight: 800; color: #f1f5f9;">Cascading Wipe</h2>
<p style="color: rgba(148, 163, 184, 0.8); line-height: 1.8; max-width: 560px;">
Elements within the same group automatically stagger their reveals,
creating a beautiful sequential wipe effect down the page.
</p>
</div>
</section>
</div>Box Reveal
A striking two-step wipe animation where a colored box slides in from one side to cover the area, then slides out the other direction to reveal the content underneath.
How it works
- Content starts hidden (
opacity: 0) behind a pseudo-element - When the element enters the viewport, the pseudo-element slides in from the left covering the area
- At the midpoint, the content becomes visible and the pseudo-element slides out to the right
- The result is a clean, dramatic reveal effect
Customization
- Change the
--box-colorCSS variable to match your brand accent - Adjust
--box-durationfor faster or slower reveals - Use
data-directionattribute for different slide directions - Stagger multiple reveals with
data-delay
When to use it
- Hero section headlines
- Feature introductions
- Section transitions
- Any text or content that deserves dramatic emphasis