Case Study Carousel

Horizontal case-study carousel. Carousel Desktop (content + image card) is used on /, /work, and /about. Carousel Mobile (image-dominant card with title overlay and success-metric sticker) renders below the lg breakpoint on those surfaces and in the home hero's right column.

Carousel Desktop

Default layout (variant="full"). 960px-wide cards with content on the left and image on the right. Shown with mock data.

Carousel Mobile

Compact image-dominated cards (variant="hero"). Used as the mobile layout for the case-studies module and inside the home-page hero's right column. Keeps the same rotating border animation on the active card.

Loading skeleton — desktop

Shimmer state that mirrors the desktop carousel's active card. Used by WorkCarousel as a fade-in overlay during category filter transitions so the carousel never flash-swaps.

Loading skeleton — mobile

Mirrors the mobile hero card — aspect-square shimmer plus dots and arrow-button placeholders below.

Filter-aware wrapper (WorkCarousel)

WorkCarousel is the client wrapper used on /work. It reads the ?category= URL param (written by CategoryFilters), filters the case studies client-side, and fades the skeleton overlay in for 500ms during the transition so new cards never flash-replace old ones. If a filter produces zero matches, it renders a centered empty-state message instead.

View full version

Props

caseStudies

CaseStudy[] (required)

Array of case study data from Sanity CMS.

variant

"full" | "hero" (default "full")

"full" is the responsive Case Studies module: Carousel Mobile below lg, Carousel Desktop at lg+. "hero" forces Carousel Mobile at all widths — used inside the home-page hero, wrapped in a lg:block parent so it only shows on desktop.

title

string (default: "Case Studies", ignored when forced to hero)

Section heading. Set to empty string to hide. Hero-forced renders never draw a title.

subtitle

string (optional)

Description text below heading. Full variant only.

showViewAll

boolean (default false)

Full variant only. Renders a "View all case studies" button below the carousel.

Card styling

.card-carousel-item

Gradient from accent-subtle to surface, subtle border. Shared by both variants.

.card-carousel-active

Rotating conic-gradient border animation applied only to the active (centered) card in both variants.

Shimmer primitive

Every shimmer bar in the skeleton is a local `ShimmerBar` — a rounded div with `bg-foreground/10 animate-pulse`. Colors adapt to light / dark theme via CSS variables.

tsx
function ShimmerBar({ className }: { className?: string }) {
  return (
    <div
      className={cn(
        'rounded-sm bg-foreground/10 animate-pulse',
        className
      )}
    />
  )
}

Usage

tsx
import { CaseStudyCarousel } from '@/components/sections/CaseStudyCarousel'
import { CaseStudyGridSkeleton } from '@/components/sections/CaseStudyGridSkeleton'

// Full carousel on /work and /home
<CaseStudyCarousel caseStudies={caseStudies} showViewAll subtitle="…" />

// Compact hero variant — wrap in an lg:block container
<div className="hidden w-full lg:block lg:max-w-[540px]">
  <CaseStudyCarousel caseStudies={caseStudies} variant="hero" />
</div>

// Loading skeleton (auto-picks variant by viewport)
<CaseStudyGridSkeleton />

Import

tsx
import { CaseStudyCarousel } from '@/components/sections/CaseStudyCarousel'
import { CaseStudyGridSkeleton } from '@/components/sections/CaseStudyGridSkeleton'
import { WorkCarousel } from '@/components/sections/WorkCarousel'

Related files

src/components/sections/CaseStudyCarousel.tsxsrc/components/sections/CaseStudyGridSkeleton.tsxsrc/components/sections/WorkCarousel.tsxsanity/schemas/caseStudy.tssrc/lib/queries.ts