Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions documentation-site/components/yard/config/sliding-button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import { SlidingButton, THRESHOLD } from "baseui/sliding-button";
import { PropTypes } from "react-view";
import type { TConfig } from "../types";

const SlidingButtonConfig: TConfig = {
componentName: "SlidingButton",
imports: {
"baseui/sliding-button": {
named: ["SlidingButton"],
},
},
scope: {
SlidingButton,
THRESHOLD,
},
theme: [],
props: {
label: {
value: "Slide to confirm",
type: PropTypes.String,
description: "Text displayed in the button track.",
},
onComplete: {
value: '() => console.log("completed!")',
type: PropTypes.Function,
description: "Called once when the user drags past the threshold.",
},
threshold: {
value: "THRESHOLD.high",
defaultValue: "THRESHOLD.high",
options: THRESHOLD,
type: PropTypes.Enum,
description:
"Completion threshold — 'high' requires 80%, 'low' requires 20%.",
imports: {
"baseui/sliding-button": {
named: ["THRESHOLD"],
},
},
},
isLoading: {
value: false,
type: PropTypes.Boolean,
description: "Shows loading spinner, disables interaction.",
},
isDisabled: {
value: false,
type: PropTypes.Boolean,
description: "Grays out the component, disables interaction.",
},
slideBackAfterMs: {
value: undefined,
type: PropTypes.Number,
description: "Auto-reset to idle state after N milliseconds.",
},
overrides: {
value: undefined,
type: PropTypes.Custom,
description: "Override internal elements.",
},
},
};

export default SlidingButtonConfig;
20 changes: 20 additions & 0 deletions documentation-site/examples/sliding-button/basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from "react";
import { SlidingButton } from "baseui/sliding-button";

export default function Example() {
return (
<SlidingButton
label="Slide to confirm"
onComplete={() => {
// eslint-disable-next-line no-console
console.log("Completed!");
}}
/>
);
}
22 changes: 22 additions & 0 deletions documentation-site/examples/sliding-button/low-threshold.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from "react";
import { SlidingButton, THRESHOLD } from "baseui/sliding-button";

export default function Example() {
return (
<SlidingButton
label="Slide to confirm"
threshold={THRESHOLD.low}
slideBackAfterMs={1500}
onComplete={() => {
// eslint-disable-next-line no-console
console.log("Completed!");
}}
/>
);
}
26 changes: 26 additions & 0 deletions documentation-site/examples/sliding-button/states.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from "react";
import { SlidingButton } from "baseui/sliding-button";

export default function Example() {
const [isLoading, setIsLoading] = React.useState(false);
return (
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<SlidingButton label="Slide to confirm" onComplete={() => {}} />
<SlidingButton
label="Slide to confirm"
isLoading={isLoading}
onComplete={() => {
setIsLoading(true);
setTimeout(() => setIsLoading(false), 2000);
}}
/>
<SlidingButton label="Slide to confirm" isDisabled />
</div>
);
}
79 changes: 79 additions & 0 deletions documentation-site/pages/components/sliding-button.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Example from "../../components/example";
import Exports from "../../components/exports";
import Layout from "../../components/layout";
import SEO from "../../components/meta-seo";

import Basic from "examples/sliding-button/basic.tsx";
import States from "examples/sliding-button/states.tsx";
import LowThreshold from "examples/sliding-button/low-threshold.tsx";

import * as SlidingButtonExports from "baseui/sliding-button";

import Yard from "../../components/yard/index";
import slidingButtonYardConfig from "../../components/yard/config/sliding-button";

<SEO
description="Sliding buttons use a drag gesture instead of a tap to confirm actions, reducing accidental triggers and reinforcing user intent."
keywords="sliding button, swipe to confirm, slide to confirm, react drag button"
/>

export default Layout;

# Sliding Button

<Yard placeholderHeight={56} {...slidingButtonYardConfig} />

Sliding buttons are a variation of primary buttons that use a drag gesture instead of a standard tap to confirm actions. They help reduce accidental triggers and reinforce user intent by introducing deliberate friction.

## When to use

- **Prevent accidental actions**: Situations requiring confirmation to avoid unintended triggers.
- **Costly or non-reversible actions**: Confirming intent on high-stakes operations (e.g., emergency assistance, trip completion).
- **Multi-step flow completion**: Final step in extended user journeys.

> **Caution**: If the action is not critical, a sliding button may add unnecessary complexity. Use a standard [Button](/components/button) instead.

## Examples

<Example title="Basic usage" path="sliding-button/basic.tsx">
<Basic />
</Example>

<Example
title="States (default, loading, disabled)"
path="sliding-button/states.tsx"
>
<States />
</Example>

<Example
title="Low threshold (20% drag)"
path="sliding-button/low-threshold.tsx"
>
<LowThreshold />
</Example>

## Threshold

Two completion thresholds support different user types:

- **`THRESHOLD.high`** (default) — at least 80% drag distance. Standard, requires deliberate intent.
- **`THRESHOLD.low`** — at least 20% drag distance. For users with motor disabilities, seniors, or low-friction flows.

## Accessibility

- **Keyboard support**: Press Enter or Space to activate — provides a standard button interaction for screen reader users.
- **Screen readers**: Use the `aria-label` prop to provide alternative text that omits "Slide" terminology, since swipe gestures behave differently with VoiceOver and TalkBack enabled.
- The component sets `role="button"`, `aria-busy` during loading, and `aria-disabled` when disabled.

## Content guidelines

- **Use "Slide"** in the visible label to indicate the drag interaction.
- **Avoid "Swipe"** — it carries a navigational connotation rather than task completion.
- **Omit "Slide"** from `aria-label` when VoiceOver/TalkBack is expected — use the `aria-label` prop to provide a plain-language alternative.

<Exports
component={SlidingButtonExports}
title="Sliding Button exports"
path="baseui/sliding-button"
/>
4 changes: 4 additions & 0 deletions documentation-site/routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ const routes = [
title: 'Slider',
itemId: '/components/slider',
},
{
title: 'Sliding Button',
itemId: '/components/sliding-button',
},
{
title: 'Stepper',
itemId: '/components/stepper',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "baseui",
"version": "18.0.0",
"version": "18.1.0",
"description": "A React Component library implementing the Base design language",
"keywords": [
"react",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from "react";
import { SlidingButton, THRESHOLD } from "..";

export function Scenario() {
return (
<SlidingButton
label="Slide to confirm"
threshold={THRESHOLD.low}
slideBackAfterMs={1500}
onComplete={() => {
// eslint-disable-next-line no-console
console.log("completed");
}}
/>
);
}
26 changes: 26 additions & 0 deletions src/sliding-button/__tests__/sliding-button-states.scenario.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from "react";
import { SlidingButton } from "..";

export function Scenario() {
const [isLoading, setIsLoading] = React.useState(false);
return (
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
<SlidingButton label="Slide to confirm" onComplete={() => {}} />
<SlidingButton
label="Slide to confirm"
isLoading={isLoading}
onComplete={() => {
setIsLoading(true);
setTimeout(() => setIsLoading(false), 2000);
}}
/>
<SlidingButton label="Slide to confirm" isDisabled />
</div>
);
}
20 changes: 20 additions & 0 deletions src/sliding-button/__tests__/sliding-button.scenario.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import * as React from "react";
import { SlidingButton } from "..";

export function Scenario() {
return (
<SlidingButton
label="Slide to confirm"
onComplete={() => {
// eslint-disable-next-line no-console
console.log("completed");
}}
/>
);
}
20 changes: 20 additions & 0 deletions src/sliding-button/__tests__/sliding-button.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
Copyright (c) Uber Technologies, Inc.

This source code is licensed under the MIT license found in the
LICENSE file in the root directory of this source tree.
*/
import React from "react";
import { Scenario as SlidingButtonDefault } from "./sliding-button.scenario";
import { Scenario as SlidingButtonStates } from "./sliding-button-states.scenario";
import { Scenario as SlidingButtonLowThreshold } from "./sliding-button-low-threshold.scenario";

export const Default = () => <SlidingButtonDefault />;
export const States = () => <SlidingButtonStates />;
export const LowThreshold = () => <SlidingButtonLowThreshold />;

export default {
meta: {
runtimeErrorsAllowed: true,
},
};
Loading
Loading