Skip to content

Commit 7e3beed

Browse files
Deliazclaude
andauthored
Add SlidingButton component (#5398)
* Add SlidingButton component Introduces the SlidingButton (swipe-to-confirm) component to the Base Design System. Uses a drag gesture instead of a tap to confirm actions, reducing accidental triggers and reinforcing user intent. Features: - Drag interaction with configurable threshold (high: 80%, low: 20%) - Loading and disabled states - Auto-reset via slideBackAfterMs - Keyboard support (Enter / Space) - RTL support - Full override system (Root, Track, Slider, Label, CompletedLabel, Grabber, LoadingOverlay, LoadingSpinner) - ARIA attributes (role, aria-label, aria-busy, aria-disabled, aria-live) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Add SlidingButton tests, stories, and bump version to 18.1.0 - Unit tests covering rendering, a11y attributes, keyboard interaction, loading/disabled states, slideBackAfterMs reset, and ref forwarding - Ubook stories: default, states, low-threshold scenarios - npm version minor (18.0.0 → 18.1.0) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Add SlidingButton to docs navigation routes Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Move SlidingButton to Inputs section in docs nav Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * Fix threshold wording and table rendering in docs Replace markdown table (unsupported without remark-gfm) with bullet list. Use 'at least N%' instead of '>N%' for clarity. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent a2c7918 commit 7e3beed

18 files changed

Lines changed: 1038 additions & 1 deletion
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
import { SlidingButton, THRESHOLD } from "baseui/sliding-button";
8+
import { PropTypes } from "react-view";
9+
import type { TConfig } from "../types";
10+
11+
const SlidingButtonConfig: TConfig = {
12+
componentName: "SlidingButton",
13+
imports: {
14+
"baseui/sliding-button": {
15+
named: ["SlidingButton"],
16+
},
17+
},
18+
scope: {
19+
SlidingButton,
20+
THRESHOLD,
21+
},
22+
theme: [],
23+
props: {
24+
label: {
25+
value: "Slide to confirm",
26+
type: PropTypes.String,
27+
description: "Text displayed in the button track.",
28+
},
29+
onComplete: {
30+
value: '() => console.log("completed!")',
31+
type: PropTypes.Function,
32+
description: "Called once when the user drags past the threshold.",
33+
},
34+
threshold: {
35+
value: "THRESHOLD.high",
36+
defaultValue: "THRESHOLD.high",
37+
options: THRESHOLD,
38+
type: PropTypes.Enum,
39+
description:
40+
"Completion threshold — 'high' requires 80%, 'low' requires 20%.",
41+
imports: {
42+
"baseui/sliding-button": {
43+
named: ["THRESHOLD"],
44+
},
45+
},
46+
},
47+
isLoading: {
48+
value: false,
49+
type: PropTypes.Boolean,
50+
description: "Shows loading spinner, disables interaction.",
51+
},
52+
isDisabled: {
53+
value: false,
54+
type: PropTypes.Boolean,
55+
description: "Grays out the component, disables interaction.",
56+
},
57+
slideBackAfterMs: {
58+
value: undefined,
59+
type: PropTypes.Number,
60+
description: "Auto-reset to idle state after N milliseconds.",
61+
},
62+
overrides: {
63+
value: undefined,
64+
type: PropTypes.Custom,
65+
description: "Override internal elements.",
66+
},
67+
},
68+
};
69+
70+
export default SlidingButtonConfig;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
import * as React from "react";
8+
import { SlidingButton } from "baseui/sliding-button";
9+
10+
export default function Example() {
11+
return (
12+
<SlidingButton
13+
label="Slide to confirm"
14+
onComplete={() => {
15+
// eslint-disable-next-line no-console
16+
console.log("Completed!");
17+
}}
18+
/>
19+
);
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
import * as React from "react";
8+
import { SlidingButton, THRESHOLD } from "baseui/sliding-button";
9+
10+
export default function Example() {
11+
return (
12+
<SlidingButton
13+
label="Slide to confirm"
14+
threshold={THRESHOLD.low}
15+
slideBackAfterMs={1500}
16+
onComplete={() => {
17+
// eslint-disable-next-line no-console
18+
console.log("Completed!");
19+
}}
20+
/>
21+
);
22+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
import * as React from "react";
8+
import { SlidingButton } from "baseui/sliding-button";
9+
10+
export default function Example() {
11+
const [isLoading, setIsLoading] = React.useState(false);
12+
return (
13+
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
14+
<SlidingButton label="Slide to confirm" onComplete={() => {}} />
15+
<SlidingButton
16+
label="Slide to confirm"
17+
isLoading={isLoading}
18+
onComplete={() => {
19+
setIsLoading(true);
20+
setTimeout(() => setIsLoading(false), 2000);
21+
}}
22+
/>
23+
<SlidingButton label="Slide to confirm" isDisabled />
24+
</div>
25+
);
26+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import Example from "../../components/example";
2+
import Exports from "../../components/exports";
3+
import Layout from "../../components/layout";
4+
import SEO from "../../components/meta-seo";
5+
6+
import Basic from "examples/sliding-button/basic.tsx";
7+
import States from "examples/sliding-button/states.tsx";
8+
import LowThreshold from "examples/sliding-button/low-threshold.tsx";
9+
10+
import * as SlidingButtonExports from "baseui/sliding-button";
11+
12+
import Yard from "../../components/yard/index";
13+
import slidingButtonYardConfig from "../../components/yard/config/sliding-button";
14+
15+
<SEO
16+
description="Sliding buttons use a drag gesture instead of a tap to confirm actions, reducing accidental triggers and reinforcing user intent."
17+
keywords="sliding button, swipe to confirm, slide to confirm, react drag button"
18+
/>
19+
20+
export default Layout;
21+
22+
# Sliding Button
23+
24+
<Yard placeholderHeight={56} {...slidingButtonYardConfig} />
25+
26+
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.
27+
28+
## When to use
29+
30+
- **Prevent accidental actions**: Situations requiring confirmation to avoid unintended triggers.
31+
- **Costly or non-reversible actions**: Confirming intent on high-stakes operations (e.g., emergency assistance, trip completion).
32+
- **Multi-step flow completion**: Final step in extended user journeys.
33+
34+
> **Caution**: If the action is not critical, a sliding button may add unnecessary complexity. Use a standard [Button](/components/button) instead.
35+
36+
## Examples
37+
38+
<Example title="Basic usage" path="sliding-button/basic.tsx">
39+
<Basic />
40+
</Example>
41+
42+
<Example
43+
title="States (default, loading, disabled)"
44+
path="sliding-button/states.tsx"
45+
>
46+
<States />
47+
</Example>
48+
49+
<Example
50+
title="Low threshold (20% drag)"
51+
path="sliding-button/low-threshold.tsx"
52+
>
53+
<LowThreshold />
54+
</Example>
55+
56+
## Threshold
57+
58+
Two completion thresholds support different user types:
59+
60+
- **`THRESHOLD.high`** (default) — at least 80% drag distance. Standard, requires deliberate intent.
61+
- **`THRESHOLD.low`** — at least 20% drag distance. For users with motor disabilities, seniors, or low-friction flows.
62+
63+
## Accessibility
64+
65+
- **Keyboard support**: Press Enter or Space to activate — provides a standard button interaction for screen reader users.
66+
- **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.
67+
- The component sets `role="button"`, `aria-busy` during loading, and `aria-disabled` when disabled.
68+
69+
## Content guidelines
70+
71+
- **Use "Slide"** in the visible label to indicate the drag interaction.
72+
- **Avoid "Swipe"** — it carries a navigational connotation rather than task completion.
73+
- **Omit "Slide"** from `aria-label` when VoiceOver/TalkBack is expected — use the `aria-label` prop to provide a plain-language alternative.
74+
75+
<Exports
76+
component={SlidingButtonExports}
77+
title="Sliding Button exports"
78+
path="baseui/sliding-button"
79+
/>

documentation-site/routes.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ const routes = [
131131
title: 'Slider',
132132
itemId: '/components/slider',
133133
},
134+
{
135+
title: 'Sliding Button',
136+
itemId: '/components/sliding-button',
137+
},
134138
{
135139
title: 'Stepper',
136140
itemId: '/components/stepper',

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "baseui",
3-
"version": "18.0.0",
3+
"version": "18.1.0",
44
"description": "A React Component library implementing the Base design language",
55
"keywords": [
66
"react",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
import * as React from "react";
8+
import { SlidingButton, THRESHOLD } from "..";
9+
10+
export function Scenario() {
11+
return (
12+
<SlidingButton
13+
label="Slide to confirm"
14+
threshold={THRESHOLD.low}
15+
slideBackAfterMs={1500}
16+
onComplete={() => {
17+
// eslint-disable-next-line no-console
18+
console.log("completed");
19+
}}
20+
/>
21+
);
22+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
import * as React from "react";
8+
import { SlidingButton } from "..";
9+
10+
export function Scenario() {
11+
const [isLoading, setIsLoading] = React.useState(false);
12+
return (
13+
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
14+
<SlidingButton label="Slide to confirm" onComplete={() => {}} />
15+
<SlidingButton
16+
label="Slide to confirm"
17+
isLoading={isLoading}
18+
onComplete={() => {
19+
setIsLoading(true);
20+
setTimeout(() => setIsLoading(false), 2000);
21+
}}
22+
/>
23+
<SlidingButton label="Slide to confirm" isDisabled />
24+
</div>
25+
);
26+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
Copyright (c) Uber Technologies, Inc.
3+
4+
This source code is licensed under the MIT license found in the
5+
LICENSE file in the root directory of this source tree.
6+
*/
7+
import * as React from "react";
8+
import { SlidingButton } from "..";
9+
10+
export function Scenario() {
11+
return (
12+
<SlidingButton
13+
label="Slide to confirm"
14+
onComplete={() => {
15+
// eslint-disable-next-line no-console
16+
console.log("completed");
17+
}}
18+
/>
19+
);
20+
}

0 commit comments

Comments
 (0)