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.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);
}
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 />
);
}
import { SwipeDirection, SwipeIndicator } from '@wearables-ui-toolkit/mrbd';
<SwipeIndicator direction={SwipeDirection.DOWN} showScrim={false} aria-hidden />;
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.| Prop | Type | Default | Description |
|---|---|---|---|
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. |
SwipeDirection| Member | Description |
|---|---|
UP | Caret points up, hinting a swipe up on the surrounding surface. |
DOWN | Caret points down, hinting a swipe down on the surrounding surface. |
ref exposes SwipeIndicatorHandle.| Method | Signature | Description |
|---|---|---|
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. |
direction. Rotation itself is never animated, so switching direction is instant.animated governs the expand and collapse transition only. It has no effect on the caret rotation, and the nudge carries its own animated flag.--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.--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.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.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.visibility, not just opacity, so it is out of the accessibility tree and is not announced until expanded is set.nudge only as an occasional attention cue, not on a loop.showScrim and the semantic tokens the component already resolves.