SectionsAdvancedApril 27, 2026
Slide Pillars
A scroll-driven services section with cycling headings, animated counter with progress bar, and staggered service lists that slide in and out per pillar. Pinned full-viewport layout with left info column and right service list column.
View Full Demo →Preview
Research
Strategy
Design
Development
Content
00
1
2
3
4
5
005
We build beautiful digital products and experiences for brands, organisations and agencies. We're obsessed with technology and harness our skills in research and strategy, branding, UX/UI, and development to create impactful web experiences that drive value for our clients and their customers.
Customer Research
Trends Analysis
Competitor Review
Best Practice Review
Usability Research & Testing
Market Research
Analytics Reports
Product Ideation
Brand Positioning & Architecture
Brand Naming & Strategy
Target Audience Discovery
Customer Journey Mapping
Feature Definition
Information Architecture
Usability Audit & Review
Post-Launch Strategy
Art Direction
Brand Identity Design
Design Systems
Graphic Design
Wireframing
User Interface Design
User Experience Design
Interaction Design
3D Design
Motion Design
Digital Product Design
Prototyping
Packaging
Quality Assurance
Technical Strategy
Technical Consulting
CMS Implementation
React / Next.js Development
WebGL / 3D Development
Shopify Development
WordPress Development
Webflow Development
Cross-browser Testing
Cross-device Testing
SEO & Performance Optimisation
Quality Assurance
Copywriting
SEO Copy Analysis & Refinement
Thematic Keyword Research
Social Media Design
Content Management
Source
demo.jsx
import SlidePillars from "./index.jsx";
import { slidePillars } from "../demo-data.js";
export default function SlidePillarsDemo() {
return <SlidePillars {...slidePillars} />;
}
index.jsx
"use client";
import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
import styles from "./styles.module.css";
gsap.registerPlugin(ScrollTrigger);
export default function SlidePillars({
pillars = [],
description = "",
ctaLabel = "Our Services",
ctaHref = "/services",
}) {
const sectionRef = useRef(null);
const headingsRef = useRef(null);
const numsRef = useRef(null);
const progressRef = useRef(null);
const listsRef = useRef(null);
const total = pillars.length;
const totalFormatted = String(total).padStart(3, "0");
useEffect(() => {
if (!pillars.length) return;
const ctx = gsap.context(() => {
const steps = pillars.length;
const headingsEl = headingsRef.current;
const numsEl = numsRef.current;
const progressEl = progressRef.current;
const section = sectionRef.current;
if (!headingsEl || !numsEl || !progressEl) return;
// Main scroll-driven timeline
const tl = gsap.timeline({
scrollTrigger: {
trigger: section,
start: "top top",
end: `+=${steps * 100}%`,
pin: true,
scrub: 0.5,
},
});
// Headings cycle
tl.to(headingsEl, {
yPercent: -((steps - 1) / steps) * 100,
ease: "none",
}, 0);
// Counter numbers cycle
tl.to(numsEl, {
yPercent: -((steps - 1) / steps) * 100,
ease: "none",
}, 0);
// Progress bar
tl.fromTo(progressEl, { xPercent: -100 }, { xPercent: 0, ease: "none" }, 0);
// Right column vertical scroll, per-segment
const listWraps = section.querySelectorAll(`.${styles.listWrap}`);
const listsContainer = listsRef.current;
if (listsContainer && listWraps.length) {
const seg = 1 / steps;
let cumulativeY = 0;
for (let i = 0; i < steps - 1; i++) {
const targetY = cumulativeY + listWraps[i].offsetHeight;
tl.to(listsContainer, { y: -targetY, ease: "none" }, i * seg);
cumulativeY = targetY;
}
}
}, sectionRef);
return () => ctx.revert();
}, [pillars]);
return (
<section ref={sectionRef} className={styles.section}>
<div className={styles.inner}>
{/* Left column */}
<div className={styles.column}>
<header className={styles.headingsWrap}>
<div ref={headingsRef} className={styles.headings}>
{pillars.map((p, i) => (
<h2 key={i} className={styles.heading}>
{p.title}
</h2>
))}
</div>
</header>
<div className={styles.infoWrap}>
<div className={styles.counterWrap}>
<div className={styles.counter}>
<div className={styles.numWrap}>
<div>00</div>
<div ref={numsRef} className={styles.nums}>
{pillars.map((_, i) => (
<div key={i} className={styles.num}>
{i + 1}
</div>
))}
</div>
</div>
<div className={styles.progressWrap}>
<div ref={progressRef} className={styles.progress} />
</div>
</div>
<div className={styles.countTotal}>{totalFormatted}</div>
</div>
<p className={styles.paragraph}>{description}</p>
<a className={styles.button} href={ctaHref}>
<span className={styles.buttonText}>{ctaLabel}</span>
</a>
</div>
</div>
{/* Right column */}
<div className={`${styles.column} ${styles.columnRight}`}>
<div ref={listsRef} className={styles.listsContainer}>
{pillars.map((p, i) => (
<div key={i} className={styles.listWrap}>
{p.services.map((service, j) => (
<h3 key={j} className={styles.listItem}>
{service}
</h3>
))}
<div className={styles.separatorWrap}>
<div className={styles.separator} />
</div>
</div>
))}
</div>
</div>
</div>
</section>
);
}
styles.module.css
.section {
position: relative;
padding: 4vw 0;
background-color: #bcbcb4;
color: #2a2a2a;
}
.inner {
display: flex;
gap: 0;
padding: 0 2.5vw;
height: calc(100dvh - 8vw);
align-items: stretch;
}
/* ---- Columns ---- */
.column {
display: flex;
flex-direction: column;
}
.column:first-child {
flex: 0 0 48%;
max-width: 48%;
justify-content: space-between;
padding: 2vw 2vw 2vw 0;
}
.columnRight {
flex: 1;
position: relative;
overflow: hidden;
justify-content: center;
}
/* ---- Headings ---- */
.headingsWrap {
height: clamp(3.5rem, 8vw, 7rem);
overflow: hidden;
position: relative;
}
.headings {
display: flex;
flex-direction: column;
}
.heading {
font-size: clamp(3rem, 7vw, 6rem);
font-weight: 400;
line-height: 1;
letter-spacing: -0.02em;
text-transform: uppercase;
height: clamp(3.5rem, 8vw, 7rem);
display: flex;
align-items: center;
}
/* ---- Info wrap ---- */
.infoWrap {
display: flex;
flex-direction: column;
gap: 1.5rem;
max-width: 500px;
}
/* ---- Counter ---- */
.counterWrap {
display: flex;
align-items: center;
gap: 0;
}
.counter {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
}
.numWrap {
display: flex;
align-items: baseline;
gap: 0;
font-size: clamp(0.85rem, 1.1vw, 1rem);
font-variant-numeric: tabular-nums;
overflow: hidden;
height: 1.2em;
}
.nums {
display: flex;
flex-direction: column;
}
.num {
height: 1.2em;
display: flex;
align-items: center;
}
.progressWrap {
flex: 1;
height: 1px;
background: rgba(0, 0, 0, 0.15);
overflow: hidden;
}
.progress {
width: 100%;
height: 100%;
background: #2a2a2a;
transform: translateX(-100%);
}
.countTotal {
font-size: clamp(0.85rem, 1.1vw, 1rem);
font-variant-numeric: tabular-nums;
}
/* ---- Paragraph ---- */
.paragraph {
font-size: clamp(0.85rem, 1vw, 0.95rem);
line-height: 1.55;
}
/* ---- Button ---- */
.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75em 1.8em;
background: #2a2a2a;
border: none;
border-radius: 999px;
font-size: clamp(0.75rem, 0.9vw, 0.875rem);
text-decoration: none;
color: #fff;
text-transform: uppercase;
letter-spacing: 0.02em;
width: fit-content;
transition: opacity 0.3s ease;
}
.button:hover {
opacity: 0.8;
}
.buttonText {
position: relative;
}
/* ---- Right column — lists ---- */
.listsContainer {
display: flex;
flex-direction: column;
will-change: transform;
}
.listWrap {
display: flex;
flex-direction: column;
padding: 0 2vw;
}
.listItem {
font-size: clamp(1.1rem, 2.2vw, 1.75rem);
font-weight: 400;
line-height: 1;
padding: 0.5em 0;
text-transform: uppercase;
will-change: transform;
}
.separatorWrap {
padding-top: 1em;
overflow: hidden;
}
.separator {
height: 1px;
background: rgba(0, 0, 0, 0.2);
width: 100%;
will-change: transform;
}
/* ---- Mobile ---- */
@media (max-width: 768px) {
.inner {
flex-direction: column;
padding: 0 1.25rem;
height: calc(100dvh - 8vw);
}
.column:first-child {
flex: none;
max-width: 100%;
padding: 1.5rem 0 0;
}
.columnRight {
flex: 1;
min-height: 40vh;
}
.headingsWrap {
height: clamp(2.5rem, 10vw, 4rem);
}
.heading {
font-size: clamp(2rem, 9vw, 3.5rem);
height: clamp(2.5rem, 10vw, 4rem);
}
.listItem {
font-size: clamp(0.9rem, 4vw, 1.25rem);
}
.infoWrap {
gap: 1rem;
padding-bottom: 1.5rem;
}
}
Dependencies
gsap