Getting Started
App Providerglobal.d.tsThemesTypes
cn(...args)Text ParserUnits Converters
AnchorAvatarBreadcrumbBurgerButtonCardCarouselCheckerCodeColor PickerCommandConfettiCopyButtonDouble Helix WordsFloating IndicatorGroupHighlight TextIndicatorInputKbdLabelLoaderPaginationPassword RequirementPolymorphic SlotProgressProseRatingRunning AreaScroll AreaSheetsSkeletonSliderStackSvgTableTabsTextareaTimelineTimesToasterTooltipTyping WordsTypography
useClickOutsideuseClipboarduseDeviceInfouseDialoguseDidUpdateuseDirectionuseDisclosureuseDocumentTitleuseDocumentVisibilityuseElementInfouseEyeDropperuseFetchuseFullscreenuseGeoLocationuseHotkeysuseHoveruseIduseImagePopupuseInputStateuseIntersectionuseIntervaluseIsomorphicEffectuseListStateuseLocalStorageuseMeasureScrollbaruseMediaQueryuseMergedRefuseMouseuseMoveuseMutationObserveruseNetworkuseOpenStateuseOrientationuseOSusePaginationusePWAInstalleruseRandomColorsuseReducedMotionuseReloaduseResizeObserveruseScrollIntoViewuseStateHistoryuseTimeoutuseTouchuseTriggeruseUncontrolleduseValidatedStateuseViewportSizeuseWindowEventuseWindowScroll
Docs
Web
Components
Floating Indicator
Double Helix Words

A visually striking text animation component that mimics the motion of a double helix.

Group

A utility component for grouping and aligning multiple elements as a cohesive unit.


Edit this page on GitHub
  • Started
  • Utilities
  • Configuration
  • Components
  • Hooks
  • Examples
  • Github
  • Contributing
⌘+J

© 2025 oeri rights MIT


Designed in Earth-616

Built by oeri

Floating Indicator

A UI element that floats near or on top of a reference element, often used for highlighting or drawing attention to specific areas.

Usage

Basic usage example to quickly see how the FloatingIndicator works.

"use client";
import { useState } from "react";
import { UnstyledButton } from "@/ui/button";
import { FloatingIndicator } from "@/ui/floating-indicator";

const data = ["React", "Vue", "Angular", "Svelte"];

export function FloatingIndicatorDemo() {
  const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
  const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
  const [active, setActive] = useState(0);
  const [hover, setHover] = useState<number | null>(null);

  const setControlRef = (index: number) => (node: HTMLButtonElement) => {
    controlsRefs[index] = node;
    setControlsRefs(controlsRefs);
  };
  const isActive = (index: number) => (active === index ? "true" : undefined);

  const controls = data.map((item, index) => (
    <UnstyledButton
      key={item}
      className="touch-manipulation appearance-none rounded-md text-sm font-medium leading-none text-muted-foreground transition-[color] hover:text-color data-[active]:text-color"
      ref={setControlRef(index)}
      onClick={() => setActive(index)}
      data-active={isActive(index)}
    >
      <span className="relative z-1 px-3 py-[.4375rem]" onMouseEnter={() => setHover(index)} onMouseLeave={() => setHover(null)}>{item}</span>
    </UnstyledButton>
  ));

  return (
    <div className="relative m-auto w-fit rounded-lg border bg-background p-[.3125rem]" ref={setRootRef}>
      {controls}
      <FloatingIndicator target={controlsRefs[hover ?? active]} parent={rootRef} className="rounded-md" />
    </div>
  );
}

Properties

Interactive configurator to explore customization options for the FloatingIndicator component.

Transition duration
Color
"use client";
import { useState } from "react";
import { ArrowIcon, CircleIcon } from "@/icons/*";
import { UnstyledButton } from "@/ui/button";
import { FloatingIndicator } from "@/ui/floating-indicator";
import { cvx, cvxVariants } from "xuxi";

const classes = cvx({
  variants: {
    selector: {
      root: "m-auto relative w-max p-2 rounded-lg border bg-muted/60",
      indicator: "rounded-md shadow-md border border-solid",
      controls: "flex",
      control: "size-[50px] flex items-center justify-center text-muted-foreground rounded-md data-[active]:text-color hover:text-color [@media(hover:hover)]:hover:bg-muted/60 [&>svg]:block [&>svg]:relative [&>svg]:z-1"
    }
  }
});

const style = (selector: NonNullable<cvxVariants<typeof classes>>["selector"]) => classes({ selector });

export function FloatingIndicatorMultipleDemo() {
  const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
  const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
  const [active, setActive] = useState<string>("center");

  const setControlRef = (name: string) => (node: HTMLButtonElement) => {
    controlsRefs[name] = node;
    setControlsRefs(controlsRefs);
  };
  const dataActive = (state: string) => ({ "data-active": active === state });

  const controls = [
    ["up-left", "up", "up-right"],
    ["left", "center", "right"],
    ["down-left", "down", "down-right"]
  ];

  const renderIcon = (name: string) => {
    if (name === "center") return <CircleIcon size={26} stroke={1.5} />;
    return <ArrowIcon arrow={name as any} size={26} stroke={1.5} />;
  };

  return (
    <div className={style("root")} dir="ltr" ref={setRootRef}>
      <FloatingIndicator color="yellowgreen" target={controlsRefs[active]} parent={rootRef} className={style("indicator")} />
      {controls.map((row, rowIndex) => (
        <div key={rowIndex} className={style("controls")}>
          {row.map(name => (
            <UnstyledButton
              key={name}
              className={style("control")}
              onClick={() => setActive(name)}
              ref={setControlRef(name)}
              {...dataActive(name)}
            >
              {renderIcon(name)}
            </UnstyledButton>
          ))}
        </div>
      ))}
    </div>
  );
}

API References

Styles API

Styles APITypeDefaultAnnotation
unstyled?booleanfalseif true, all default styles will be removed
className?stringundefinedpass to root component <div>
style?CSSProperties & Record<string, any>undefinedpass to root component <div>
color?CSSProperties["color"]undefinedDetermines to indicator color

Props API

Props APITypeDefaultAnnotation
parentHTMLElement | null | undefinedundefinedParent element with relative position based on which indicator position should be calculated
target?HTMLElement | null | undefinedundefinedTarget element over which indicator should be displayed
transitionDuration?number | string150Transition duration in ms
displayAfterTransitionEnd?boolean``Determines whether indicator should be displayed after transition ends, should be set if used inside a container that has transform: scale(n) styles

Source Codes

Full working code example, including necessary markup and styles. You can copy and paste this code directly to start using the component immediately.

floating-indicator.tsx