# Hover Card

> A peek: hover, focus, or tap a person, status, count, or position to see the story behind it, without leaving the page.

- Name: `hover-card`
- Status: beta
- Categories: overlays
- File: the install writes `hover-card.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`. The install adds them.
- Other registry items: `@arbokui/motion-timing`. The install adds them.
- Gallery: https://arbokui.com/components/hover-card
- Registry item: https://arbokui.com/r/hover-card.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@arbokui=https://arbokui.com/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@arbokui": "https://arbokui.com/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@arbokui` in a public list that Arbokui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @arbokui/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @arbokui/hover-card
   ```

## Use it for

- Showing the story behind a person, a status, a count, or a position, like who the 21 going are.
- Put a PeekCard in content. Its title names the peek, and in a peek it shows a glance: up to 3 facts and 2 actions.
- Mark each row of a list with data-peek-row. The peek then opens beside the row, so the rows above and below stay in reach.
- Set control when the trigger is itself a control, like an event card you press to select. It keeps its role and its Enter.
- Set underline on words in a sentence, so people can see there is more behind them.
- While a menu, list, or popover is open, hover opens no peek outside it. The peek waits, and opens once that closes if the pointer is still there.

## Avoid it for

- Anything people must see to finish a task. Peeks are extra. Put the rest on the page the peek links to.
- A short name for an icon button. Use a Tooltip.
- A menu of choices, or a form. Use a Menu, a Popover, or a Dialog.
- Opening one on click. A peek opens on hover, focus, and tap. Use a Popover for a click.

## Props

- `content`: `ReactNode`. Required. What the peek shows. A PeekCard is the standard content.
- `children`: `ReactNode`. The trigger: a person, a status, a count, or a position.
- `delay`: `number`. Default `320`. How long the pointer rests on the trigger before the peek opens, in milliseconds.
- `glance`: `boolean`. Default `true`. A PeekCard inside shows up to 3 facts and 2 actions. False shows it whole.
- `width`: `number`. Default `320`. The peek's width in pixels. It never grows past the screen.
- `side`: `"auto" | "right" | "left" | "top" | "bottom"`. Default `"auto"`. auto opens beside a row marked data-peek-row, level with the row, or else below the trigger, lined up with its start. It flips when there is no room.
- `inline`: `boolean`. Default `true`. A span trigger that sits in a line of text. False makes it a block.
- `control`: `boolean`. Default `false`. The child is itself a control. It keeps its role and its Enter, and the peek follows its hover and focus.
- `underline`: `boolean`. Default `false`. A faint dotted underline under the whole trigger, on every line when it wraps.
- `label`: `string`. The peek's name for screen readers, when its content is not a PeekCard with a title.
- `open`: `boolean`. Controlled. The peek shows while true. Hover, focus, Esc, and a press outside only ask, through onOpenChange.
- `defaultOpen`: `boolean`. Default `false`. Starts open, without taking focus.
- `onOpenChange`: `(open: boolean) => void`. Hears every open and close, or what the peek asks for when controlled.
- `effect`: `"rise" | "grow"`. Default `"rise"`. How the peek opens and closes. rise fades in and moves out from the trigger. grow fades in and grows from the trigger, and closes the same way. Left out, it follows an effect-\<name> class on \<body>, where popups open, and rises with none.
- `className`: `string`. Extra classes on the trigger. Later classes win.
- `useHoverCard`: `() => { titleId; registerTitle; glance; close } | null`. Hook. Content inside a peek reads it to name the peek, to trim to a glance, and to close it.

## Accessibility

- The trigger is a focusable role="button" with aria-haspopup="dialog", aria-expanded, and aria-controls while open. With control, the child keeps its own role instead.
- The peek opens on keyboard focus at once, and on hover after the delay. A tap opens it on touch, and a second tap on the trigger closes it.
- The peek is a non-modal role="dialog". A PeekCard's title names it. Without one, label names it, or else the trigger's words.
- Enter or Space on the trigger moves focus into the peek. Tab moves through it, and past either end focus is back on the trigger. Esc closes it and puts focus back.
- ↑ and ↓ step to the peek above or below, while the keyboard is on a trigger or in its peek.
- A trigger with no words, like an icon or a swatch, needs an aria-label. The console says so until it has one.
- It opens with a quick fade from its trigger's side, and closes with a quicker fade and a slight shrink.

## Examples

### Words in a sentence

```tsx
<p>
  Design review on Tuesday:{" "}
  <HoverCard underline label="Who is going" content={<p>Sam, Kate, Josh, and 18 more.</p>}>
    21 going
  </HoverCard>
</p>
```

### Rows in a list

```tsx
const people = [
  { name: "Sam Carter", role: "Design lead" },
  { name: "Kate Clarke", role: "Support lead" },
];

<ul>
  {people.map((person) => (
    <li key={person.name} data-peek-row>
      <HoverCard label={person.name} content={<p>{person.role}</p>}>
        {person.name}
      </HoverCard>
    </li>
  ))}
</ul>
```

### Around a control

```tsx
<HoverCard control label="Thursday's review" content={<p>Starts 15:00 in the Boardroom.</p>}>
  <button type="button">Quarterly review</button>
</HoverCard>
```

### Grows from its trigger

```tsx
<p>
  Design review on Tuesday:{" "}
  <HoverCard underline effect="grow" label="Who is going" content={<p>Sam, Kate, Josh, and 18 more.</p>}>
    21 going
  </HoverCard>
</p>
```
