SwipeIndicator

Updated: Sep 24, 2026
SwipeIndicator is a display-only hint: a directional caret, an optional prompt, and an optional gradient scrim. It cannot receive focus, cannot be activated, and never becomes a standalone control. The surface around it owns the interaction; the indicator only explains it.
Reach for it when a supported interaction on the surrounding surface is not obvious from the layout — typically at the edge of the region the interaction affects. The component owns the caret rotation, the expand and collapse transition, the prompt measurement, the scrim gradient, and the attention nudge.

Usage

import { SwipeDirection, SwipeIndicator } from '@wearables-ui-toolkit/mrbd';

function DetailSurface({ hasMore, children }: DetailSurfaceProps) {
  return (
    <div className="surface">
      {children}
      <SwipeIndicator
        prompt="Swipe up for details"
        direction={SwipeDirection.UP}
        expanded={hasMore}
        animated
      />
    </div>
  );
}
.surface {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  block-size: 100%;
  gap: var(--uit-spacing-medium);
}
Hold a ref and nudge the caret when the user seems stuck:
import { useEffect, useRef } from 'react';
import {
  SwipeIndicator,
  type SwipeIndicatorHandle,
} from '@wearables-ui-toolkit/mrbd';

function IdleHint({ isIdle }: IdleHintProps) {
  const indicatorRef = useRef<SwipeIndicatorHandle>(null);

  useEffect(() => {
    if (isIdle) {
      // Three animated nudge cycles.
      indicatorRef.current?.nudge(3, true);
    }
  }, [isIdle]);

  return (
    <SwipeIndicator ref={indicatorRef} prompt="Swipe up" expanded animated />
  );
}
Drop the prompt and the scrim when the indicator sits on an opaque surface and the caret alone is enough:
import { SwipeDirection, SwipeIndicator } from '@wearables-ui-toolkit/mrbd';

<SwipeIndicator direction={SwipeDirection.DOWN} showScrim={false} aria-hidden />;

Props

The full prop type is SwipeIndicatorProps. Beyond the props below, SwipeIndicator accepts id, aria-label, aria-labelledby, aria-describedby, aria-hidden, and any data-* attribute. Other attributes — including role, tabIndex, and event handlers — are filtered out rather than forwarded, because the element is not interactive.
PropTypeDefaultDescription
prompt
string
''
Text shown beside the caret. An empty string renders no prompt element at all, leaving the caret alone.
expanded
boolean
false
Whether the prompt is visible.
direction
SwipeDirection
SwipeDirection.UP
Direction the caret points.
showScrim
boolean
true
Whether to draw the gradient scrim behind the indicator. false removes the scrim element entirely.
animated
boolean
false
Whether expand and collapse changes transition instead of jumping.
className
string
''
Extra class on the root element.
style
CSSProperties
{}
Inline styles on the root element, for placement only.

Types and enums

SwipeDirection

MemberDescription
UP
Caret points up, hinting a swipe up on the surrounding surface.
DOWN
Caret points down, hinting a swipe down on the surrounding surface.

Imperative handle

The ref exposes SwipeIndicatorHandle.
MethodSignatureDescription
nudge
nudge(count?: number, animated?: boolean): void
Runs count nudge cycles on the caret to draw attention. Both parameters default to one animated cycle. Passing false makes the caret jump instead of easing. Calling it again while a run is in flight replaces the remaining count rather than starting a second run.

Behavior

  • Direction. The caret rotates to match direction. Rotation itself is never animated, so switching direction is instant.
  • Expand and collapse. The prompt fades between visible and hidden. While collapsed, the content and scrim shift down by the measured prompt height, so the caret keeps the same position whether or not the prompt is showing. The component measures the prompt with a resize observer, re-measures when the text changes, and falls back to a window resize listener where no resize observer is available.
  • Motion.animated governs the expand and collapse transition only. It has no effect on the caret rotation, and the nudge carries its own animated flag.
  • Nudge. Each cycle shifts the caret a short distance along the vertical axis, holds it briefly, returns it, and — while cycles remain — waits a couple of seconds before starting the next one. Only the caret moves: the prompt and the scrim stay put. Pending timers are cleared on unmount.
  • Scrim. A top-to-bottom gradient from transparent to --uit-color-background-window, at a fixed opacity, drawn behind the content. Turn it off with showScrim when the indicator already sits on an opaque surface.
  • Additive light. The caret uses --uit-color-icon-secondary and the prompt uses --uit-color-text-secondary, both composited with a lighten blend so they lift against whatever is behind them. The prompt uses the uit-text-meta3 appearance and is centered under the caret.
  • Sizing. The indicator fills the width it is given and derives its height from the caret plus the prompt. Do not assign it a fixed device-sized box.
  • Not a control. The prop type does not accept onClick, role, or tabIndex, and anything beyond the listed accessibility props and data-* attributes is dropped before the root renders. The root is never focusable.

Accessibility

The component draws no accessibility semantics of its own. It is decorative in most layouts, so pass aria-hidden when the prompt merely restates something a focusable element already announces. When the hint is the only description of the interaction, give it an aria-label, or reference it from the affected region with aria-describedby and an id.
A collapsed prompt is hidden with visibility, not just opacity, so it is out of the accessibility tree and is not announced until expanded is set.
  • Place it at the edge of the region whose interaction it describes.
  • Keep the prompt to a few words naming the direction and the result.
  • Collapse the prompt once the user has performed the interaction.
  • Reach for nudge only as an occasional attention cue, not on a loop.

Avoid

  • Avoid attaching a click handler, focus, or a role to it — it is not a control.
  • Show the indicator only for an interaction the surrounding surface supports, so the prompt always leads to a working action.
  • Avoid repeating it per row inside a list.
  • Avoid restyling the caret or the scrim; use showScrim and the semantic tokens the component already resolves.
  • Avoid relying on the prompt as the only announcement of the interaction unless you also give the indicator an accessible name.