Web InnoventixFreeCode

Inset Frame Effect

Original · free

A thin inset frame draws inside the image on hover.

byWeb InnoventixReact + Tailwind
insetframeimageeffect
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-inset-frame.json
imgfx-inset-frame.tsx
"use client";

export default function ImgfxInsetFrame() {
  return (
    <section className="relative w-full bg-neutral-50 px-6 py-20 dark:bg-neutral-950 sm:px-10 sm:py-28">
      <style>{`
        .imgfx_insetframe_card {
          --imgfx-insetframe-gap: 0.875rem;
          --imgfx-insetframe-width: 1px;
        }
        .imgfx_insetframe_line {
          position: absolute;
          background-color: rgba(255, 255, 255, 0.92);
          box-shadow: 0 0 0.5px rgba(0, 0, 0, 0.25);
          opacity: 0;
          transition: opacity 0.5s ease;
        }
        .imgfx_insetframe_line--top,
        .imgfx_insetframe_line--bottom {
          left: var(--imgfx-insetframe-gap);
          right: var(--imgfx-insetframe-gap);
          height: var(--imgfx-insetframe-width);
          transform: scaleX(0);
          transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.35s ease;
        }
        .imgfx_insetframe_line--left,
        .imgfx_insetframe_line--right {
          top: var(--imgfx-insetframe-gap);
          bottom: var(--imgfx-insetframe-gap);
          width: var(--imgfx-insetframe-width);
          transform: scaleY(0);
          transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1) 0.08s, opacity 0.35s ease;
        }
        .imgfx_insetframe_line--top {
          top: var(--imgfx-insetframe-gap);
          transform-origin: left center;
        }
        .imgfx_insetframe_line--bottom {
          bottom: var(--imgfx-insetframe-gap);
          transform-origin: right center;
        }
        .imgfx_insetframe_line--left {
          left: var(--imgfx-insetframe-gap);
          transform-origin: center top;
        }
        .imgfx_insetframe_line--right {
          right: var(--imgfx-insetframe-gap);
          transform-origin: center bottom;
        }
        .imgfx_insetframe_media {
          transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
        }
        .imgfx_insetframe_card:hover .imgfx_insetframe_line,
        .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line {
          opacity: 1;
        }
        .imgfx_insetframe_card:hover .imgfx_insetframe_line--top,
        .imgfx_insetframe_card:hover .imgfx_insetframe_line--bottom,
        .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--top,
        .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--bottom {
          transform: scaleX(1);
        }
        .imgfx_insetframe_card:hover .imgfx_insetframe_line--left,
        .imgfx_insetframe_card:hover .imgfx_insetframe_line--right,
        .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--left,
        .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--right {
          transform: scaleY(1);
        }
        .imgfx_insetframe_card:hover .imgfx_insetframe_media,
        .imgfx_insetframe_card:focus-visible .imgfx_insetframe_media {
          transform: scale(1.045);
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfx_insetframe_line,
          .imgfx_insetframe_media {
            transition: opacity 0.2s ease !important;
          }
          .imgfx_insetframe_card:hover .imgfx_insetframe_line--top,
          .imgfx_insetframe_card:hover .imgfx_insetframe_line--bottom,
          .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--top,
          .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--bottom {
            transform: scaleX(1);
          }
          .imgfx_insetframe_card:hover .imgfx_insetframe_line--left,
          .imgfx_insetframe_card:hover .imgfx_insetframe_line--right,
          .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--left,
          .imgfx_insetframe_card:focus-visible .imgfx_insetframe_line--right {
            transform: scaleY(1);
          }
          .imgfx_insetframe_card:hover .imgfx_insetframe_media,
          .imgfx_insetframe_card:focus-visible .imgfx_insetframe_media {
            transform: none;
          }
        }
      `}</style>

      <div className="mx-auto max-w-2xl text-center">
        <p className="text-xs font-semibold uppercase tracking-[0.22em] text-indigo-600 dark:text-indigo-400">
          Inset Frame
        </p>
        <h2 className="mt-3 text-3xl font-semibold tracking-tight text-neutral-900 dark:text-neutral-50 sm:text-4xl">
          A thin line draws itself inside the photo
        </h2>
        <p className="mt-4 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
          Hover the image and a hairline frame traces the edges from within, quietly gallery-mounting the shot.
        </p>
      </div>

      <div className="mx-auto mt-14 max-w-3xl">
        <button
          type="button"
          aria-label="Coastal dunes at first light — hover to reveal an inset hairline frame"
          className="imgfx_insetframe_card group relative block w-full overflow-hidden rounded-2xl bg-neutral-200 outline-none ring-offset-4 ring-offset-neutral-50 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:bg-neutral-800 dark:ring-offset-neutral-950"
        >
          <div className="relative aspect-[4/3] w-full overflow-hidden">
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img
              src="/img/gallery/g14.webp"
              alt="Wind-carved coastal dunes catching soft light at dawn"
              loading="lazy"
              draggable={false}
              className="imgfx_insetframe_media h-full w-full object-cover"
            />
            <span className="imgfx_insetframe_line imgfx_insetframe_line--top" aria-hidden="true" />
            <span className="imgfx_insetframe_line imgfx_insetframe_line--right" aria-hidden="true" />
            <span className="imgfx_insetframe_line imgfx_insetframe_line--bottom" aria-hidden="true" />
            <span className="imgfx_insetframe_line imgfx_insetframe_line--left" aria-hidden="true" />
          </div>
        </button>

        <p className="mt-5 text-center text-xs font-medium uppercase tracking-[0.18em] text-neutral-500 dark:text-neutral-500">
          Hover or focus the photo
        </p>
      </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 →