Skip to main content

Floating

Floating is a positioning primitive for custom overlays anchored to a trigger, another element, or a point in the viewport.

Import

import { Floating } from '@vortexlabs/vortex';
import type { FloatingProps } from '@vortexlabs/vortex';

Allied components

Other links

"use client";

import { Button, Card, Floating } from "@vortexlabs/vortex";

export const FloatingExample = () => (
  <Floating>
    <Floating.Trigger>
      {(attributes) => <Button attributes={attributes}>Open panel</Button>}
    </Floating.Trigger>
    <Floating.Content attributes={{ "aria-label": "Example panel" }}>
      <Card>Panel content</Card>
    </Floating.Content>
  </Floating>
);

Features

  • Positions content beside an element or viewport coordinates.
  • Opens on click, hover, or focus.
  • Adjusts placement when space is limited.
  • Supports controlled visibility and custom surfaces.

Anatomy

<Floating>
  <Floating.Trigger>
    {(attributes) => <Button attributes={attributes}>Open panel</Button>}
  </Floating.Trigger>
  <Floating.Content />
</Floating>
Component Part
Description
Controls visibility, placement, and focus.
Connects a trigger to the content.
Renders the portaled content.

Position

Set position to choose a side and alignment; start and end follow text direction. Use fallbackPositions to choose alternatives when space is limited.

"use client";

import { Button, Card, Floating, Stack } from "@vortexlabs/vortex";

export const FloatingPositionExample = () => (
  <Stack direction="row" gap={2} wrap>
    {(["top-center", "bottom-center", "start-center", "end-center"] as const).map((position) => (
      <Floating key={position} position={position}>
        <Floating.Trigger>
          {(attributes) => <Button attributes={attributes}>{position}</Button>}
        </Floating.Trigger>
        <Floating.Content>
          <Card>{position}</Card>
        </Floating.Content>
      </Floating>
    ))}
  </Stack>
);

Triggers

Set triggerType to open on click, hover, or focus. Use trapFocusMode={false} to let focus leave.

"use client";

import { Button, Card, Floating, Stack } from "@vortexlabs/vortex";

export const FloatingTriggersExample = () => (
  <Stack direction="row" gap={2} wrap>
    {(["click", "hover", "focus"] as const).map((triggerType) => (
      <Floating
        key={triggerType}
        triggerType={triggerType}
        trapFocusMode={false}
      >
        <Floating.Trigger>
          {(attributes) => <Button attributes={attributes}>{triggerType}</Button>}
        </Floating.Trigger>
        <Floating.Content>
          <Card>18.4 °C · Updated a moment ago</Card>
        </Floating.Content>
      </Floating>
    ))}
  </Stack>
);

Controlled Visibility

Pass active and update it in onOpen and onClose. Use defaultActive instead for an uncontrolled panel that starts open.

"use client";

import { Button, Card, Floating, Stack, Text } from "@vortexlabs/vortex";
import React from "react";

export const FloatingControlledExample = () => {
  const [active, setActive] = React.useState(false);

  return (
    <Floating
      active={active}
      onOpen={() => setActive(true)}
      onClose={() => setActive(false)}
    >
      <Floating.Trigger>
        {(attributes) => <Button attributes={attributes}>Inspect station</Button>}
      </Floating.Trigger>
      <Floating.Content attributes={{ "aria-label": "Station inspector" }}>
        <Card>
          <Stack gap={3}>
            <Text>North pier · 18.4 °C</Text>
            <Button size="sm" onClick={() => setActive(false)}>Close inspector</Button>
          </Stack>
        </Card>
      </Floating.Content>
    </Floating>
  );
};

Separate Anchor

Use positionRef to anchor content to another element. instanceRef exposes open(), close(), and updatePosition() for manual control.

Station A
"use client";

import { Button, Card, Floating, type FloatingInstance, Stack, Text } from "@vortexlabs/vortex";
import React from "react";

export const FloatingAnchorExample = () => {
  const positionRef = React.useRef<HTMLDivElement>(null);
  const instanceRef = React.useRef<FloatingInstance>(null);

  return (
    <Stack gap={4} align="start">
      <Stack padding={4} borderColor="default" borderRadius="sm" attributes={{ ref: positionRef }}>
        Station A
      </Stack>
      <Floating positionRef={positionRef} instanceRef={instanceRef} position="bottom-start">
        <Floating.Trigger>
          {(attributes) => <Button attributes={attributes}>Inspect Station A</Button>}
        </Floating.Trigger>
        <Floating.Content attributes={{ "aria-label": "Station A inspector" }}>
          <Card>
            <Stack gap={3}>
              <Text>18.4 °C · Updated a moment ago</Text>
              <Button size="sm" onClick={() => instanceRef.current?.close()}>Close</Button>
            </Stack>
          </Card>
        </Floating.Content>
      </Floating>
    </Stack>
  );
};

Accessibility

Key
Description
TabShiftTab
Cycles through panel controls in the default dialog mode.
EnterSpace
Toggles a click-triggered panel from its button.
Escape
Requests dismissal; update active in controlled mode.
Set aria-label in Floating.Content.attributes if the trigger does not name the dialog.