# Link

**Maturity:** experimental

A control that opens a URL — the SwiftUI Link .

## Import

```tsx
import { Link } from "@liquidify/react/link"
```

## Props

- `aria-label`: `string | undefined`. Accessible name override — required when {@link LinkCommonProps.children} is icon-only.
- `children`: `ReactNode`. The visible content and accessible-name source — SwiftUI `Link(_ title:, destination:)` (a string title) or the `Link(destination:)` label-closure form (an arbitrary node). An icon-only glass trigger (no visible text, only `leadingIcon` / `trailingIcon`) **requires** an explicit {@link LinkCommonProps. "aria-label" | aria-label} (WCAG 2.1 SC 1.1.1). children role: `label` (token parity — Link's text renders through Button; no `labelProps` seam, per ADR-0028 rule 3 / charter §2 rule 5).
- `className`: `string | undefined`. Optional additional CSS class merged into the glass surface class. The supported per-instance customisation seam: override any `--lq-*` custom property from this class's CSS — the token cascade the engine reads (ADR-0004). No raw inline style is involved, so this stays ADR-0003 clean.
- `disabled`: `boolean | undefined`. Disables the control. Shape N (action `<button>`): native `disabled` + `data-disabled`, no `aria-disabled`. Shape A (any `<a>` mode — inline, or a glass variant with `href`): `aria-disabled="true"` + `data-disabled` + hard suppression (navigation prevented, `onClick` / `onPress` suppressed, hover / press motion suppressed via `[data-disabled] { pointer-events: none }`) — the element **stays focusable** so assistive tech can perceive it (`docs/api-conventions.md` §3).
- `href`: `string | undefined`. The destination URL — SwiftUI `Link(destination:)`. Rendered as the anchor's native `href`. Absent in action mode — an action has no destination.
- `leadingIcon`: `ReactNode`. Leading glyph rendered before the label. **Glass variants only.**
- `onClick`: `((event: MouseEvent<HTMLAnchorElement>) => void) | ((event: MouseEvent<HTMLButtonElement>) => void) | undefined`. Low-level native passthrough; prefer `onPress`. Fires FIRST, before `onPress` — call `event.preventDefault()` here to suppress `onPress` and the default navigation (charter §1 rules 2–3). Low-level native passthrough; prefer `onPress`. Fires FIRST, before `onPress` — call `event.preventDefault()` here to suppress `onPress` (charter §1 rules 2–3).
- `onPress`: `(() => void) | undefined`. Semantic activation — the SwiftUI `Button(action:)` / environment `openURL` closure Link's action opens `destination` through. Payload-free, fires at most once per interaction, and only AFTER the {@link onClick } escape hatch on any surface where it fired without cancelling (charter §1 rules 1–3). Disabled emits nothing on every surface (audit §8).
- `ref`: `((instance: HTMLButtonElement | null) => void | (() => VoidOrUndefinedOnly) | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | RefObject<HTMLButtonElement | null> | ((instance: HTMLAnchorElement | null) => void | (() => VoidOrUndefinedOnly) | DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | RefObject<HTMLAnchorElement | null> | null | undefined`. Forwarded ref to the rendered element — the component's native root (or the merged child element on a component that opts into {@link SlottableProps}). Generic over `E` (charter `docs/api-conventions.md` §4 rule 1, default `HTMLElement`) so a component with a polymorphic root (Link's navigation / action mode union, Text's `as`) can instantiate the concrete element per variant instead of leaving every consumer with a widened `Ref<HTMLElement>`. A component that does not narrow simply inherits the `HTMLElement` default — source-compatible with every pre-existing non-generic `extends ControlProps`.
- `size`: `ButtonSize | undefined`; default `Button's own default (`"regular"`).`. Control size — the `small · regular · large` subset, forwarded to {@link Button}. **Glass variants only**; ignored on the `"inline"` surface, which inherits the surrounding text size. Narrows the inherited {@link SizedControlProps} `size` axis to the three sizes the glass surface ships — the truthful `ButtonSize` re-export (charter §7 rule 4, the composition-wrapper exception: Link may not mint its own `Extract`). Control size — the `small · regular · large` subset, forwarded to {@link Button}. Narrows the inherited {@link SizedControlProps} `size` axis to the three sizes the glass surface ships — the truthful `ButtonSize` re-export (charter §7 rule 4).
- `target`: `LinkTarget | undefined`; default `same-tab (no `target` attribute)`. New-tab vs same-tab navigation (web-only; SwiftUI's `openURL` has no "new window" concept). `"_blank"` opens a new browsing context, auto-adds `rel="noopener noreferrer"` (never independently settable), and appends a visually-hidden `" (opens in new tab)"` suffix to the accessible name. Absent in action mode — `target` is an anchor-only axis.
- `tint`: `LiquidTintValue | undefined`. System-accent of the control — drawn from the RESTRICTED {@link LIQUID_COLORS} palette (the thirteen Apple system colours). **Orthogonal to a component's `variant`**: it recolours the accent the surface draws from (focus ring, tinted fill, selection) without changing its variant. Resolved via `data-tint` attribute selectors in CSS (tokens-only, ADR-0003).
- `trailingIcon`: `ReactNode`. Trailing glyph rendered after the label. **Glass variants only.**
- `variant`: `LinkVariant | undefined`; default ``"inline"``. The surface — SwiftUI `buttonStyle`. `"inline"` (default) is the Text-like inline link; the glass variants render the composed {@link Button}'s surface. Orthogonal to the root element, which follows the `href` discriminant (an `<a>` to navigate, a `<button>` to act).

## SwiftUI mapping

- ``destination: URL`` → ``href`` (direct): The URL to open — the anchor's native `href`. Optional: an action-only glass trigger (e.g. ShareLink) may omit it and drive the control via `onClick` / `onPress` — Link then renders a native `<button>` instead of an `<a>`.
- ``Link(_ title:, destination:)` / `Link(_ titleKey:, destination:)`` → ``children` (string)` (direct): Title-as-text form (demo: `plain`, `bordered`, `Visit`).
- ``Link(destination:) { Label(...) }`` → ``children` (node) + `leadingIcon` / `trailingIcon`` (direct): Slot form — the label node, plus explicit glyph slots (glass variants only).
- ``.buttonStyle(_:)`` → ``variant`` (direct): `"inline" | ButtonVariant`. `"inline"` (default) = text link; the rest = glass surface.
- ``.controlSize(_:)`` → ``size`` (direct): `small | regular | large`, forwarded to Button. **Glass variants only** — the inline surface inherits the ambient text size.
- ``.disabled(_:)`` → ``disabled`` (direct): A native anchor has no `disabled`; Link marks the `<a>` `aria-disabled` and prevents its default navigation + handlers.
- ``.tint(color)`` → ``tint`` (direct): Restricted `LiquidColor` (13 system colours) — recolours the inline text / the glass capsule wash.
- `*(web-only, no SwiftUI analogue)*` → ``target`` (web-only): New-tab (`"_blank"`) vs same-tab (default).
- `*(web-only, derived — not a prop)*` → ``rel`` (web-only): Auto-computed `"noopener noreferrer"` whenever `target="_blank"`; never settable directly (security-critical, ADR-0017).
- ``Link` action semantics (environment `openURL`)` → ``onPress?: () => void`` (direct): Payload-free semantic activation — the SwiftUI action-closure analogue (charter §1 rule 1). Fires at most once per interaction, after `onClick` when it did not cancel.
- `*(web-only)*` → ``onClick?: (event) => void`` (web-only): Low-level native passthrough; prefer `onPress`. Typed to the concrete element per mode (`MouseEvent<HTMLAnchorElement>` navigation, `MouseEvent<HTMLButtonElement>` action). Fires FIRST; `event.preventDefault()` there suppresses `onPress` **and** the default navigation (charter §1 rules 2–3).
