Voice UI Kit

Highlight Overlay

Imperative overlay to highlight any DOM element via id or ref.

Overview

The HighlightOverlay renders a temporary, animated ring on top of a target element. You can target by providing a highlightedElement id (string) and/or a map of element refs.

It automatically positions and tracks layout changes, supports optional auto-clear, and can be styled via className.

This component is designed to help AI agents guide the user's attention to specific elements on a page (i.e. via a tool call).

Usage

Basic

import { Button, Card, HighlightOverlay } from "@pipecat-ai/voice-ui-kit";

function Demo() {
  const [highlightedElement, setHighlightedElement] = React.useState<string | null>(null);
  const card1Ref = React.useRef<HTMLDivElement>(null);
  const card2Ref = React.useRef<HTMLDivElement>(null);

  const elementRefs = {
    card1: card1Ref.current,
    card2: card2Ref.current,
  };

  return (
    <div className="flex flex-col gap-4 items-center">
      <ButtonGroup>
        <Button variant="outline" onClick={() => setHighlightedElement("card1")}>Highlight Card 1</Button>
        <Button variant="outline" onClick={() => setHighlightedElement("card2")}>Highlight Card 2</Button>
        <Button variant="outline" onClick={() => setHighlightedElement(null)}>Clear Highlight</Button>
      </ButtonGroup>

      <div className="grid grid-cols-2 gap-6 my-4">
        <Card ref={card1Ref} className="p-6 w-64 flex items-center justify-center">
          <CardHeader>
            <CardTitle className="font-semibold">Card 1</CardTitle>
          </CardHeader>
        </Card>

        <Card ref={card2Ref} className="p-6 w-64 flex items-center justify-center">
          <CardHeader>
            <CardTitle className="font-semibold">Card 2</CardTitle>
          </CardHeader>
        </Card>
      </div>

      <HighlightOverlay
        highlightedElement={highlightedElement}
        elementRefs={elementRefs}
        onHighlightElement={setHighlightedElement}
        className="ring-2 ring-blue-500 ring-offset-2"
        offset={8}
        autoClearDuration={4000}
      />
    </div>
  );
}

render(<Demo />)

Custom styling

import { Button, Card, HighlightOverlay } from "@pipecat-ai/voice-ui-kit";

function Demo() {
  const [highlightedElement, setHighlightedElement] = React.useState<string | null>(null);

  return (
    <div className="flex flex-col gap-4 items-center">
      <ButtonGroup>
        <Button variant="outline" onClick={() => setHighlightedElement("card1")}>Highlight Card 1</Button>
        <Button variant="outline" onClick={() => setHighlightedElement("card2")}>Highlight Card 2</Button>
        <Button variant="outline" onClick={() => setHighlightedElement(null)}>Clear Highlight</Button>
      </ButtonGroup>

      <div className="grid grid-cols-2 gap-6 my-4">
        <Card id="card1" className="p-6 w-64 flex items-center justify-center">
          <CardHeader>
            <CardTitle className="font-semibold">Card 1</CardTitle>
          </CardHeader>
        </Card>

        <Card id="card2" className="p-6 w-64 flex items-center justify-center">
          <CardHeader>
            <CardTitle className="font-semibold">Card 2</CardTitle>
          </CardHeader>
        </Card>
      </div>

      <HighlightOverlay
        highlightedElement={highlightedElement}
        onHighlightElement={setHighlightedElement}
        className={highlightedElement === "card1" ? "ring-4 ring-green-500" : "ring-4 ring-red-500"}
        offset={12}
        autoClearDuration={3000}
      />
    </div>
  );
}

render(<Demo />)

No auto-clear

import { Button, Card, HighlightOverlay } from "@pipecat-ai/voice-ui-kit";

function Demo() {
  const [highlightedElement, setHighlightedElement] = React.useState<string | null>(null);

  return (
    <div className="flex flex-col gap-4 items-center">
      <ButtonGroup>
        <Button variant="outline" onClick={() => setHighlightedElement("card3")}>Highlight Card 1</Button>
        <Button variant="outline" onClick={() => setHighlightedElement("card4")}>Highlight Card 2</Button>
        <Button variant="outline" onClick={() => setHighlightedElement(null)}>Clear Highlight</Button>
      </ButtonGroup>

      <div className="grid grid-cols-2 gap-6 my-4">
        <Card id="card3" className="p-6 w-64 flex items-center justify-center">
          <CardHeader>
            <CardTitle className="font-semibold">Card 1</CardTitle>
          </CardHeader>
        </Card>

        <Card id="card4" className="p-6 w-64 flex items-center justify-center">
          <CardHeader>
            <CardTitle className="font-semibold">Card 2</CardTitle>
          </CardHeader>
        </Card>
      </div>

      <HighlightOverlay
        highlightedElement={highlightedElement}
        onHighlightElement={setHighlightedElement}
        className="ring-2 ring-purple-500 ring-offset-4"
        offset={6}
        autoClearDuration={0}
      />
    </div>
  );
}

render(<Demo />)

Props

PropTypeDefault
highlightedElement?
string | null
null
elementRefs?
Record<string, HTMLElement | null>
{}
onHighlightElement?
(elementId: string | null) => void
undefined
className?
string
"ring-4"
offset?
number
4
autoClearDuration?
number
3000

CSS variables

  • --animate-highlight: controls the highlight animation shorthand. Default is highlight-focus 1.2s ease-in-out forwards.
  • --highlight-final-opacity: controls the final opacity at the end of the animation. The component sets this automatically based on autoClearDuration (0 → 1, otherwise → 0), but you can override it.

Override globally (scoped styles use .voice-ui-kit):

.voice-ui-kit {
  --animate-highlight: highlight-focus 900ms ease-out forwards;
}

Per-instance override:

<HighlightOverlay
  highlightedElement="card1"
  style={{ "--highlight-final-opacity": "0.6" }}
/>