How to Use Tooltip and HoverCard Overlay Components in Astryx

Astryx provides two complementary overlay components—Tooltip for brief, non-interactive text hints and HoverCard for rich, interactive content—both built on the shared useLayer infrastructure with standardized placement APIs and WCAG-compliant focus management.

The facebook/astryx repository includes a robust design system with advanced overlay primitives for React applications. Understanding the architectural differences between Tooltip and HoverCard ensures you render accessible, performant user interfaces that handle focus management and positioning correctly.

Tooltip vs HoverCard: Key Differences

Astryx distinguishes these components by content type and interactivity requirements:

  • Tooltip: Designed for short, static text labels (e.g., icon descriptions, button hints). It renders content in a portal and supports an optional dashed underline via the hasHoverIndication prop for text-only children.
  • HoverCard: Built for richer, interactive content (e.g., profile cards, action menus). Unlike Tooltip, it renders an inline-safe floating layer that remains in the DOM flow, ensuring focusable elements inside stay reachable via keyboard navigation.

Both components share the same LayerPlacement and LayerAlignment props (e.g., above, below, right) and support controlled state via isOpen, isDefaultOpen, and onOpenChange. They also allow external anchoring through the anchorRef prop for sibling-mode positioning.

Core Architecture and Source Files

Understanding the underlying hooks clarifies when to choose each component:

useTooltip (source: packages/core/src/Tooltip/useTooltip.tsx) supplies a ref to the trigger element, generates aria-describedby attributes for accessibility, and returns a renderTooltip function that creates the floating element.

Tooltip (source: packages/core/src/Tooltip/Tooltip.tsx) composes useTooltip and decides whether to render inline (as a <span>) for text-only children or wrap element children in a display:contents container to preserve refs.

useHoverCard (source: packages/core/src/HoverCard/useHoverCard.tsx) acts as a thin wrapper around the core layer system. Crucially, it does not portal the layer, keeping the floating card in-document for interactive content.

HoverCard (source: packages/core/src/HoverCard/HoverCard.tsx) wraps useHoverCard and renders the trigger together with the card content via renderHoverCard.

Both hooks expose a focus trigger parameter (auto, always, never) that determines keyboard focus interactions, making them suitable for complex composite widgets.

Implementation Examples

Basic Tooltip with Button Trigger

Use Tooltip for simple text hints that appear after the default 200ms delay.

import {Tooltip} from '@astryxdesign/core/Tooltip';
import {Button} from '@astryxdesign/core/Button';

function ExampleTooltip() {
  return (
    <Tooltip content="Save your changes" placement="above">
      <Button>Save</Button>
    </Tooltip>
  );
}

The tooltip accepts delay, hideDelay, and focusTrigger props to customize timing and keyboard behavior.

Tooltip with External Anchor (Sibling Mode)

When you need to attach a tooltip to a sibling element rather than wrapping it as a child, use the anchorRef prop.

import {Tooltip} from '@astryxdesign/core/Tooltip';
import {useRef} from 'react';

function ExternalAnchor() {
  const ref = useRef<HTMLDivElement>(null);

  return (
    <>
      <div ref={ref}>Hover over me</div>
      <Tooltip anchorRef={ref} content="External anchor tooltip" />
    </>
  );
}

In this mode, Tooltip renders as a sibling of the referenced element and requires no children.

Interactive HoverCard with Profile Content

Use HoverCard when the overlay contains interactive controls like buttons or links that must remain focusable.

import {HoverCard} from '@astryxdesign/core/HoverCard';
import {Avatar} from '@astryxdesign/core/Avatar';
import {Button} from '@astryxdesign/core/Button';

function ProfileCard({user}) {
  return (
    <div style={{padding: 12}}>
      <Avatar src={user.avatar} alt={user.name} />
      <h4>{user.name}</h4>
      <Button onClick={() => alert('Message sent')}>Message</Button>
    </div>
  );
}

function ExampleHoverCard() {
  return (
    <HoverCard content={<ProfileCard user={myUser} />} placement="right">
      <span>John Doe</span>
    </HoverCard>
  );
}

Because HoverCard does not portal the layer, inner controls stay reachable via Tab navigation without being reparented in the DOM.

HoverCard with External Anchor Reference

Similar to Tooltip, HoverCard supports external anchoring via anchorRef for layouts where wrapping is not feasible.

import {HoverCard} from '@astryxdesign/core/HoverCard';
import {useRef} from 'react';

function AnchorHoverCard() {
  const ref = useRef<HTMLDivElement>(null);
  return (
    <>
      <div ref={ref}>Hover over this area</div>
      <HoverCard anchorRef={ref} content={<p>Additional info</p>} />
    </>
  );
}

Summary

  • Tooltip handles brief, non-interactive text hints and renders content through a portal, with optional hover indication for text-only triggers.
  • HoverCard manages rich, interactive overlays that remain in the document flow, ensuring focusable elements stay accessible.
  • Both components utilize the useLayer infrastructure in packages/core/src/ and support standardized placement, alignment, and controlled state APIs.
  • Use anchorRef for sibling-mode positioning when you cannot wrap the trigger element directly.
  • Configure focusTrigger and delay props to fine-tune accessibility and interaction timing.

Frequently Asked Questions

What is the difference between Tooltip and HoverCard in Astryx?

Tooltip is optimized for short, static text labels without interactive elements, rendering content in a portal with optional hover indication. HoverCard is designed for richer content containing buttons, links, or complex layouts that must stay in the DOM flow to remain focusable and interactive.

Can HoverCard contain interactive elements like buttons?

Yes. Unlike Tooltip, HoverCard renders its layer inline rather than in a portal, which keeps interactive elements like buttons and links in the natural tab order. This is implemented in packages/core/src/HoverCard/useHoverCard.tsx to ensure the floating card stays inside the DOM flow.

How do I control the open state programmatically?

Both Tooltip and HoverCard support controlled state via three props: isOpen for full control, isDefaultOpen for initial state, and onOpenChange for change callbacks. These props allow integration with parent state management or external events.

Where are the useTooltip and useHoverCard hooks implemented?

The useTooltip hook is located at packages/core/src/Tooltip/useTooltip.tsx and manages positioning, delays, focus handling, and ARIA attributes. The useHoverCard hook is found at packages/core/src/HoverCard/useHoverCard.tsx and provides similar functionality but without portal reparenting, making it suitable for interactive content.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →