All components

Image Zoom

Image zoom is a component that allows you to zoom in on an image.

React
Open in New Tab

components/shadcncraft/pro-ecommerce/image-zoom.tsx

"use client";

import Zoom, {
  Controlled,
  type ControlledProps,
  type UncontrolledProps,
} from "react-medium-image-zoom";

import { cn } from "@/lib/utils";

const classDialog = cn(
  "[&::backdrop]:hidden",
  "[&[open]]:fixed [&[open]]:m-0 [&[open]]:h-dvh [&[open]]:max-h-none [&[open]]:w-dvw [&[open]]:max-w-none [&[open]]:overflow-hidden [&[open]]:border-0 [&[open]]:bg-transparent [&[open]]:p-0",
  "[&_[data-rmiz-modal-overlay]]:absolute [&_[data-rmiz-modal-overlay]]:inset-0 [&_[data-rmiz-modal-overlay]]:transition-all",
  '[&_[data-rmiz-modal-overlay="hidden"]]:bg-transparent',
  '[&_[data-rmiz-modal-overlay="visible"]]:bg-background/80 [&_[data-rmiz-modal-overlay="visible"]]:backdrop-blur-md',
  "[&_[data-rmiz-modal-content]]:relative [&_[data-rmiz-modal-content]]:size-full",
  "[&_[data-rmiz-modal-img]]:absolute [&_[data-rmiz-modal-img]]:origin-top-left [&_[data-rmiz-modal-img]]:cursor-zoom-out [&_[data-rmiz-modal-img]]:transition-transform",
  "motion-reduce:[&_[data-rmiz-modal-img]]:transition-none motion-reduce:[&_[data-rmiz-modal-overlay]]:transition-none"
);

export type ImageZoomProps = UncontrolledProps & {
  className?: string;
  backdropClassName?: string;
};

export const ImageZoom = ({ className, backdropClassName, ...props }: ImageZoomProps) => (
  <Wrapper className={className}>
    <Zoom classDialog={cn(classDialog, backdropClassName)} {...props} />
  </Wrapper>
);

export type ImageZoomControlledProps = ControlledProps & {
  className?: string;
  backdropClassName?: string;
};

export const ImageZoomControlled = ({
  className,
  backdropClassName,
  ...props
}: ImageZoomControlledProps) => (
  <Wrapper className={className}>
    <Controlled classDialog={cn(classDialog, backdropClassName)} {...props} />
  </Wrapper>
);

const wrapperClasses = cn(
  "relative",
  // Ghost placeholder
  "[&_[data-rmiz-ghost]]:pointer-events-none [&_[data-rmiz-ghost]]:absolute",
  // Button base styles
  "[&_[data-rmiz-btn-zoom]]:m-0 [&_[data-rmiz-btn-zoom]]:size-10 [&_[data-rmiz-btn-zoom]]:touch-manipulation [&_[data-rmiz-btn-zoom]]:appearance-none [&_[data-rmiz-btn-zoom]]:rounded-[50%] [&_[data-rmiz-btn-zoom]]:border-none [&_[data-rmiz-btn-zoom]]:bg-foreground/70 [&_[data-rmiz-btn-zoom]]:p-2 [&_[data-rmiz-btn-zoom]]:text-background [&_[data-rmiz-btn-zoom]]:outline-offset-2",
  "[&_[data-rmiz-btn-unzoom]]:m-0 [&_[data-rmiz-btn-unzoom]]:size-10 [&_[data-rmiz-btn-unzoom]]:touch-manipulation [&_[data-rmiz-btn-unzoom]]:appearance-none [&_[data-rmiz-btn-unzoom]]:rounded-[50%] [&_[data-rmiz-btn-unzoom]]:border-none [&_[data-rmiz-btn-unzoom]]:bg-foreground/70 [&_[data-rmiz-btn-unzoom]]:p-2 [&_[data-rmiz-btn-unzoom]]:text-background [&_[data-rmiz-btn-unzoom]]:outline-offset-2",
  // Zoom button: visually hidden until focused/active (SR-accessible)
  "[&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:pointer-events-none [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:absolute [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:size-px [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:overflow-hidden [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:whitespace-nowrap [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:[clip-path:inset(50%)] [&_[data-rmiz-btn-zoom]:not(:focus):not(:active)]:[clip:rect(0_0_0_0)]",
  // Button positions
  "[&_[data-rmiz-btn-zoom]]:absolute [&_[data-rmiz-btn-zoom]]:top-2.5 [&_[data-rmiz-btn-zoom]]:right-2.5 [&_[data-rmiz-btn-zoom]]:bottom-auto [&_[data-rmiz-btn-zoom]]:left-auto [&_[data-rmiz-btn-zoom]]:cursor-zoom-in",
  "[&_[data-rmiz-btn-unzoom]]:absolute [&_[data-rmiz-btn-unzoom]]:top-5 [&_[data-rmiz-btn-unzoom]]:right-5 [&_[data-rmiz-btn-unzoom]]:bottom-auto [&_[data-rmiz-btn-unzoom]]:left-auto [&_[data-rmiz-btn-unzoom]]:z-[1] [&_[data-rmiz-btn-unzoom]]:cursor-zoom-out",
  // Cursor for zoomable content
  '[&_[data-rmiz-content="found"]_img]:cursor-zoom-in',
  '[&_[data-rmiz-content="found"]_svg]:cursor-zoom-in',
  '[&_[data-rmiz-content="found"]_[role="img"]]:cursor-zoom-in',
  '[&_[data-rmiz-content="found"]_[data-zoom]]:cursor-zoom-in'
);

function Wrapper({
  className,
  children,
}: React.PropsWithChildren<{ className?: string }>) {
  return <div className={cn(wrapperClasses, className)}>{children}</div>;
}
SBIOTJ

Join 3,000+ builders shipping with shadcncraft

Get access to this component and the full library

Production-ready blocks and components with matching Figma and React.

Get this componentFree
Benefits

Built for real interfaces

Each block is designed to work as part of a complete page, not just as a visual fragment.

Structured Layouts

Clear grids, consistent spacing, and predictable hierarchy.

Marketing + App Coverage

Landing pages, dashboards, onboarding, pricing, e-commerce, and more.

Theme Compatible

Works seamlessly with semantic tokens and theming.

Production-Ready States

Designed with real content, tokenized spacing, and real interaction patterns.

Figma + React Parity (Pro + React)

Design in Figma and ship with aligned React components.

Composable by Design

Mix blocks together to create complete pages without layout conflicts.

Frequently Asked Questions

Quick FAQs to get you started.
Still have questions? See all FAQs, or get support.

Yes. They follow shadcn/ui structure and are built for real products.

Real support from the team behind shadcncraft

Get help within 24 hours from the people who build and maintain the system.

Email
Prefer a direct line? Send us a message and we'll get back to you as soon as possible.
Discord
Get quick support, share feedback, or connect with other builders.
Feedback
Got something to say about anything shadcncraft? We'd love to hear it.