Skip to content

BeforeAfter Comparison Slider ​

Neobrutalist-style image sliding comparison component. Used to visually compare the difference between the same image before (Before) and after (After) modification. Built on native <input type="range"> combined with CSS clip-path masking, zero dependencies, touch-friendly.

Demo ​

Preview

水平方向

之前
之后

垂直方向

之前
之后

提示:拖拽滑块的位置过渡尊重系统的「减少动态效果」设置。启用后分割线与裁剪区域将即时跟随,不再带有缓动动画。

Installation ​

pnpm dlx brutx-vue@latest add before-after

Usage ​

vue
<script setup>
import { BeforeAfter } from 'brutx-ui-vue'

const original = '/images/before.jpg'
const modified = '/images/after.jpg'
</script>

<template>
    <BeforeAfter
        :before="original"
        :after="modified"
        :default-value="50"
    />
</template>

Vertical Orientation ​

Switch the divider direction via the orientation prop. Default horizontal is left-right dragging; when set to vertical, the divider becomes horizontal with up-down dragging, clipping proceeds from bottom to top (clip-path: inset(0 0 ...)), and the handle icon rotates 90 degrees automatically.

vue
<BeforeAfter
    :before="original"
    :after="modified"
    orientation="vertical"
    :default-value="50"
/>

Browser compatibility note: In vertical mode, the divider/handle position (top: <value>%, larger value = lower) matches the native vertical range's value direction in Chromium; WebKit (Safari) may render the vertical slider's increment direction reversed (dragging down decreases the value and moves the divider up), and the orient attribute is unsupported in some browsers. Verify in target browsers for cross-browser scenarios.

Variants ​

VariantDescription
horizontalDefault direction, left-right drag divider
verticalVertical direction, up-down drag divider, clipping proceeds from bottom to top

API Reference ​

BeforeAfter — Props
NameTypeDefaultDescription
before
Required
string
—
The URL of the underlying before image.
Details

Declared type

string

Default value details

Not declared
—

Source

BeforeAfter.vue:13

packages/ui/src/components/before-after/BeforeAfter.vue

after
Required
string
—
The URL of the overlaid after image.
Details

Declared type

string

Default value details

Not declared
—

Source

BeforeAfter.vue:14

packages/ui/src/components/before-after/BeforeAfter.vue

beforeAlt
string | undefined
undefined

Runtime fallback:Before

Alternative text for the underlying before image. Uses a localized value when omitted.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined
Runtime fallback
Before

Applied by the component under the documented runtime conditions.

Source

BeforeAfter.vue:15

packages/ui/src/components/before-after/BeforeAfter.vue

afterAlt
string | undefined
undefined

Runtime fallback:After

Alternative text for the overlaid after image. Uses a localized value when omitted.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined
Runtime fallback
After

Applied by the component under the documented runtime conditions.

Source

BeforeAfter.vue:16

packages/ui/src/components/before-after/BeforeAfter.vue

modelValue
number | undefined
undefined
The current divider position as a percentage, available through v-model. Assigned and interactive values are clamped to 0–100.
Details

Declared type

number | undefined

Default value details

Resolved value
undefined

Source

BeforeAfter.vue:17

packages/ui/src/components/before-after/BeforeAfter.vue

defaultValue
number | undefined
50
The initial divider percentage in uncontrolled mode. It is read only during initialization and defaults to 50.
Details

Declared type

number | undefined

Default value details

Declaration
DEFAULT_SLIDER_POSITION

Source: packages/ui/src/components/before-after/BeforeAfter.vue

Resolved value
50

Source

BeforeAfter.vue:18

packages/ui/src/components/before-after/BeforeAfter.vue

disabled
boolean | undefined
false
Disables dragging the native range slider.
Details

Declared type

boolean | undefined

Literal values

  • false
  • true

Default value details

Resolved value
false

Source

BeforeAfter.vue:19

packages/ui/src/components/before-after/BeforeAfter.vue

orientation
"vertical" | "horizontal" | undefined
"horizontal"
Selects the divider orientation. Horizontal displays a vertical divider; vertical displays a horizontal divider.
Details

Declared type

"vertical" | "horizontal" | undefined

Literal values

  • "horizontal"
  • "vertical"

Default value details

Resolved value
"horizontal"

Source

BeforeAfter.vue:20

packages/ui/src/components/before-after/BeforeAfter.vue

class
string | undefined
undefined
Custom CSS classes appended to the comparison container root.
Details

Declared type

string | undefined

Default value details

Resolved value
undefined

Source

BeforeAfter.vue:21

packages/ui/src/components/before-after/BeforeAfter.vue

iconSize
IconSize | undefined
"md"
Sets the divider handle icon size.
Details

Declared type

IconSize | undefined

Literal values

  • "2xl"
  • "lg"
  • "md"
  • "sm"
  • "xl"
  • "xs"

Referenced types

IconSize
Type definition — IconSize
export type IconSize = NonNullable<VariantProps<typeof iconSizeVariants>['size']>

Default value details

Resolved value
"md"

Source

BeforeAfter.vue:22

packages/ui/src/components/before-after/BeforeAfter.vue

BeforeAfter — Events
NameSignature or typeDescription
@update:modelValue
[value: number]
Emits the new percentage, clamped to 0–100, when slider interaction changes the position.
Details

Declared type

[value: number]

Source

BeforeAfter.vue

packages/ui/src/components/before-after/BeforeAfter.vue

Accessibility ​

  • Keyboard control: Supports keyboard arrow keys to control divider position; keyboard interaction is disabled in disabled state
  • Focus indicator: The transparent range input has no visible focus by itself; keyboard focus state is surfaced on the container ring via the has-[:focus-visible] variant (ring-brutal-ring), so the current position is perceivable when tabbing
  • Motion reduction: The component respects the prefers-reduced-motion system setting. When the user enables "reduce motion" (via useReducedMotion listening for prefers-reduced-motion: reduce):
    • Slider transition removal: The divider, drag handle, and clip-path clipping layer no longer apply transition styles. Position changes take effect immediately during dragging without easing animations
    • Interaction preserved: Dragging, keyboard arrow keys, disabled and other behaviors are completely unaffected; only visual transitions are removed
    • Real-time response: Preference changes take effect immediately without remounting the component
    • This mechanism is implemented through a reactive motionTransition computed property: in motion reduction mode it returns an empty string, causing related elements to skip transition styles

FAQ ​

Q: What is the technical approach of this component?

A: This component is built on the native browser <input type="range"> combined with CSS clip-path masking, with the following advantages:

  1. Touch-friendly: Perfectly adapts to swipe gestures on mobile phones, tablets, and other mobile devices
  2. Zero dependencies: No need to load any additional external gesture interaction JS libraries, package size is nearly zero
  3. Perfect layout: The underlying clip-path: inset(...) clipping method does not break the image aspect ratio, ensuring alignment across platforms
  4. Adaptive ratio: The container automatically calculates aspect-ratio based on the actual image dimensions, no need to manually specify the ratio. Before images load, default ratios are used as placeholders (horizontal 16:9, vertical 9:16)

Brute force builds.