SlideVerify
Introduction
MSlideVerify is a slide-to-verify component with drag interaction and state transition animations. Built on motion-v for smooth transitions, it is suitable for form verification, sensitive operation confirmation, and similar scenarios.
Usage
Hold and drag the slider to the right to reach the threshold and pass verification:
<script setup lang="ts">
const value = ref(false)
</script>
<template>
<MSlideVerify v-model="value" />
</template>
threshold Pass Threshold
threshold determines the drag ratio required to pass. Defaults to 0.9. Lowering it relaxes the requirement:
<template>
<MSlideVerify :threshold="0.5" text="Drag halfway to pass" />
</template>
text Hint Text
text sets the hint before verification; successText sets the text displayed after passing:
<template>
<MSlideVerify text="Hold and drag right" success-text="Human verification passed" />
</template>
icon Slider Icon
icon sets the icon before verification; successIcon sets the icon displayed after passing:
<template>
<MSlideVerify icon="i-lucide-arrow-right" success-icon="i-lucide-shield-check" />
</template>
size Size
Use size to adjust the component size:
<template>
<MSlideVerify size="md" />
</template>
disabled Disabled State
disabled freezes the slider. The cursor cannot drag and the unverified state is preserved:
<template>
<MSlideVerify disabled />
</template>
Examples
Inheriting Field Context
When placed inside UFormField, it receives field size and error state, and the slider renders according to the form state:
<template>
<UFormField label="Slide Verification" size="xs" error="Example error state">
<MSlideVerify />
</UFormField>
</template>
Inside UFieldGroup
When combined with a reset button, they share the UFieldGroup size. The slider area and button maintain a unified height:
<template>
<UFieldGroup size="xs">
<MSlideVerify class="flex-1" />
<UButton icon="i-lucide-rotate-ccw" color="neutral" variant="subtle" />
</UFieldGroup>
</template>
Custom Slider Content
The slider slot takes over the slider's inner rendering. Read verified and progress to dynamically display progress:
<script setup lang="ts">
const isVerified = ref(false)
</script>
<template>
<MSlideVerify v-model="isVerified" class="w-sm">
<template #slider="{ verified, progress }">
<UIcon v-if="verified" name="i-lucide-check" />
<span v-else class="text-xs font-medium">{{ Math.round(progress * 100) }}%</span>
</template>
</MSlideVerify>
</template>
Event Callbacks
Drag start fires dragStart. Releasing fires dragEnd with a success boolean. Reaching the threshold additionally fires success:
<script setup lang="ts">
const isVerified = ref(false)
const slideVerifyRef = useTemplateRef('slideVerifyRef')
const toast = useToast()
function handleSuccess() {
toast.add({
title: '验证成功',
description: '已触发 success 事件',
color: 'success'
})
}
function handleDragEnd(success: boolean) {
if (!success) {
toast.add({
title: '验证失败',
description: '请继续滑动',
color: 'neutral'
})
}
}
function handleReset() {
slideVerifyRef.value?.reset()
toast.add({
title: '已重置',
color: 'neutral'
})
}
</script>
<template>
<div class="w-sm flex gap-4">
<MSlideVerify
ref="slideVerifyRef"
v-model="isVerified"
class="flex-1"
@success="handleSuccess"
@drag-end="handleDragEnd"
/>
<UButton size="sm" color="neutral" variant="outline" @click="handleReset"> 重置验证 </UButton>
</div>
</template>
API
Props
| Prop | Default | Type |
|---|---|---|
text | '请向右滑动验证' | string待滑动时的提示文本 |
icon | 'i-lucide-chevrons-right' | any滑块图标 |
successText | '验证成功' | string验证成功时的提示文本 |
successIcon | 'i-lucide-check' | any验证成功时的图标 |
threshold | 0.9 | number完成验证所需的阈值百分比(0-1) |
size | 'md' | "md" | "xs" | "sm" | "lg" | "xl"尺寸大小 |
id | string | |
name | string | |
disabled | boolean是否禁用 | |
modelValue | false | boolean |
ui | Record<string, ClassNameValue> & { root?: SlotClass; track?: SlotClass; fill?: SlotClass; text?: SlotClass; slider?: SlotClass; icon?: SlotClass; } |
Emits
| Event | Type |
|---|---|
update:modelValue | [value: boolean] |
success | [] |
dragStart | [] |
dragEnd | [success: boolean] |
Slots
| Slot | Type |
|---|---|
slider | { verified?: boolean; progress: number; } |
Expose
You can access the typed component instance via useTemplateRef.
| Name | Type |
|---|---|
reset() | Promise<void> Reset the verification state |
Theme
export default defineAppConfig({
ui: {
slideVerify: {
slots: {
root: 'w-full relative flex items-center select-none overflow-hidden rounded-md border transition-colors duration-300',
track: 'absolute inset-0',
fill: 'absolute inset-y-0 left-0 bg-primary/20 opacity-60',
text: 'absolute inset-0 flex items-center justify-center font-medium pointer-events-none',
slider: 'relative z-10 flex shrink-0 touch-none items-center justify-center rounded-md shadow-sm transition-colors',
icon: 'shrink-0'
},
variants: {
size: {
xs: {
root: 'p-1',
text: 'text-xs',
slider: 'px-2 py-1',
icon: 'size-4'
},
sm: {
root: 'p-1.5',
text: 'text-xs',
slider: 'px-2.5 py-1.5',
icon: 'size-4'
},
md: {
root: 'p-1.5',
text: 'text-sm',
slider: 'px-2.5 py-1.5',
icon: 'size-5'
},
lg: {
root: 'p-2',
text: 'text-sm',
slider: 'px-3 py-2',
icon: 'size-5'
},
xl: {
root: 'p-2',
text: 'text-base',
slider: 'px-3 py-2',
icon: 'size-6'
}
},
disabled: {
true: {
root: 'opacity-50 cursor-not-allowed'
}
},
verified: {
true: {
root: 'bg-success border-transparent',
slider: 'bg-white/90',
icon: 'text-success'
},
false: {
root: 'bg-elevated border-default',
slider: 'bg-default cursor-grab active:cursor-grabbing ring-1 ring-default',
icon: 'text-primary'
}
},
fieldGroup: {
horizontal: {
root: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]'
},
vertical: {
root: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
}
}
},
defaultVariants: {
size: 'md'
}
}
}
})