Hooks
useSlidingIndicator
Slide a highlight between items of a segmented control, tab bar or filter group, following the active item through resizes.
Preview
128 posts
Usage
import {
slidingIndicatorTransitionClassName,
useSlidingIndicator,
} from '@vitnode/core/hooks/use-sliding-indicator'const [active, setActive] = React.useState(0)
const { containerRef, indicatorRef, isReady } =
useSlidingIndicator<HTMLDivElement>(active)
return (
<div
className="bg-muted relative flex gap-1 rounded-lg p-1"
ref={containerRef}
role="group"
>
<span
aria-hidden
className={cn(
'bg-background pointer-events-none absolute top-0 left-0 rounded-md opacity-0',
isReady && slidingIndicatorTransitionClassName,
)}
ref={indicatorRef}
/>
{filters.map((label, index) => (
<button
aria-pressed={active === index}
className="relative rounded-md px-3 py-1.5 text-sm"
data-sliding-indicator-item
key={label}
onClick={() => setActive(index)}
type="button"
>
{label}
</button>
))}
</div>
)Mark every item with data-sliding-indicator-item and pass the active index.
The hook sizes and moves the indicator to sit behind that item. VitNode's theme
switcher and mobile navigation bar work this way.
Styling checklist
- The container is
relative. The indicator isabsolute top-0 left-0and starts atopacity-0. The hook sets its size, position and opacity. - Items are
relative, so they sit above the indicator. - The indicator is
aria-hidden. Put the active state on the items witharia-pressed,aria-selectedoraria-current.
How it behaves
- Follows resizes. A
ResizeObserveron the container repositions the indicator, for example when labels change language. - No slide on first render.
isReadyturnstrueone frame after mount. Add the transition class only then, or the indicator flies in from the corner. - Hides when nothing is active. An index with no matching item, like
-1, hides the indicator. - Respects reduced motion. The transition class slides for 300ms on
ease-fluidand turns off underprefers-reduced-motion.
API
Prop
Type
Returns:
Prop
Type