Skip to content

Lens

A magnifier that follows the mouse over an image or any content. The real content stays in place and fully accessible; the lens is a hidden, inert enlarged copy that appears only under a real pointer.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

Optional, and kept apart.

Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.

A misty valley with pine trees and a river at sunrise
import { Lens } from "@rdloom/react";

// A free-to-use photo from Unsplash. Swap in your own image.
const picture = "https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=1200&q=80";

// Move the mouse over the picture. The image keeps its alt text and the lens is hidden from screen readers.
export default function LensBasicExample() {
  return (
    <div className="flex w-full justify-center">
      <Lens className="rounded-[var(--rd-radius-overlay)]">
        <img src={picture} alt="A misty valley with pine trees and a river at sunrise" width={1200} height={800} className="block h-auto w-full max-w-xl" />
      </Lens>
    </div>
  );
}

Installation

npx rdloom add lens

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/lens.json

Works, but without upgrade tracking, and you import the motion CSS yourself.

Usage

import { Lens } from "@rdloom/react";

<div className="flex w-full justify-center">
  <Lens className="rounded-[var(--rd-radius-overlay)]">
    <img src={picture} alt="A misty valley with pine trees and a river at sunrise" width={1200} height={800} className="block h-auto w-full max-w-xl" />
  </Lens>
</div>

Size and zoom

Green highlands under a cloudy sunrise with a winding road
import { Lens } from "@rdloom/react";

// A free-to-use photo from Unsplash. Swap in your own image.
const picture = "https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?auto=format&fit=crop&w=1200&q=80";

// A bigger lens with stronger zoom, for fine detail.
export default function LensSizeAndZoomExample() {
  return (
    <div className="flex w-full justify-center">
      <Lens zoom={3} size={160} className="rounded-[var(--rd-radius-overlay)]">
        <img src={picture} alt="Green highlands under a cloudy sunrise with a winding road" width={1200} height={800} className="block h-auto w-full max-w-xl" />
      </Lens>
    </div>
  );
}

With text

Warranty: this product is covered for twenty four months from the date of purchase. Keep your receipt. Damage from water or drops is not covered. Contact support with your order number to start a claim.

import { Lens } from "@rdloom/react";

// The lens works on any content, not only images. The text below is read once, from the original.
export default function LensWithTextExample() {
  return (
    <div className="flex w-full justify-center">
      <Lens zoom={2.5} size={110} className="rounded-[var(--rd-radius-overlay)] border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)]">
        <p className="m-0 max-w-xs p-4 text-xs leading-relaxed text-[var(--rd-color-text-default)]">
          Warranty: this product is covered for twenty four months from the date of purchase. Keep your receipt. Damage from water or drops is not covered. Contact support
          with your order number to start a claim.
        </p>
      </Lens>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
childrenrequired

The content to magnify, usually an image with alt text.

nodenone
zoom

Magnification factor.

number2
size

Diameter of the lens in px.

number120
isStill

Turns the lens off. Also off for touch and for visitors who prefer reduced motion.

booleanfalse

Accessibility

Role presentation, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

Not interactive.

Screen readers announce

  • The child is announced as usual; the lens is not announced

What your code must do

  • The lens is decoration for mouse users: it never takes focus and no information may depend on it
  • The enlarged copy is aria-hidden and inert, so assistive technology reads the child once
  • The child keeps its own alt text, links and controls exactly as before
  • Off for touch input, for prefers-reduced-motion and inside rdm-still or with isStill
  • Keyboard users can still reach everything: zooming the page is the way to magnify for them
  • Forced-colors: the lens ring uses a system colour

Guidelines

Use it when

  • Product photos and diagrams where detail matters
  • Maps and charts with fine print

Avoid it when

  • Content that is only meaningful when enlarged, since not everyone can use a mouse
  • Touch-first layouts

Don't

  • Wrapping interactive forms, whose copy would be inert
  • Using it as the only way to read small text

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-border-strong