A keyboard accessible before & after component for React
react-comparison-slider
React Comparison Slider is a fully customizable component for building bespoke, keyboard-accessible "before & after" sliders for the web. You bring the content and the visuals, and it'll handle the heavy lifting.
Installation
yarn add react-comparison-slider
The "Hello World" example
The key ingredients to this component are:
aspectRatio
, expressed either numerically as a fraction (e.g.,16/9
), or as a string (e.g.,"16x9"
or"16:9"
). Providing an aspect ratio ensures that the before and after "images" (or HTML elements, whatever you decide to provide) line up with one another.itemOne
of typeReact.ReactNode
or function as a child({value}) => React.ReactNode
itemTwo
of typeReact.ReactNode
or function as a child({value}) => React.ReactNode
defaultValue
, if you'd like to use the component in an uncontrolled fashionorientation
, where you can pass eithervertical
orhorizontal
. Horizontal sliders are the default.
import { ComparisonSlider } from 'react-comparison-slider';
export const HelloWorldExample = () => {
return (
<ComparisonSlider
defaultValue={50}
itemOne={<div className="bg-red-200"></div>}
itemTwo={<div className="bg-blue-200"></div>}
aspectRatio={16 / 9}
orientation="horizontal"
/>
);
};
Customization
React Comparison Slider does ship with some very lightweight styling, but encourages you to bring your own styling (BYOS)™️. Customization is handled via a set of render props that expose all of the underlying components for your needs. There is a total of 4 of these visual elements
// For adding a "bar" above the handle (or to the left, if in "vertical" orientation)
handleBefore?: React.ReactNode;
// For adding a "bar" below the handle (or to the right, if in "vertical" orientation)
handleAfter?: React.ReactNode;
// For customizing the slider handle itself. Note that `ComparisonSliderHandleProps` exposes an `isFocused` prop that you can use to style the handle when it has keyboard focus.
handle?: (props: ComparisonSliderHandleProps) => React.ReactNode;
handleBefore
and handleAfter
These props allows you to add visual indicators such as a scrubbing bar to the slider handle itself. In the example below, we add a thin white bar above and below the handle as shown in the screenshot below.
import { ComparisonSlider } from 'react-comparison-slider';
export const CustomHandleDecorations = () => {
return (
<ComparisonSlider
defaultValue={50}
itemOne={<div className="bg-red"></div>}
itemTwo={<div className="bg-blue"></div>}
aspectRatio={16 / 9}
handleBefore={
<div className="bg-gradient-to-t from-white to-transparent w-2 h-full"></div>
}
handleAfter={
<div className="bg-gradient-to-b from-white to-transparent w-2 h-full"></div>
}
handle={({ isFocused }) => {
return (
<div
className={cc([
'rounded-full w-8 h-8 bg-white',
{ ring: isFocused },
])}
></div>
);
}}
/>
);
};
handle
Of course, you can fully style the handle itself. You can make it bigger, add an icon, add fancy shadows...
import { ComparisonSlider } from 'react-comparison-slider';
export const CustomHandle = () => {
return (
<ComparisonSlider
defaultValue={50}
itemOne={<div className="bg-red"></div>}
itemTwo={<div className="bg-blue"></div>}
aspectRatio={16 / 9}
handle={({ isFocused }) => {
return (
<div
className={cc([
'rounded-full w-10 h-10 bg-white text-graty-600 flex items-center justify-center',
{ ring: isFocused },
])}
>
<BiMoveHorizontal size={24} />
</div>
);
}}
/>
);
};
The API
Below is a high-level interface definition for the component. Note that because this component can be used in both a controlled and uncontrolled fashion, the first three props – value
, defaultValue
, and onChange
are actually totally dynamic. That is to say, if you provide a defaultValue
you won't be asked for value
or onChange
. In fact, you'll get a compilation error if you try to use them. Conversely, if you provide value
and onChange
, you won't be asked for defaultValue
and will error out accordingly if you provide it.
value?: number;
onValueChange?: (value: number) => void;
defaultValue?: number;
// The "first" item in the viewport.
itemOne:
| React.ReactNode
| (({ value }: { value: number }) => React.ReactNode);
// The "second" item in the viewport.
itemTwo:
| React.ReactNode
| (({ value }: { value: number }) => React.ReactNode);
// The...aspect ratio.
aspectRatio: number | string;
// Decoration that appears above (or to the left of, depending on orientation) the handle.
handleBefore?: React.ReactNode;
// Decoration that appears below (or to the bottom of, depending on orientation) the handle.
handleAfter?: React.ReactNode;
// Handle component
handle?: (props: ComparisonSliderHandleProps) => React.ReactNode;
// Whether the slider is vertical or horizontal ?
orientation?: 'vertical' | 'horizontal';
// Whether only the handle itself should be interactive
onlyHandleDraggable?: boolean;