Compare Slider
An interactive before/after comparison slider for comparing two elements side by side.
import {
CompareSlider,
CompareSliderAfter,
CompareSliderBefore,
CompareSliderHandle,
} from "@/components/ui/compare-slider";
export function CompareSliderDemo() {
return (
<CompareSlider
defaultValue={50}
className="h-[400px] overflow-hidden rounded-lg border"
>
<CompareSliderBefore>
{/* biome-ignore lint/performance/noImgElement: Demo image for comparison slider */}
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&h=1080&fit=crop&auto=format&fm=webp&q=80"
alt="Before"
className="size-full object-cover"
/>
</CompareSliderBefore>
<CompareSliderAfter>
{/* biome-ignore lint/performance/noImgElement: Demo image for comparison slider */}
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&h=1080&fit=crop&auto=format&fm=webp&q=80&sat=-100"
alt="After"
className="size-full object-cover grayscale"
/>
</CompareSliderAfter>
<CompareSliderHandle />
</CompareSlider>
);
}Installation
CLI
npx shadcn@latest add "https://diceui.com/r/compare-slider"Manual
Install the following dependencies:
npm install @radix-ui/react-slot lucide-reactCopy the refs composition utilities into your lib/compose-refs.ts file.
/**
* @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx
*/
import * as React from "react";
type PossibleRef<T> = React.Ref<T> | undefined;
/**
* Set a given ref to a given value
* This utility takes care of different types of refs: callback refs and RefObject(s)
*/
function setRef<T>(ref: PossibleRef<T>, value: T) {
if (typeof ref === "function") {
return ref(value);
}
if (ref !== null && ref !== undefined) {
ref.current = value;
}
}
/**
* A utility to compose multiple refs together
* Accepts callback refs and RefObject(s)
*/
function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
return (node) => {
let hasCleanup = false;
const cleanups = refs.map((ref) => {
const cleanup = setRef(ref, node);
if (!hasCleanup && typeof cleanup === "function") {
hasCleanup = true;
}
return cleanup;
});
// React <19 will log an error to the console if a callback ref returns a
// value. We don't use ref cleanups internally so this will only happen if a
// user's ref callback returns a value, which we only expect if they are
// using the cleanup functionality added in React 19.
if (hasCleanup) {
return () => {
for (let i = 0; i < cleanups.length; i++) {
const cleanup = cleanups[i];
if (typeof cleanup === "function") {
cleanup();
} else {
setRef(refs[i], null);
}
}
};
}
};
}
/**
* A custom hook that composes multiple refs
* Accepts callback refs and RefObject(s)
*/
function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
// biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values
return React.useCallback(composeRefs(...refs), refs);
}
export { composeRefs, useComposedRefs };Copy and paste the following code into your project.
"use client";
import { Slot } from "@radix-ui/react-slot";
import {
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronUpIcon,
} from "lucide-react";
import * as React from "react";
import { useComposedRefs } from "@/lib/compose-refs";
import { cn } from "@/lib/utils";
const ROOT_NAME = "CompareSlider";
const BEFORE_NAME = "CompareSliderBefore";
const AFTER_NAME = "CompareSliderAfter";
const LABEL_NAME = "CompareSliderLabel";
const HANDLE_NAME = "CompareSliderHandle";
const PAGE_KEYS = ["PageUp", "PageDown"];
const ARROW_KEYS = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];
type Interaction = "hover" | "drag";
type Orientation = "horizontal" | "vertical";
interface DivProps extends React.ComponentProps<"div"> {
asChild?: boolean;
}
type RootImplElement = React.ComponentRef<typeof CompareSliderRootImpl>;
const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
function useAsRef<T>(props: T) {
const ref = React.useRef<T>(props);
useIsomorphicLayoutEffect(() => {
ref.current = props;
});
return ref;
}
function useLazyRef<T>(fn: () => T) {
const ref = React.useRef<T | null>(null);
if (ref.current === null) {
ref.current = fn();
}
return ref as React.RefObject<T>;
}
function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
interface StoreState {
value: number;
isDragging: boolean;
}
interface Store {
subscribe: (callback: () => void) => () => void;
getState: () => StoreState;
setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;
notify: () => void;
}
const StoreContext = React.createContext<Store | null>(null);
function useStoreContext(consumerName: string) {
const context = React.useContext(StoreContext);
if (!context) {
throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
}
return context;
}
function useStore<T>(selector: (state: StoreState) => T): T {
const store = useStoreContext("useStore");
const getSnapshot = React.useCallback(
() => selector(store.getState()),
[store, selector],
);
return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
}
interface CompareSliderContextValue {
interaction: Interaction;
orientation: Orientation;
}
const CompareSliderContext =
React.createContext<CompareSliderContextValue | null>(null);
function useCompareSliderContext(consumerName: string) {
const context = React.useContext(CompareSliderContext);
if (!context) {
throw new Error(`\`${consumerName}\` must be used within \`${ROOT_NAME}\``);
}
return context;
}
interface CompareSliderRootProps extends DivProps {
value?: number;
defaultValue?: number;
onValueChange?: (value: number) => void;
step?: number;
interaction?: Interaction;
orientation?: Orientation;
}
function CompareSliderRoot(props: CompareSliderRootProps) {
const { value, defaultValue = 50, onValueChange, ...rootProps } = props;
const stateRef = useLazyRef<StoreState>(() => ({
value: clamp(value ?? defaultValue, 0, 100),
isDragging: false,
}));
const listenersRef = useLazyRef(() => new Set<() => void>());
const onValueChangeRef = useAsRef(onValueChange);
const store = React.useMemo<Store>(() => {
return {
subscribe: (cb) => {
listenersRef.current.add(cb);
return () => listenersRef.current.delete(cb);
},
getState: () => stateRef.current,
setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => {
if (Object.is(stateRef.current[key], value)) return;
stateRef.current[key] = value;
if (key === "value") {
onValueChangeRef.current?.(value as number);
}
store.notify();
},
notify: () => {
for (const cb of listenersRef.current) {
cb();
}
},
};
}, [listenersRef, stateRef, onValueChangeRef]);
useIsomorphicLayoutEffect(() => {
if (value !== undefined) {
store.setState("value", clamp(value, 0, 100));
}
}, [value, store]);
return (
<StoreContext.Provider value={store}>
<CompareSliderRootImpl {...rootProps} />
</StoreContext.Provider>
);
}
function CompareSliderRootImpl(
props: Omit<
CompareSliderRootProps,
"value" | "defaultValue" | "onValueChange"
>,
) {
const {
step = 1,
interaction = "drag",
orientation = "horizontal",
className,
children,
ref,
onPointerMove: onPointerMoveProp,
onPointerUp: onPointerUpProp,
onPointerDown: onPointerDownProp,
onKeyDown: onKeyDownProp,
asChild,
...rootProps
} = props;
const store = useStoreContext(ROOT_NAME);
const value = useStore((state) => state.value);
const containerRef = React.useRef<RootImplElement>(null);
const composedRef = useComposedRefs(ref, containerRef);
const isDraggingRef = React.useRef(false);
const propsRef = useAsRef({
onPointerMove: onPointerMoveProp,
onPointerUp: onPointerUpProp,
onPointerDown: onPointerDownProp,
onKeyDown: onKeyDownProp,
interaction,
orientation,
step,
});
const onPointerMove = React.useCallback(
(event: React.PointerEvent<RootImplElement>) => {
if (!isDraggingRef.current && propsRef.current.interaction === "drag") {
return;
}
if (!containerRef.current) return;
propsRef.current.onPointerMove?.(event);
if (event.defaultPrevented) return;
const containerRect = containerRef.current.getBoundingClientRect();
const isVertical = propsRef.current.orientation === "vertical";
const position = isVertical
? event.clientY - containerRect.top
: event.clientX - containerRect.left;
const size = isVertical ? containerRect.height : containerRect.width;
const percentage = clamp((position / size) * 100, 0, 100);
store.setState("value", percentage);
},
[propsRef, store],
);
const onPointerDown = React.useCallback(
(event: React.PointerEvent<RootImplElement>) => {
if (propsRef.current.interaction !== "drag") return;
propsRef.current.onPointerDown?.(event);
if (event.defaultPrevented) return;
event.currentTarget.setPointerCapture(event.pointerId);
isDraggingRef.current = true;
store.setState("isDragging", true);
},
[store, propsRef],
);
const onPointerUp = React.useCallback(
(event: React.PointerEvent<RootImplElement>) => {
if (propsRef.current.interaction !== "drag") return;
propsRef.current.onPointerUp?.(event);
if (event.defaultPrevented) return;
event.currentTarget.releasePointerCapture(event.pointerId);
isDraggingRef.current = false;
store.setState("isDragging", false);
},
[store, propsRef],
);
const onKeyDown = React.useCallback(
(event: React.KeyboardEvent<RootImplElement>) => {
propsRef.current.onKeyDown?.(event);
if (event.defaultPrevented) return;
const currentValue = store.getState().value;
const isVertical = propsRef.current.orientation === "vertical";
if (event.key === "Home") {
event.preventDefault();
store.setState("value", 0);
} else if (event.key === "End") {
event.preventDefault();
store.setState("value", 100);
} else if (PAGE_KEYS.concat(ARROW_KEYS).includes(event.key)) {
event.preventDefault();
const isPageKey = PAGE_KEYS.includes(event.key);
const isSkipKey =
isPageKey || (event.shiftKey && ARROW_KEYS.includes(event.key));
const multiplier = isSkipKey ? 10 : 1;
let direction = 0;
if (isVertical) {
const isDecreaseKey = ["ArrowUp", "PageUp"].includes(event.key);
direction = isDecreaseKey ? -1 : 1;
} else {
const isDecreaseKey = ["ArrowLeft", "PageUp"].includes(event.key);
direction = isDecreaseKey ? -1 : 1;
}
const stepInDirection = propsRef.current.step * multiplier * direction;
const newValue = clamp(currentValue + stepInDirection, 0, 100);
store.setState("value", newValue);
}
},
[store, propsRef],
);
const contextValue = React.useMemo<CompareSliderContextValue>(
() => ({
interaction,
orientation,
}),
[interaction, orientation],
);
const RootPrimitive = asChild ? Slot : "div";
return (
<CompareSliderContext.Provider value={contextValue}>
<RootPrimitive
role="slider"
aria-orientation={orientation}
aria-valuemax={100}
aria-valuemin={0}
aria-valuenow={value}
data-slot="compare-slider"
data-orientation={orientation}
{...rootProps}
ref={composedRef}
tabIndex={0}
className={cn(
"relative isolate select-none overflow-hidden outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
orientation === "horizontal" ? "w-full" : "h-full",
className,
)}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
onPointerUp={onPointerUp}
onPointerCancel={onPointerUp}
onKeyDown={onKeyDown}
>
{children}
</RootPrimitive>
</CompareSliderContext.Provider>
);
}
interface CompareSliderBeforeProps extends DivProps {
label?: string;
}
function CompareSliderBefore(props: CompareSliderBeforeProps) {
const { className, children, style, label, asChild, ref, ...beforeProps } =
props;
const value = useStore((state) => state.value);
const { orientation } = useCompareSliderContext(BEFORE_NAME);
const labelId = React.useId();
const isVertical = orientation === "vertical";
const clipPath = isVertical
? `inset(${value}% 0 0 0)`
: `inset(0 0 0 ${value}%)`;
const BeforePrimitive = asChild ? Slot : "div";
return (
<BeforePrimitive
role="img"
aria-labelledby={label ? labelId : undefined}
aria-hidden={label ? undefined : "true"}
data-slot="compare-slider-before"
data-orientation={orientation}
{...beforeProps}
ref={ref}
className={cn("absolute inset-0 h-full w-full object-cover", className)}
style={{
clipPath,
...style,
}}
>
{children}
{label && (
<CompareSliderLabel id={labelId} side="before">
{label}
</CompareSliderLabel>
)}
</BeforePrimitive>
);
}
interface CompareSliderAfterProps extends DivProps {
label?: string;
}
function CompareSliderAfter(props: CompareSliderAfterProps) {
const { className, children, style, label, asChild, ref, ...afterProps } =
props;
const value = useStore((state) => state.value);
const { orientation } = useCompareSliderContext(AFTER_NAME);
const labelId = React.useId();
const isVertical = orientation === "vertical";
const clipPath = isVertical
? `inset(0 0 ${100 - value}% 0)`
: `inset(0 ${100 - value}% 0 0)`;
const AfterPrimitive = asChild ? Slot : "div";
return (
<AfterPrimitive
role="img"
aria-labelledby={label ? labelId : undefined}
aria-hidden={label ? undefined : "true"}
data-slot="compare-slider-after"
data-orientation={orientation}
{...afterProps}
ref={ref}
className={cn("absolute inset-0 h-full w-full object-cover", className)}
style={{
clipPath,
...style,
}}
>
{children}
{label && (
<CompareSliderLabel id={labelId} side="after">
{label}
</CompareSliderLabel>
)}
</AfterPrimitive>
);
}
function CompareSliderHandle(props: DivProps) {
const { className, children, style, asChild, ref, ...handleProps } = props;
const value = useStore((state) => state.value);
const { interaction, orientation } = useCompareSliderContext(HANDLE_NAME);
const isVertical = orientation === "vertical";
const HandlePrimitive = asChild ? Slot : "div";
return (
<HandlePrimitive
role="presentation"
aria-hidden="true"
data-slot="compare-slider-handle"
data-orientation={orientation}
{...handleProps}
ref={ref}
className={cn(
"absolute z-50 flex items-center justify-center",
isVertical
? "-translate-y-1/2 left-0 h-10 w-full"
: "-translate-x-1/2 top-0 h-full w-10",
interaction === "drag" && "cursor-grab active:cursor-grabbing",
className,
)}
style={{
[isVertical ? "top" : "left"]: `${value}%`,
...style,
}}
>
{children ?? (
<>
<div
className={cn(
"absolute bg-background",
isVertical
? "-translate-y-1/2 top-1/2 h-1 w-full"
: "-translate-x-1/2 left-1/2 h-full w-1",
)}
/>
{interaction === "drag" && (
<div className="z-50 flex aspect-square size-11 shrink-0 items-center justify-center rounded-full bg-background p-2 [&_svg]:size-4 [&_svg]:select-none [&_svg]:stroke-3 [&_svg]:text-muted-foreground">
{isVertical ? (
<div className="flex flex-col items-center">
<ChevronUpIcon />
<ChevronDownIcon />
</div>
) : (
<div className="flex items-center">
<ChevronLeftIcon />
<ChevronRightIcon />
</div>
)}
</div>
)}
</>
)}
</HandlePrimitive>
);
}
interface CompareSliderLabelProps extends DivProps {
side?: "before" | "after";
}
function CompareSliderLabel(props: CompareSliderLabelProps) {
const { className, children, side, asChild, ref, ...labelProps } = props;
const { orientation } = useCompareSliderContext(LABEL_NAME);
const isVertical = orientation === "vertical";
const LabelPrimitive = asChild ? Slot : "div";
return (
<LabelPrimitive
ref={ref}
data-slot="compare-slider-label"
className={cn(
"absolute z-20 rounded-md border border-border bg-background/80 px-3 py-1.5 font-medium text-sm backdrop-blur-sm",
isVertical
? side === "before"
? "top-2 left-2"
: "bottom-2 left-2"
: side === "before"
? "top-2 left-2"
: "top-2 right-2",
className,
)}
{...labelProps}
>
{children}
</LabelPrimitive>
);
}
export {
CompareSliderRoot as Root,
CompareSliderAfter as After,
CompareSliderBefore as Before,
CompareSliderHandle as Handle,
CompareSliderLabel as Label,
//
CompareSliderRoot as CompareSlider,
CompareSliderAfter,
CompareSliderBefore,
CompareSliderHandle,
CompareSliderLabel,
};Layout
Import the parts, and compose them together.
import * as CompareSlider from "@/components/ui/compare-slider";
return (
<CompareSlider.Root>
<CompareSlider.Before />
<CompareSlider.After />
<CompareSlider.Handle />
</CompareSlider.Root>
)Examples
Controlled State
A compare slider with external controls for the slider position.
"use client";
import * as React from "react";
import {
CompareSlider,
CompareSliderAfter,
CompareSliderBefore,
CompareSliderHandle,
} from "@/components/ui/compare-slider";
export function CompareSliderControlledDemo() {
const [value, setValue] = React.useState(30);
return (
<CompareSlider
value={value}
onValueChange={setValue}
className="h-[400px] overflow-hidden rounded-lg border"
>
<CompareSliderBefore label="Original">
{/* biome-ignore lint/performance/noImgElement: Demo image for comparison slider */}
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&h=1080&fit=crop&auto=format&fm=webp&q=80"
alt="Original"
className="size-full object-cover"
/>
</CompareSliderBefore>
<CompareSliderAfter label="Enhanced">
{/* biome-ignore lint/performance/noImgElement: Demo image for comparison slider */}
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&h=1080&fit=crop&auto=format&fm=webp&q=80&sat=50"
alt="Enhanced"
className="size-full object-cover"
/>
</CompareSliderAfter>
<CompareSliderHandle />
</CompareSlider>
);
}Vertical Orientation
A compare slider with vertical orientation, perfect for comparing tall images or content.
import {
CompareSlider,
CompareSliderAfter,
CompareSliderBefore,
CompareSliderHandle,
} from "@/components/ui/compare-slider";
export function CompareSliderVerticalDemo() {
return (
<CompareSlider
defaultValue={50}
orientation="vertical"
className="h-[400px] w-full overflow-hidden rounded-lg border"
>
<CompareSliderBefore>
{/* biome-ignore lint/performance/noImgElement: Demo image for comparison slider */}
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&h=1080&fit=crop&auto=format&fm=webp&q=80"
alt="Before"
className="size-full object-cover"
/>
</CompareSliderBefore>
<CompareSliderAfter>
{/* biome-ignore lint/performance/noImgElement: Demo image for comparison slider */}
<img
src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1920&h=1080&fit=crop&auto=format&fm=webp&q=80&sat=-100"
alt="After"
className="size-full object-cover grayscale"
/>
</CompareSliderAfter>
<CompareSliderHandle />
</CompareSlider>
);
}Customization
Compare slider with custom handle, labels, and vertical orientation.
"use client";
import {
CompareSlider,
CompareSliderAfter,
CompareSliderBefore,
CompareSliderHandle,
} from "@/components/ui/compare-slider";
export function CompareSliderCustomDemo() {
return (
<CompareSlider
defaultValue={50}
className="h-[300px] overflow-hidden rounded-lg border"
>
<CompareSliderBefore className="flex size-full items-center justify-center bg-muted text-center">
<div className="font-bold text-2xl">Kickflip</div>
</CompareSliderBefore>
<CompareSliderAfter className="flex size-full items-center justify-center bg-primary text-center text-primary-foreground">
<div className="font-bold text-2xl">Heelflip</div>
</CompareSliderAfter>
<CompareSliderHandle>
<div className="flex size-10 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg">
<span className="font-bold text-xs">VS</span>
</div>
</CompareSliderHandle>
</CompareSlider>
);
}API Reference
Root
The root container for the compare slider component.
Prop
Type
| Data Attribute | Value |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
Before
The container for the "before" content that appears on the left (or top in vertical mode).
Prop
Type
| Data Attribute | Value |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
After
The container for the "after" content that appears on the right (or bottom in vertical mode).
Prop
Type
| Data Attribute | Value |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
Handle
The draggable handle that controls the comparison position.
Prop
Type
| Data Attribute | Value |
|---|---|
[data-orientation] | "horizontal" | "vertical" |
Label
Custom labels that can be positioned on either side of the comparison.
Prop
Type
Accessibility
Keyboard Interactions
| Key | Description |
|---|---|
| Tab | Moves focus to the slider. |
| Shift + Tab | Moves focus away from the slider to the previous focusable element. |
| ArrowLeftArrowUp | Moves the slider position left (or up in vertical mode) by the step amount. |
| ArrowRightArrowDown | Moves the slider position right (or down in vertical mode) by the step amount. |
| PageUp | Moves the slider position left (or up in vertical mode) by ten steps. |
| PageDown | Moves the slider position right (or down in vertical mode) by ten steps. |
| Shift + ArrowLeftShift + ArrowUp | Moves the slider position left (or up in vertical mode) by ten steps. |
| Shift + ArrowRightShift + ArrowDown | Moves the slider position right (or down in vertical mode) by ten steps. |
| Home | Moves the slider to the minimum position (0%). |
| End | Moves the slider to the maximum position (100%). |
Mouse and Touch Interactions
- Drag: Click and drag the handle to adjust the comparison position
- Click: Click anywhere on the slider container to jump to that position
- Touch: Full touch support for mobile devices
Advanced Usage
Custom Content Types
The compare slider works with any React content, not just images:
<CompareSlider>
<CompareSliderBefore>
<div className="flex items-center justify-center bg-blue-500">
<p>Old Design</p>
</div>
</CompareSliderBefore>
<CompareSliderAfter>
<div className="flex items-center justify-center bg-green-500">
<p>New Design</p>
</div>
</CompareSliderAfter>
<CompareSliderHandle />
</CompareSlider>Vertical Orientation
Use vertical orientation for comparing content that works better in a vertical layout. The slider handle moves vertically, and the "before" content appears on top while "after" content appears on bottom.
<CompareSlider orientation="vertical" className="h-[600px]">
<CompareSliderBefore>
{/* Top content */}
</CompareSliderBefore>
<CompareSliderAfter>
{/* Bottom content */}
</CompareSliderAfter>
<CompareSliderHandle />
</CompareSlider>See the Vertical Orientation example for a complete demo.
Custom Labels
Add custom labels to identify each side:
<CompareSlider>
<CompareSliderBefore label="Original">
{/* Content */}
</CompareSliderBefore>
<CompareSliderAfter label="Enhanced">
{/* Content */}
</CompareSliderAfter>
<CompareSliderHandle />
</CompareSlider>Or use the CompareSliderLabel component for more control:
<CompareSlider>
<CompareSliderBefore>
{/* Content */}
</CompareSliderBefore>
<CompareSliderAfter>
{/* Content */}
</CompareSliderAfter>
<CompareSliderHandle />
<CompareSliderLabel side="before" className="bg-blue-500/90 text-white">
Original
</CompareSliderLabel>
<CompareSliderLabel side="after" className="bg-green-500/90 text-white">
Enhanced
</CompareSliderLabel>
</CompareSlider>Browser Support
Core Features
All core comparison features work in modern browsers:
- Chrome/Edge: Full support
- Firefox: Full support
- Safari: Full support (iOS 13+)
Touch Gestures
Touch interactions require modern touch APIs:
- iOS Safari: Supported from iOS 13+
- Chrome Mobile: Full support
- Firefox Mobile: Full support
Troubleshooting
Content Overflow
Ensure your content is properly contained within the slider:
<CompareSlider className="h-[400px] overflow-hidden">
<CompareSliderBefore>
<img className="size-full object-cover" src="..." />
</CompareSliderBefore>
{/* ... */}
</CompareSlider>Performance with Large Images
For large images, consider:
- Using optimized image formats (WebP, AVIF)
- Lazy loading images
- Using appropriate image sizes for the display size
- Implementing image preloading for smoother transitions
Mobile Considerations
On mobile devices:
- Ensure touch targets are adequately sized
- Test on various screen sizes
- Consider using vertical orientation for better mobile UX
- Ensure content is responsive and scales appropriately
Credits
- Samuel Ferrara on Unsplash - For the demo images used in the examples.