Glin UI
Primitive / MoleculeAdapted from Motion Primitivesbeta

Image Comparison

Before and after layers split by a draggable, keyboard operable slider handle.

Edit on GitHub
Notifications3 new
Deploy finished2 minutes ago
Build queuedJust now
Notifications3
Deploy finished2 minutes ago
Build queuedJust now

Installation

Install from
pnpm dlx @glinui/cli@latest add image-comparison

Credits. Adapted from Motion Primitives (MIT, (c) 2024 ibelick). motion/react and springs removed (pointer capture and CSS clip-path), slider role with keys, ReactNode slots, tokens, reduced motion, RTL Attribution details.

Prefer the package?

Usage

The layers are any ReactNode, so you can compare images, screenshots, UI states or whole themes.

Variants

Vertical split, and hover mode that follows a fine pointer.

Notifications3 new
Deploy finished2 minutes ago
Build queuedJust now
Notifications3
Deploy finished2 minutes ago
Build queuedJust now
Notifications3 new
Deploy finished2 minutes ago
Build queuedJust now
Notifications3
Deploy finished2 minutes ago
Build queuedJust now
<ImageComparison orientation="vertical" before={a} after={b} />
<ImageComparison hover before={a} after={b} />

States

Compare themes or any other pair of states.

Dark
Light
<ImageComparison beforeLabel="Light" afterLabel="Dark" before={light} after={dark} />

In a layout

A redesign section with a heading above the comparison.

See the redesign

Notifications3 new
Deploy finished2 minutes ago
Build queuedJust now
Notifications3
Deploy finished2 minutes ago
Build queuedJust now
<section aria-labelledby="redesign">
<h3 id="redesign">See the redesign</h3>
<ImageComparison before={a} after={b} />
</section>

Accessibility

  • The handle is role slider with aria-valuenow, aria-valuetext, aria-orientation and an accessible name.
  • Everything a pointer can do works from the keyboard: arrows, Page keys, Home and End.
  • touch-action is pan-y (pan-x when vertical) so a horizontal drag moves the handle while the page still scrolls the other way.
  • Both layers are named groups, so their content stays readable.

Keyboard Interactions

KeyAction
ArrowLeft / ArrowRightMove the handle (reversed in RTL).
ArrowUp / ArrowDownMove the handle in vertical mode.
Shift + Arrow, PageUp / PageDownMove five steps.
Home / EndJump to 0 or 100.

ARIA Attributes

  • `role="slider"` with `aria-valuemin`, `aria-valuemax`, `aria-valuenow`
  • `aria-valuetext` reads both percentages
  • `aria-orientation`

Reduced Motion

The short clip-path glide on keyboard moves only runs at motion level full. Dragging is always immediate.

Affected properties

clip-path transitionhandle position transition

API Reference

PropTypeRequiredDefaultDescription
before / afterReactNode--The two layers. Before is shown from the start edge up to the handle.
beforeLabel / afterLabelstring-Before / AfterNames for the layers and the corner chips.
showLabelsbooleanNotrueShow the corner chips.
value / defaultValue / onValueChangenumber / number / (value) => void-50Handle position, percent of the before layer.
orientation"horizontal" | "vertical"NohorizontalDirection of the split.
stepnumberNo2Arrow key step in percent. Shift multiplies by 5.
hoverbooleanNofalseFollow a fine pointer without pressing. Touch still drags.
labelstringNoComparison positionAccessible name of the slider.
variantSurfaceVariant | "default"Noambient (glinr)Frame surface.
afterReact.ReactNodeYes-Layer shown behind, revealed past the handle. Any ReactNode.
afterLabelstringNo"After"Auto-generated from TypeScript source.
beforeReact.ReactNodeYes-Layer shown from the start edge up to the handle. Any ReactNode.
beforeLabelstringNo"Before"Auto-generated from TypeScript source.
defaultValuenumberNo50Auto-generated from TypeScript source.
onValueChange(value: number) => voidNo-Auto-generated from TypeScript source.
valuenumberNo-Handle position, 0 to 100 (percent of the before layer shown).

Source

Import directly from the package or browse the source on GitHub. Click any file to view it.

Import

import { ImageComparison } from "@glinui/ui"

Dependencies

@glinui/ui@glinui/motion@phosphor-icons/react

Generated API Snapshot

Beta

Auto-extracted from TypeScript source in packages/ui/src/components/image-comparison.tsx. This section is in beta and may lag behind hand-curated docs. Regenerate with pnpm --filter @glinui/docs api:generate.

Generated: 2026-10-08T01:41:21.546Z · Full index: /docs/api-metadata

Primary Props Type

ImageComparisonProps

ImageComparisonProps

PropTypeRequiredDefaultDescription
afterReact.ReactNodeYes-Layer shown behind, revealed past the handle. Any ReactNode.
afterLabelstringNo"After"-
beforeReact.ReactNodeYes-Layer shown from the start edge up to the handle. Any ReactNode.
beforeLabelstringNo"Before"-
defaultValuenumberNo50-
hoverbooleanNofalseFollow a fine pointer without pressing. Touch still drags.
labelstringNo"Comparison position"Accessible name of the slider handle.
onValueChange(value: number) => voidNo--
orientation"horizontal" | "vertical"No"horizontal"-
showLabelsbooleanNotrueShow the visible Before and After chips.
stepnumberNo2Arrow key step in percent. Shift or Page keys move 5 steps.
valuenumberNo-Handle position, 0 to 100 (percent of the before layer shown).
variantSurfaceVariant | "default"No--