Web InnoventixFreeCode

Split Bars Effect

Original · free

Two horizontal bars slide apart to reveal a title on hover.

byWeb InnoventixReact + Tailwind
splitbarsimageeffect
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/imgfx-split-bars.json
imgfx-split-bars.tsx
"use client";

import { useId } from "react";

type SplitBarsCard = {
  src: string;
  alt: string;
  eyebrow: string;
  title: string;
};

const CARDS: readonly SplitBarsCard[] = [
  {
    src: "/img/gallery/g07.webp",
    alt: "Morning light spilling across a quiet mountain ridgeline",
    eyebrow: "Field notes",
    title: "High Ground",
  },
  {
    src: "/img/gallery/g19.webp",
    alt: "Neon-lit street corner reflected in rain-slicked pavement",
    eyebrow: "After hours",
    title: "Night Signal",
  },
  {
    src: "/img/gallery/g28.webp",
    alt: "Calm coastline where pale water meets soft evening sky",
    eyebrow: "Long exposure",
    title: "Still Tide",
  },
];

export default function ImgfxSplitBars() {
  const rawId = useId();
  const prefix = `sbfx-${rawId.replace(/[^a-zA-Z0-9]/g, "")}`;

  return (
    <section className="relative w-full bg-neutral-50 px-6 py-20 dark:bg-neutral-950 sm:px-10 sm:py-28">
      <style>{`
        .${prefix}-card {
          --${prefix}-ease: cubic-bezier(0.22, 1, 0.36, 1);
        }
        .${prefix}-bar,
        .${prefix}-title,
        .${prefix}-img {
          transition:
            transform 0.6s var(--${prefix}-ease),
            opacity 0.5s var(--${prefix}-ease),
            filter 0.6s var(--${prefix}-ease),
            letter-spacing 0.6s var(--${prefix}-ease);
        }
        .${prefix}-bar-top { transform: translateY(0); }
        .${prefix}-bar-bottom { transform: translateY(0); }
        .${prefix}-title {
          opacity: 0;
          transform: translateY(0.75rem);
          letter-spacing: 0.35em;
        }
        .${prefix}-img {
          transform: scale(1.08);
          filter: saturate(0.7) brightness(0.82);
        }
        .${prefix}-card:hover .${prefix}-bar-top,
        .${prefix}-card:focus-within .${prefix}-bar-top {
          transform: translateY(-115%);
        }
        .${prefix}-card:hover .${prefix}-bar-bottom,
        .${prefix}-card:focus-within .${prefix}-bar-bottom {
          transform: translateY(115%);
        }
        .${prefix}-card:hover .${prefix}-title,
        .${prefix}-card:focus-within .${prefix}-title {
          opacity: 1;
          transform: translateY(0);
          letter-spacing: 0.18em;
        }
        .${prefix}-card:hover .${prefix}-img,
        .${prefix}-card:focus-within .${prefix}-img {
          transform: scale(1);
          filter: saturate(1) brightness(1);
        }
        @media (prefers-reduced-motion: reduce) {
          .${prefix}-bar,
          .${prefix}-title,
          .${prefix}-img {
            transition: opacity 0.2s linear;
          }
          .${prefix}-card:hover .${prefix}-bar-top,
          .${prefix}-card:focus-within .${prefix}-bar-top,
          .${prefix}-card:hover .${prefix}-bar-bottom,
          .${prefix}-card:focus-within .${prefix}-bar-bottom {
            transform: none;
            opacity: 0;
          }
          .${prefix}-title { transform: none; letter-spacing: 0.18em; }
          .${prefix}-img { transform: none; filter: none; }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        <div className="mb-14 text-center">
          <p className="text-xs font-semibold uppercase tracking-[0.4em] text-indigo-600 dark:text-indigo-400">
            Image Effect
          </p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-neutral-900 dark:text-neutral-50 sm:text-4xl">
            Split Bars Reveal
          </h2>
          <p className="mt-4 text-sm text-neutral-500 dark:text-neutral-400">
            Two bars slide apart to unveil the title. Hover a frame &mdash; or tab to it.
          </p>
        </div>

        <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
          {CARDS.map((card) => (
            <article
              key={card.src}
              tabIndex={0}
              aria-label={`${card.title} — ${card.eyebrow}`}
              className={`${prefix}-card group relative aspect-[4/5] cursor-pointer overflow-hidden rounded-2xl bg-neutral-900 shadow-lg shadow-neutral-900/10 outline-none ring-1 ring-neutral-200/60 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:ring-white/10`}
            >
              {/* eslint-disable-next-line @next/next/no-img-element */}
              <img
                src={card.src}
                alt={card.alt}
                loading="lazy"
                draggable={false}
                className={`${prefix}-img absolute inset-0 h-full w-full object-cover`}
              />

              {/* centred title revealed between the bars */}
              <div className="pointer-events-none absolute inset-0 z-20 flex flex-col items-center justify-center px-6 text-center">
                <span
                  className={`${prefix}-title text-lg font-semibold uppercase text-white sm:text-xl`}
                  style={{ textShadow: "0 2px 18px rgba(0,0,0,0.55)" }}
                >
                  {card.title}
                </span>
              </div>

              {/* top bar */}
              <div
                className={`${prefix}-bar ${prefix}-bar-top pointer-events-none absolute inset-x-0 top-0 z-10 flex h-1/2 items-start justify-between bg-neutral-950/85 px-5 pt-5 backdrop-blur-[2px] dark:bg-neutral-950/90`}
              >
                <span className="text-[0.65rem] font-semibold uppercase tracking-[0.3em] text-indigo-300">
                  {card.eyebrow}
                </span>
                <span className="h-2 w-2 rounded-full bg-indigo-400/80" aria-hidden="true" />
              </div>

              {/* bottom bar */}
              <div
                className={`${prefix}-bar ${prefix}-bar-bottom pointer-events-none absolute inset-x-0 bottom-0 z-10 flex h-1/2 items-end justify-between bg-neutral-950/85 px-5 pb-5 backdrop-blur-[2px] dark:bg-neutral-950/90`}
              >
                <span className="text-[0.65rem] font-medium uppercase tracking-[0.3em] text-neutral-400">
                  Hover to reveal
                </span>
                <span className="h-px w-10 bg-white/40" aria-hidden="true" />
              </div>
            </article>
          ))}
        </div>
      </div>
    </section>
  );
}

Dependencies

None (React + Tailwind only).

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →