<!-- Protocore Design System — LinkOverlay -->
# LinkOverlay

- **Category:** Layout (`layout`)
- **Slug:** `layout/link-overlay`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { LinkOverlay, LinkBox } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/layout/link-overlay

> A stretched-link helper that makes a whole Card or Tile clickable through one keyboard-correct anchor.

## Why not wrap the card in an anchor

This is the affordance-honesty law (§7.2) made reusable. Wrapping a whole card in an `<a>` is invalid when the card contains other links or buttons, and an `onClick` on the surface is invisible to the keyboard and to assistive tech. The stretched-link pattern gives you one real, focusable, announceable anchor whose hit area is the entire surface.

`LinkOverlay` supports `asChild`, so you can hand it your framework's `Link` (Next, Remix) and keep client-side navigation. Give the surface a visible hover/focus affordance only when it is genuinely interactive — the whole point is that it now is.

## Props

### LinkOverlay

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | Merge props onto the single child instead of rendering an `<a>` (framework links). |
| `className` | `string` | no | — | — |
| `style` | `CSSProperties` | no | — | — |

### LinkBox

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `className` | `string` | no | — | — |
| `style` | `CSSProperties` | no | — | — |

## Examples

### A clickable card

Wrap the surface in `LinkBox` (which sets `position: relative`) and drop a `LinkOverlay` anywhere inside. Its `::after` covers the whole box, so the entire card is the click target — but the DOM still holds exactly one real anchor, so keyboard and screen-reader behaviour stays correct.

```tsx
import { Card, LinkBox, LinkOverlay, Heading, Text } from "@protocore/pds";

export default function LinkOverlayBasics() {
  return (
    <LinkBox style={{ maxWidth: 340 }}>
      <Card data-interactive="true" style={{ cursor: "pointer" }}>
        <Heading as="h3" size="h3">
          <LinkOverlay href="#trasor">Trasor</LinkOverlay>
        </Heading>
        <Text size="sm" color="muted" style={{ marginTop: 6 }}>
          Static site tenant · 4 environments · last deploy 3m ago. The whole card links through
          one anchor.
        </Text>
      </Card>
    </LinkBox>
  );
}
```

### With a secondary action

A second interactive element inside the box (a button, an outbound link) stays clickable by raising its own stacking context — `position: relative; z-index: 1`. It sits above the overlay while the rest of the card still routes to the primary link.

```tsx
import { Card, LinkBox, LinkOverlay, Link, Heading, Text } from "@protocore/pds";

export default function LinkOverlayNested() {
  return (
    <LinkBox style={{ maxWidth: 340 }}>
      <Card data-interactive="true" style={{ cursor: "pointer" }}>
        <Heading as="h3" size="h3">
          <LinkOverlay href="#roxana">Roxana</LinkOverlay>
        </Heading>
        <Text size="sm" color="muted" style={{ marginTop: 6 }}>
          The card links to the tenant, while the live-site link below stays independently
          clickable.
        </Text>
        <div style={{ position: "relative", zIndex: 1, marginTop: 12 }}>
          <Link href="https://roxana.protocore.io" external>
            roxana.protocore.io
          </Link>
        </div>
      </Card>
    </LinkBox>
  );
}
```

## Usage

**Do**

- Wrap the surface in `LinkBox` (or set `position: relative` yourself).
- Keep exactly one primary `LinkOverlay` per box.
- Raise secondary actions with `position: relative; z-index: 1`.
- Use `asChild` to pass a framework `Link` for client navigation.

**Don't**

- Wrap the whole card in an `<a>` — invalid with nested interactives.
- Put two overlays in one box; the hit areas will fight.
- Add an `onClick` to the surface instead; that skips the keyboard.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to the single overlay anchor |
| `Enter` | Activates the link |

**Notes**

- Exactly one anchor is in the accessibility tree, so screen readers announce one link for the whole surface.
- The focus ring is moved onto the stretched `::after`, so keyboard focus outlines the entire surface rather than a zero-width inline anchor.

## Related

`card`, `product-card`, `listing-row`, `link`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
