# HoverCard

A hover-revealed floating surface that displays rich preview content when the pointer rests on an interactive trigger.

```slint
import { HoverCard } from "@glint/components/hover-card.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger-box := Rectangle {
                width: 140px;
                height: 32px;
                border-radius: Tokens.radius-md;
                background: Tokens.color-secondary;

                trigger := TouchArea { }

                Text {
                    text: "@glint-ui";
                    color: Tokens.color-foreground;
                    font-size: Tokens.typography-body-sm-size;
                    horizontal-alignment: center;
                    vertical-alignment: center;
                }
            }
        }
    }

    card := HoverCard {
        x: trigger-box.x;
        y: trigger-box.y;
        width: trigger-box.width;
        height: trigger-box.height;
        active: trigger.has-hover;
        content-width: 280px;

        VerticalLayout {
            spacing: 8px;

            Text {
                text: "Glint UI";
                color: Tokens.color-foreground;
                font-weight: 600;
                font-size: Tokens.typography-body-size;
            }

            Text {
                text: "Themable, accessible Slint component library built for desktop and embedded apps.";
                color: Tokens.color-muted-foreground;
                font-size: Tokens.typography-body-sm-size;
                wrap: word-wrap;
            }
        }
    }
}
```

## Usage

```slint
import { HoverCard } from "@glint/components/hover-card.slint";

export component AppWindow inherits Window {
    VerticalLayout {
        alignment: center;

        badge := Rectangle {
            width: 120px;
            height: 32px;
            trigger := TouchArea { }
            Text { text: "@glint-ui"; }
        }
    }

    // Give HoverCard the trigger's own geometry and bind active to hover:
    card := HoverCard {
        x: badge.x;
        y: badge.y;
        width: badge.width;
        height: badge.height;
        active: trigger.has-hover;
        content-width: 280px;

        VerticalLayout {
            // preview card content
        }
    }
}
```

A `HoverCard` is an interactive floating surface that displays rich preview information when a user hovers over a trigger element. Unlike `Tooltip` (which is for brief hints), `HoverCard` can hold complex layouts and interactive elements.

Give the `HoverCard` element the trigger’s own position and dimensions (`x`, `y`, `width`, `height`), and bind `active` to the trigger’s hover condition (`trigger.has-hover`).

## Examples

### Placement sides

`side` determines which edge of the trigger the card opens from: `bottom` (the default), `top`, `left`, or `right`.

```slint
import { HoverCard } from "@glint/components/hover-card.slint";
import { PanelSide } from "@glint/components/panel.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 360px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        spacing: 24px;
        padding: 32px;

        HorizontalLayout {
            alignment: center;
            spacing: 24px;

            top-box := Rectangle {
                width: 100px;
                height: 36px;
                border-radius: Tokens.radius-md;
                background: Tokens.color-secondary;
                top-area := TouchArea { }
                Text { text: "Top side"; color: Tokens.color-foreground; }
            }

            bottom-box := Rectangle {
                width: 100px;
                height: 36px;
                border-radius: Tokens.radius-md;
                background: Tokens.color-secondary;
                bottom-area := TouchArea { }
                Text { text: "Bottom side"; color: Tokens.color-foreground; }
            }
        }
    }

    top-card := HoverCard {
        x: top-box.x;
        y: top-box.y;
        width: top-box.width;
        height: top-box.height;
        active: top-area.has-hover;
        side: PanelSide.top;
        content-width: 220px;

        Text {
            text: "Opens above the trigger with PanelSide.top.";
            color: Tokens.color-foreground;
            wrap: word-wrap;
            font-size: Tokens.typography-body-sm-size;
        }
    }

    bottom-card := HoverCard {
        x: bottom-box.x;
        y: bottom-box.y;
        width: bottom-box.width;
        height: bottom-box.height;
        active: bottom-area.has-hover;
        side: PanelSide.bottom;
        content-width: 220px;

        Text {
            text: "Opens below the trigger with PanelSide.bottom.";
            color: Tokens.color-foreground;
            wrap: word-wrap;
            font-size: Tokens.typography-body-sm-size;
        }
    }
}
```

### Alignment

`align` specifies how the card aligns along the cross-axis of its placement side: `start` (default), `center`, or `end`.

```slint
import { HoverCard } from "@glint/components/hover-card.slint";
import { PanelAlign } from "@glint/components/panel.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger-box := Rectangle {
                width: 160px;
                height: 36px;
                border-radius: Tokens.radius-md;
                background: Tokens.color-secondary;
                trigger := TouchArea { }
                Text { text: "Centered preview"; color: Tokens.color-foreground; }
            }
        }
    }

    card := HoverCard {
        x: trigger-box.x;
        y: trigger-box.y;
        width: trigger-box.width;
        height: trigger-box.height;
        active: trigger.has-hover;
        align: PanelAlign.center;
        content-width: 260px;

        Text {
            text: "Aligned to center with PanelAlign.center.";
            color: Tokens.color-foreground;
            wrap: word-wrap;
            font-size: Tokens.typography-body-sm-size;
        }
    }
}
```

### Hover delays

`open-delay` (default 300ms) prevents accidental opens during quick pointer passes. `close-delay` (default 300ms) provides ample time for the pointer to move across the gap from the trigger onto the hover card surface.

```slint
import { HoverCard } from "@glint/components/hover-card.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 560px;
    height: 320px;
    background: Tokens.color-background;

    VerticalLayout {
        alignment: center;
        padding: 24px;

        HorizontalLayout {
            alignment: center;

            trigger-box := Rectangle {
                width: 160px;
                height: 36px;
                border-radius: Tokens.radius-md;
                background: Tokens.color-secondary;
                trigger := TouchArea { }
                Text { text: "Long close delay"; color: Tokens.color-foreground; }
            }
        }
    }

    card := HoverCard {
        x: trigger-box.x;
        y: trigger-box.y;
        width: trigger-box.width;
        height: trigger-box.height;
        active: trigger.has-hover;
        open-delay: 200ms;
        close-delay: 500ms;
        content-width: 240px;

        Text {
            text: "Generous 500ms close delay allows comfortable pointer movement.";
            color: Tokens.color-foreground;
            wrap: word-wrap;
            font-size: Tokens.typography-body-sm-size;
        }
    }
}
```

## API Reference

### Properties

| Property        | Type            | Default            | Description                                                                                                                                                                                                                                                                          |
| --------------- | --------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `active`        | `in bool`       | no default         | The hover state that reveals the card — a `TouchArea.has-hover`, a focus flag, any condition the consumer computes. Glint never owns the trigger (ADR-0006).                                                                                                                         |
| `open-delay`    | `in duration`   | `300ms`            | How long `active` must stay true before the card opens, so incidental hovers never flash it (Radix `openDelay`, which is 700ms — Glint opens sooner because 700ms reads as the card being broken rather than deliberate, and 300ms already outlasts a pointer crossing the trigger). |
| `close-delay`   | `in duration`   | `300ms`            | Grace period after `active` drops before the card closes — long enough for the pointer to travel from the trigger onto the card (Radix `closeDelay`).                                                                                                                                |
| `content-width` | `in length`     | `280px`            | Width of the surface; its height follows the content.                                                                                                                                                                                                                                |
| `side`          | `in PanelSide`  | `PanelSide.bottom` | Which edge of the trigger the card opens from — `bottom` by default, which is where it has always opened. The vocabulary is the Panel's (`PanelSide.top`, …) because the placement is: only the Panel can measure the surface an upward card has to sit on top of.                   |
| `align`         | `in PanelAlign` | `PanelAlign.start` | How the card lines up along the axis `side` leaves free. `start` is the trigger's leading edge, again the placement that was there before.                                                                                                                                           |
| `is-open`       | `out bool`      | no default         | True while the card is on screen; mirrors `PopupWindow.is-open`.                                                                                                                                                                                                                     |

### Enums

| Enum         | Values                           |
| ------------ | -------------------------------- |
| `PanelSide`  | `top`, `right`, `bottom`, `left` |
| `PanelAlign` | `start`, `center`, `end`         |

## Accessibility

- **Hover zone mechanics.** The hover zone seamlessly unions the trigger’s bounding box, the gap, and the card surface so the pointer does not lose hover when travelling into the card.
- **Pointer passthrough.** While open, clicking the trigger dismisses the card and allows subsequent pointer clicks to reach the trigger control.
- **Dismissable layer.** Pressing `Escape` closes an open hover card and restores focus to whatever control held it.
