SlideButton

A handle dragged across a rail to confirm — for an action a stray tap should never reach.

Open Source

Installation

SlideButton ships with the library — install it once, then import:

import { SlideButton } from "@delacour/react-native-ui/slide-button";

Or copy the source into your project, to own and edit it:

bunx delacour@latest add slide-button

Usage

app/index.tsx
import { SlideButton } from "@/components/ui/slide-button";
import { useState } from "react";
import { Text, View } from "react-native";

export default function Example() {
  const [shipped, setShipped] = useState(0);

  return (
    <View className="flex-1 items-center justify-center gap-4 p-4">
      <SlideButton
        accessibilityActionLabel="Ship order"
        isAutoReset
        onComplete={() => setShipped((count) => count + 1)}
      >
        <SlideButton.Label>Slide to ship</SlideButton.Label>
      </SlideButton>
      <Text className="text-muted-foreground">Shipped {shipped}</Text>
    </View>
  );
}

Anatomy

<SlideButton onComplete={ship}>
  <SlideButton.Label>Slide to ship</SlideButton.Label>
  <SlideButton.Thumb />
</SlideButton>

SlideButton.Thumb is composed in when you leave it out. The handle tracks the finger exactly; only the release is sprung.

Slide to ship

Variants

secondary, destructive and success. The handle stays neutral; the rail, trail and label take the colour.

Variants
<SlideButton variant="secondary">
  <SlideButton.Label>Slide to archive</SlideButton.Label>
</SlideButton>
<SlideButton variant="destructive">
  <SlideButton.Label>Slide to delete</SlideButton.Label>
</SlideButton>
<SlideButton variant="success">
  <SlideButton.Label>Slide to pay</SlideButton.Label>
</SlideButton>

Sizes

The same heights and corners as a Button at each size.

Sizes
Sizes
<SlideButton size="sm">…</SlideButton>
<SlideButton size="md">…</SlideButton>
<SlideButton size="lg">…</SlideButton>

Threshold

How far a release must reach, from 0.1 to 1. At 1 only a release at the far end confirms.

Threshold
<SlideButton threshold={0.5}>
  <SlideButton.Label>Slide to send</SlideButton.Label>
</SlideButton>
<SlideButton accessibilityActionLabel="Delete account" threshold={1} variant="destructive">
  <SlideButton.Label>Slide to delete</SlideButton.Label>
</SlideButton>

Controlled, with slow work

The handle waits at the end until isCompleted answers, so a request in flight never looks refused.

Controlled, with slow work
const [isSent, setIsSent] = useState(false);

<SlideButton isCompleted={isSent} onComplete={() => transfer().then(() => setIsSent(true))} variant="success">
  <SlideButton.Label>Slide to transfer $240</SlideButton.Label>
</SlideButton>

Auto reset

isAutoReset takes the handle home autoResetDelay after it confirms.

<SlideButton autoResetDelay={800} haptic="selection" isAutoReset>
  <SlideButton.Label>Slide to clock in</SlideButton.Label>
</SlideButton>

Custom handle

Children on SlideButton.Thumb replace the chevron.

<SlideButton isFullWidth size="lg">
  <SlideButton.Label>Slide to unlock</SlideButton.Label>
  <SlideButton.Thumb>
    <Icon icon={IconUnlocked} />
  </SlideButton.Thumb>
</SlideButton>

Disabled

isDisabled fades the control and refuses the drag — the handle does not move at all.

Disabled
Disabled
<SlideButton isDisabled>
  <SlideButton.Label>Slide to ship</SlideButton.Label>
</SlideButton>

API Reference

SlideButton

The rail. Extends ViewProps, except style.

Prop

Type

SlideButton.Label

Centred in the rail; never fades. Its text is the rail's accessibility label. Extends TextProps.

Prop

Type

SlideButton.Thumb

The handle. Extends ViewProps, except style.

Prop

Type

useSlideButton

Returns { variant, size, isCompleted, isDisabled, isRTL, offset, travel, handleWidth, glyph } — the last four are Reanimated shared values. Throws outside a <SlideButton>.

On this page