Add this component

npx zentauri-ui add slide-to-complete

Accessibility notes

Keyboard accessible by default with semantic markup, visible focus treatment, and tokenized states. Add descriptive labels for icon-only or decorative usage.

Dependency notes

Check Installation for shared peers. This component keeps styling in Tailwind classes and the --zui-* token contract.

Feedback

Slide to Complete

The Slide to Complete component requires users to drag a handle from the start position to the end of a track before the action is considered complete. It supports configurable thresholds, disabled/loading states, and keyboard completion.

Slide to approve
Slide to delete
Slide to unlock

Slide to complete playground

Pick an appearance, size, and threshold, then drag the thumb (or focus it and press Enter) to see the interaction complete. Toggle Show output / Show code and the snippet updates to match the selected variant.

Drag the thumb to the end, or focus it and press Enter / Space.


Slide to approve

All appearances

Every shipped appearance token at a glance. Click any swatch to load it into the playground above.

default
primary
secondary
success
destructive
warning
info
blue
violet
emerald
amber
rose
slate
zinc
gradient-blue
gradient-emerald
gradient-rose
glass

Controlled state

Drive the completed state from your own approved variable with value / onValueChange. Completing the drag calls onValueChange(true); the appearance and label switch to reflect the approved state, and the "Reset" button demonstrates setting it back from the outside.


Slide to approve
Approved: false

Slide To Complete API

Generated from the package prop types and variant definitions.

SlideToComplete

SlideToCompleteProps

Variants

PropTypeDefault
size
lgmdsm
md

State

PropTypeDefault
defaultValueboolean | undefinednone
onValueChange((completed: boolean) => void) | undefinednone
valueboolean | undefinednone

Behavior

PropTypeDefault
appearance'default' | 'blue' | 'slate' | 'zinc' | 'rose' | 'emerald' | 'gradient-blue' | 'success' | 'warning' | 'info' | 'violet' | 'glass' | 'secondary' | 'destructive' | 'amber' | 'primary' | 'gradient-rose' | 'gradient-emerald' | undefinednone
directionSlideToCompleteDirection | undefinednone
disabledboolean | undefinednone
errorboolean | undefinednone
labelReactNodenone
loadingboolean | undefinednone
onComplete(() => void | Promise<void>) | undefinednone
onDragStart(DragEventHandler<HTMLDivElement> & (() => void)) | undefinednone
onProgressChange((progress: number) => void) | undefinednone
onReset(ReactEventHandler<HTMLDivElement> & (() => void)) | undefinednone
resetOnReleaseSnap the thumb back to the start when released before the threshold. Defaults to true.boolean | undefinednone
successboolean | undefinednone
thresholdNormalized 0-1 point at which dragging auto-completes. Defaults to 0.9.number | undefinednone
thumbIconReactNodenone

Content

PropTypeDefault
childrenReactNodenone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SlideToCompleteIcon

SlideToCompleteIconProps

Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLSpanElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SlideToCompleteLabel

SlideToCompleteLabelProps

Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLSpanElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SlideToCompleteProgress

SlideToCompleteProgressProps

Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SlideToCompleteRoot

SlideToCompleteRootProps

State

PropTypeDefault
defaultValueboolean | undefinednone
onValueChange((completed: boolean) => void) | undefinednone
valueboolean | undefinednone

Behavior

PropTypeDefault
appearance'default' | 'blue' | 'slate' | 'zinc' | 'rose' | 'emerald' | 'gradient-blue' | 'success' | 'warning' | 'info' | 'violet' | 'glass' | 'secondary' | 'destructive' | 'amber' | 'primary' | 'gradient-rose' | 'gradient-emerald' | undefinednone
directionSlideToCompleteDirection | undefinednone
disabledboolean | undefinednone
errorboolean | undefinednone
loadingboolean | undefinednone
onComplete(() => void | Promise<void>) | undefinednone
onDragStart(DragEventHandler<HTMLDivElement> & (() => void)) | undefinednone
onProgressChange((progress: number) => void) | undefinednone
onReset(ReactEventHandler<HTMLDivElement> & (() => void)) | undefinednone
resetOnReleaseSnap the thumb back to the start when released before the threshold. Defaults to true.boolean | undefinednone
sizeSlideToCompleteSize | undefinednone
successboolean | undefinednone
thresholdNormalized 0-1 point at which dragging auto-completes. Defaults to 0.9.number | undefinednone

Content

PropTypeDefault
childrenReactNodenone
Inherited HTML props
PropTypeDefault
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SlideToCompleteThumb

SlideToCompleteThumbProps

Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLButtonElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SlideToCompleteTrack

SlideToCompleteTrackProps

Inherited HTML props
PropTypeDefault
childrenReactNodenone
classNamestring | undefinednone
idstring | undefinednone
onClickMouseEventHandler<HTMLDivElement> | undefinednone
styleCSSProperties | undefinednone
titlestring | undefinednone

SlideToCompleteVariant

SlideToCompleteVariantProps

Behavior

PropTypeDefault
size'md' | 'sm' | 'lg' | null | undefinednone
CSS variable overrides

Slide to Complete CSS variables

Override these slide-to-complete variables on :root, a theme selector, or a component wrapper.

280 variables

Pattern: --zui-<component>-<slot?>-<variant?>-<property>-<state?>-dark?

:root {
  --zui-slide-to-complete-amber-label-fg: #451a03;
  --zui-slide-to-complete-amber-progress-bg: #f59e0b;
  --zui-slide-to-complete-amber-thumb-bg: #f59e0b;
  --zui-slide-to-complete-amber-thumb-border: #f59e0b;
  --zui-slide-to-complete-amber-thumb-fg: #451a03;
  --zui-slide-to-complete-amber-track-bg: #fffbeb;
  --zui-slide-to-complete-amber-track-border: #fde68a;
  --zui-slide-to-complete-blue-label-fg: #172554;
  --zui-slide-to-complete-blue-progress-bg: #3b82f6;
  --zui-slide-to-complete-blue-thumb-bg: #2563eb;
  --zui-slide-to-complete-blue-thumb-border: #2563eb;
  --zui-slide-to-complete-blue-thumb-fg: #ffffff;
  --zui-slide-to-complete-blue-track-bg: #eff6ff;
  --zui-slide-to-complete-blue-track-border: #bfdbfe;
  --zui-slide-to-complete-default-label-fg: #52525b;
  --zui-slide-to-complete-default-progress-bg: #d4d4d8;
  --zui-slide-to-complete-default-thumb-bg: #ffffff;
  --zui-slide-to-complete-default-thumb-border: #d4d4d8;
  --zui-slide-to-complete-default-thumb-fg: #3f3f46;
  --zui-slide-to-complete-default-track-bg: #f4f4f5;
  --zui-slide-to-complete-default-track-border: #e4e4e7;
  --zui-slide-to-complete-destructive-label-fg: #450a0a;
  --zui-slide-to-complete-destructive-progress-bg: #ef4444;
  --zui-slide-to-complete-destructive-thumb-bg: #dc2626;
  --zui-slide-to-complete-destructive-thumb-border: #dc2626;
  --zui-slide-to-complete-destructive-thumb-fg: #ffffff;
  --zui-slide-to-complete-destructive-track-bg: #fef2f2;
  --zui-slide-to-complete-destructive-track-border: #fecaca;
  --zui-slide-to-complete-emerald-label-fg: #022c22;
  --zui-slide-to-complete-emerald-progress-bg: #10b981;
  --zui-slide-to-complete-emerald-thumb-bg: #059669;
  --zui-slide-to-complete-emerald-thumb-border: #059669;
  --zui-slide-to-complete-emerald-thumb-fg: #ffffff;
  --zui-slide-to-complete-emerald-track-bg: #ecfdf5;
  --zui-slide-to-complete-emerald-track-border: #a7f3d0;
  --zui-slide-to-complete-focus-ring: #2563eb;
  --zui-slide-to-complete-glass-label-fg: #1e293b;
  --zui-slide-to-complete-glass-progress-bg: rgba(59,130,246,0.32);
  --zui-slide-to-complete-glass-thumb-bg: rgba(255,255,255,0.86);
  --zui-slide-to-complete-glass-thumb-border: rgba(255,255,255,0.7);
  --zui-slide-to-complete-glass-thumb-fg: #1e293b;
  --zui-slide-to-complete-glass-track-bg: rgba(255,255,255,0.62);
  --zui-slide-to-complete-glass-track-border: rgba(148,163,184,0.35);
  --zui-slide-to-complete-gradient-blue-label-fg: #172554;
  --zui-slide-to-complete-gradient-blue-progress-from: #2563eb;
  --zui-slide-to-complete-gradient-blue-progress-to: #06b6d4;
  --zui-slide-to-complete-gradient-blue-thumb-border: #3b82f6;
  --zui-slide-to-complete-gradient-blue-thumb-fg: #ffffff;
  --zui-slide-to-complete-gradient-blue-thumb-from: #2563eb;
  --zui-slide-to-complete-gradient-blue-thumb-to: #06b6d4;
  --zui-slide-to-complete-gradient-blue-track-border: #bfdbfe;
  --zui-slide-to-complete-gradient-blue-track-from: #eff6ff;
  --zui-slide-to-complete-gradient-blue-track-to: #e0f2fe;
  --zui-slide-to-complete-gradient-emerald-label-fg: #022c22;
  --zui-slide-to-complete-gradient-emerald-progress-from: #059669;
  --zui-slide-to-complete-gradient-emerald-progress-to: #14b8a6;
  --zui-slide-to-complete-gradient-emerald-thumb-border: #10b981;
  --zui-slide-to-complete-gradient-emerald-thumb-fg: #ffffff;
  --zui-slide-to-complete-gradient-emerald-thumb-from: #059669;
  --zui-slide-to-complete-gradient-emerald-thumb-to: #14b8a6;
  --zui-slide-to-complete-gradient-emerald-track-border: #a7f3d0;
  --zui-slide-to-complete-gradient-emerald-track-from: #ecfdf5;
  --zui-slide-to-complete-gradient-emerald-track-to: #f0fdfa;
  --zui-slide-to-complete-gradient-rose-label-fg: #4c0519;
  --zui-slide-to-complete-gradient-rose-progress-from: #e11d48;
  --zui-slide-to-complete-gradient-rose-progress-to: #d946ef;
  --zui-slide-to-complete-gradient-rose-thumb-border: #f43f5e;
  --zui-slide-to-complete-gradient-rose-thumb-fg: #ffffff;
  --zui-slide-to-complete-gradient-rose-thumb-from: #e11d48;
  --zui-slide-to-complete-gradient-rose-thumb-to: #d946ef;
  --zui-slide-to-complete-gradient-rose-track-border: #fecdd3;
  --zui-slide-to-complete-gradient-rose-track-from: #fff1f2;
  --zui-slide-to-complete-gradient-rose-track-to: #fdf2f8;
  --zui-slide-to-complete-info-label-fg: #083344;
  --zui-slide-to-complete-info-progress-bg: #06b6d4;
  --zui-slide-to-complete-info-thumb-bg: #0891b2;
  --zui-slide-to-complete-info-thumb-border: #0891b2;
  --zui-slide-to-complete-info-thumb-fg: #ffffff;
  --zui-slide-to-complete-info-track-bg: #ecfeff;
  --zui-slide-to-complete-info-track-border: #a5f3fc;
  --zui-slide-to-complete-max-width: 24rem;
  --zui-slide-to-complete-primary-label-fg: #172554;
  --zui-slide-to-complete-primary-progress-bg: #3b82f6;
  --zui-slide-to-complete-primary-thumb-bg: #2563eb;
  --zui-slide-to-complete-primary-thumb-border: #2563eb;
  --zui-slide-to-complete-primary-thumb-fg: #ffffff;
  --zui-slide-to-complete-primary-track-bg: #eff6ff;
  --zui-slide-to-complete-primary-track-border: #bfdbfe;
  --zui-slide-to-complete-rose-label-fg: #4c0519;
  --zui-slide-to-complete-rose-progress-bg: #f43f5e;
  --zui-slide-to-complete-rose-thumb-bg: #e11d48;
  --zui-slide-to-complete-rose-thumb-border: #e11d48;
  --zui-slide-to-complete-rose-thumb-fg: #ffffff;
  --zui-slide-to-complete-rose-track-bg: #fff1f2;
  --zui-slide-to-complete-rose-track-border: #fecdd3;
  --zui-slide-to-complete-secondary-label-fg: #334155;
  --zui-slide-to-complete-secondary-progress-bg: #94a3b8;
  --zui-slide-to-complete-secondary-thumb-bg: #334155;
  --zui-slide-to-complete-secondary-thumb-border: #64748b;
  --zui-slide-to-complete-secondary-thumb-fg: #ffffff;
  --zui-slide-to-complete-secondary-track-bg: #f1f5f9;
  --zui-slide-to-complete-secondary-track-border: #e2e8f0;
  --zui-slide-to-complete-slate-label-fg: #334155;
  --zui-slide-to-complete-slate-progress-bg: #64748b;
  --zui-slide-to-complete-slate-thumb-bg: #1e293b;
  --zui-slide-to-complete-slate-thumb-border: #475569;
  --zui-slide-to-complete-slate-thumb-fg: #ffffff;
  --zui-slide-to-complete-slate-track-bg: #f1f5f9;
  --zui-slide-to-complete-slate-track-border: #cbd5e1;
  --zui-slide-to-complete-success-label-fg: #022c22;
  --zui-slide-to-complete-success-progress-bg: #10b981;
  --zui-slide-to-complete-success-thumb-bg: #059669;
  --zui-slide-to-complete-success-thumb-border: #059669;
  --zui-slide-to-complete-success-thumb-fg: #ffffff;
  --zui-slide-to-complete-success-track-bg: #ecfdf5;
  --zui-slide-to-complete-success-track-border: #a7f3d0;
  --zui-slide-to-complete-thumb-size: 2.75rem;
  --zui-slide-to-complete-track-bg: #f3f4f6;
  --zui-slide-to-complete-track-height: 3.5rem;
  --zui-slide-to-complete-violet-label-fg: #2e1065;
  --zui-slide-to-complete-violet-progress-bg: #8b5cf6;
  --zui-slide-to-complete-violet-thumb-bg: #7c3aed;
  --zui-slide-to-complete-violet-thumb-border: #7c3aed;
  --zui-slide-to-complete-violet-thumb-fg: #ffffff;
  --zui-slide-to-complete-violet-track-bg: #f5f3ff;
  --zui-slide-to-complete-violet-track-border: #ddd6fe;
  --zui-slide-to-complete-warning-label-fg: #451a03;
  --zui-slide-to-complete-warning-progress-bg: #f59e0b;
  --zui-slide-to-complete-warning-thumb-bg: #f59e0b;
  --zui-slide-to-complete-warning-thumb-border: #f59e0b;
  --zui-slide-to-complete-warning-thumb-fg: #451a03;
  --zui-slide-to-complete-warning-track-bg: #fffbeb;
  --zui-slide-to-complete-warning-track-border: #fde68a;
  --zui-slide-to-complete-zinc-label-fg: #3f3f46;
  --zui-slide-to-complete-zinc-progress-bg: #71717a;
  --zui-slide-to-complete-zinc-thumb-bg: #27272a;
  --zui-slide-to-complete-zinc-thumb-border: #52525b;
  --zui-slide-to-complete-zinc-thumb-fg: #ffffff;
  --zui-slide-to-complete-zinc-track-bg: #f4f4f5;
  --zui-slide-to-complete-zinc-track-border: #d4d4d8;
}

/* Dark theme variables follow the same names with -dark appended. */
.dark {
  --zui-slide-to-complete-amber-label-fg-dark: #fef3c7;
  --zui-slide-to-complete-amber-progress-bg-dark: #fbbf24;
  /* ...same variables with -dark at the end */
}

How it works

The component uses a drag interaction model where the user slides a thumb along a track. Progress is normalized between 0 and 1. When the threshold (default 0.9) is crossed, the component triggers the onComplete callback and animates the thumb to the end position.

Compound API

Users can compose the component from primitives: <SlideToComplete.Root>, <SlideToComplete.Track>, <SlideToComplete.Progress>, <SlideToComplete.Label>, <SlideToComplete.Thumb>, and <SlideToComplete.Icon>. The root owns all interaction state and context, while children consume it.

Keyboard interaction

Tab to focus the thumb, then use Space or Enter to complete the action. Arrow keys (←/→) can adjust progress. The component supports disabled and loading states that prevent keyboard completion.

Accessibility

The thumb is a semantic button element. Accessible label comes from the label prop or aria-label. Keyboard focus is managed, and reduced motion preferences are respected. Test with screen readers for your specific use case.

State variants

The component supports idle, dragging, resetting, completed, disabled, loading, and error states. Visual styling can be customized via data attributes (data-state, data-appearance, data-size) and CSS variables.

FAQ

Does the Slide to Complete work with keyboard navigation?

Yes. The thumb can be focused via Tab key and completed using Space or Enter. Arrow keys can also adjust progress.

Can I customize the appearance of the slider?

Yes. The component ships 17+ appearance tokens (default, primary, secondary, success, destructive, warning, info, several accent colors, gradients, and a glass variant) plus sm/md/lg size variants, all overridable through CSS variables.

What happens if the user releases before reaching the threshold?

By default, the thumb snaps back to the start position. This behavior can be changed with the resetOnRelease prop.