Link
Host Link provider so Page, Table, and Copyable navigate with your client-side router instead of a full reload.
Link is the host adapter for in-app navigation. Mount one <LinkProvider linkComponent={YourRouterLink}> next to I18nProvider. Kit surfaces — Page backHref, Table href, Copyable href, Descriptions kind: "link" — resolve through useResolvedLink. There is no styled f-ui <Link> chrome; the product is the provider plus an <a>-shaped contract.
Host wiring for Next.js and TanStack Router: Integrating Into Your App — Routing / Link.
When To Use
- SPA hosts (Next App Router, TanStack Router / Start) must mount
LinkProviderso Back, row links, and Copyable stay client-side. - MPA / static sites may omit the provider — native
"a"is then the correct document link. - Pass
linkComponent="a"(oruseResolvedLink("a")) on one surface when a real document link is intentional (download, external). - Do not wrap each table or Copyable in its own router
Link— one provider covers the tree. - Do not treat a missing provider as a no-op. In an SPA it is a full document reload.
Features
| Area | Behavior |
|---|---|
| Provider | LinkProvider injects one linkComponent for the tree |
| Resolve | useResolvedLink(override?): override → provider → native "a" |
| Contract | Adapter is <a>-shaped: required href: string, forwarded ref, native anchor props |
| SPA miss | Fallback "a" reloads the document (sidebar and lists remount). DEV logs a one-time warning |
| Override | Per-surface "a" or a custom component silences the warning for that call |
| Consumers | Page backHref, Table href, Copyable href, Descriptions kind: "link" |
SPA hosts must mount LinkProvider
A missing provider is a native document link, not a no-op. Clicking Page Back, a table href, or Copyable then reloads the whole app. Mount the provider next to I18nProvider. In development, f-ui warns once when it falls back to "a" without a provider or a local "a" override.
Installing
pnpm dlx shadcn@latest add https://ui.isaacfei.com/r/link.jsonnpx shadcn@latest add https://ui.isaacfei.com/r/link.jsonyarn dlx shadcn@latest add https://ui.isaacfei.com/r/link.jsonbun x shadcn@latest add https://ui.isaacfei.com/r/link.jsonWith a namespace: npx shadcn@latest add @f-ui/link.
No registryDependencies or extra npm packages.
Usage
"use client";
import { forwardRef } from "react";
import Link from "next/link";
import { LinkProvider } from "@/components/f-ui/link/link-provider";
import type { LinkComponentProps } from "@/components/f-ui/link/link-types";
const RouterLink = forwardRef<HTMLAnchorElement, LinkComponentProps>(
function RouterLink(props, ref) {
return <Link {...props} ref={ref} />;
},
);
<LinkProvider linkComponent={RouterLink}>{children}</LinkProvider>;Kit code then calls useResolvedLink() and renders <Link href={…}>. TanStack hosts map href → to — see Integrating Into Your App.
Examples
Provider and Host Adapter
Click Open order 42. The demo adapter intercepts the click (no navigation) and records the href the kit would pass your router.
last href: (none)
"use client";
import { useState } from "react";
import { LinkProvider } from "@/components/f-ui/link/link-provider";
import { useResolvedLink } from "@/components/f-ui/link/use-resolved-link";
import { DemoHostLink } from "@/demos/link/demo-host-link";
function OrderLink({ onNavigate }: { onNavigate: (href: string) => void }) {
const Link = useResolvedLink();
return (
<Link
href="/orders/42"
onClick={() => {
onNavigate("/orders/42");
}}
>
Open order 42
</Link>
);
}
/** Provider injects the host adapter; click records the href without leaving the page. */
export function LinkDemo() {
const [lastHref, setLastHref] = useState<string | null>(null);
return (
<LinkProvider linkComponent={DemoHostLink}>
<p>
<OrderLink onNavigate={setLastHref} />
</p>
<p>
{lastHref == null ? "last href: (none)" : `last href: ${lastHref}`}
</p>
</LinkProvider>
);
}Surface Override
The first link uses the provider (host adapter). The second calls useResolvedLink("a") so that href stays a native document link — downloads, external URLs, or an intentional full reload.
Orders resolved: host
Invoice PDF resolved: native a
"use client";
import { LinkProvider } from "@/components/f-ui/link/link-provider";
import { useResolvedLink } from "@/components/f-ui/link/use-resolved-link";
import { DemoHostLink } from "@/demos/link/demo-host-link";
function Probe({ override }: { override?: "a" }) {
const Link = useResolvedLink(override);
const isNative = Link === "a";
return (
<p>
<Link
href={isNative ? "https://example.com" : "/orders"}
onClick={(event) => {
event.preventDefault();
}}
>
{isNative ? "Invoice PDF" : "Orders"}
</Link>
{isNative ? " resolved: native a" : " resolved: host"}
</p>
);
}
/** Provider wins for in-app hrefs; `useResolvedLink("a")` keeps an intentional document link. */
export function LinkOverrideDemo() {
return (
<LinkProvider linkComponent={DemoHostLink}>
<Probe />
<Probe override="a" />
</LinkProvider>
);
}Composition
LinkProvider (linkComponent)
└── LinkContext
└── useResolvedLink(override?)
→ override | context | native "a"
└── Page backHref · Table href · Copyable · Descriptions linkAPI Reference
Props
LinkProvider
| Prop | Type | Default | Description |
|---|---|---|---|
linkComponent | LinkComponent | — | Required. Host router Link that accepts href: string and a forwarded anchor ref. |
children | ReactNode | — | App tree. |
Hook
useResolvedLink(override?: LinkComponent | "a") returns the component (or "a") to render. override wins, then the provider, then native "a". Pass "a" when this surface must be a document link.
Types
The adapter is native-<a> compatible except href is required string. Forward the ref to a real <a> so middle-click / open in new tab still works.
| Name | Shape |
|---|---|
LinkComponentProps | Native <a> props with required href: string |
LinkComponent | Component or forwardRef exotic that accepts LinkComponentProps |
See Also
- Integrating Into Your App — Routing / Link — Next.js and TanStack adapters
- Copyable — optional
hrefthrough this provider - Table — row and cell
href