<link>
Declares a linked resource such as a stylesheet, preload or icon that React hoists into the document head.
<link rel="stylesheet" href="https://example.com/style.css" precedence="default" />- Added in React 19.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$rel | string | The relationship to the resource. Required. React treats rel="stylesheet" differently from every other value. |
$hrefoptional | string | The URL of the linked resource. |
$precedenceoptional | string | Applies when rel="stylesheet". Ranks this node relative to other stylesheets in <head>, deciding which can override which. Values discovered first rank lower. Required for hoisting, de-duplication and Suspense. |
$mediaoptional | string | Applies when rel="stylesheet". Restricts the stylesheet to a media query. |
$titleoptional | string | Applies when rel="stylesheet". Names an alternative stylesheet. |
$disabledoptional | boolean | Disables the stylesheet. Supplying it turns off React's special stylesheet treatment. |
$onLoadoptional | function | Called when the stylesheet finishes loading. Supplying it turns off hoisting and special stylesheet treatment. |
$onErroroptional | function | Called when the stylesheet fails to load. Supplying it turns off hoisting and special stylesheet treatment. |
$asoptional | string | Applies when rel="preload" or rel="modulepreload". One of audio, document, embed, fetch, font, image, object, script, style, track, video, worker. |
$imageSrcSetoptional | string | Only when as="image". The source set of the image. |
$imageSizesoptional | string | Only when as="image". The sizes of the image. |
$sizesoptional | string | Applies when rel="icon" or rel="apple-touch-icon". The sizes of the icon. |
$crossOriginoptional | string | The CORS policy to use: "anonymous" or "use-credentials". Required when as is "fetch". |
$referrerPolicyoptional | string= "no-referrer-when-downgrade" | The Referer header to send when fetching. One of no-referrer-when-downgrade (the default), no-referrer, origin, origin-when-cross-origin, unsafe-url. |
$fetchPriorityoptional | string= "auto" | A relative fetch priority: "auto" (the default), "high" or "low". |
$hrefLangoptional | string | The language of the linked resource. |
$integrityoptional | string | A cryptographic hash of the resource, used to verify its authenticity. |
$typeoptional | string | The MIME type of the linked resource. |
$blockingoptional | string | Not recommended with React. Set to "render", it tells the browser not to render the page until the stylesheet loads; Suspense gives finer control. |
Return value
Renders nothing in place. React inserts the corresponding <link> node into the document <head> unless one of the hoisting exceptions applies.
Changelog
- PHP 19.0.0<link> rendered anywhere in the tree is hoisted into the document <head>. Before 19 it rendered in place.
- PHP 19.0.0rel="stylesheet" with a precedence prop is de-duplicated by href, and the component that renders it suspends while the stylesheet loads.
A <link> declares a resource the document needs — a stylesheet, a preload, an icon, a canonical URL — and React puts the resulting DOM node in <head> no matter how deep in the tree you wrote it. That means a route component can declare its own CSS and its own favicon without a build step editing index.html.
Stylesheets get extra machinery
With rel="stylesheet" and a precedence, three things happen: React de-duplicates by href, so N components asking for the same sheet produce one request; it orders the nodes by precedence, so overrides land after base rules; and the rendering component suspends until the sheet loads, so nothing paints unstyled. Drop precedence and you lose all three — the link is rendered in place like any other element.
What trips people up
Adding onLoad, onError or disabled to a stylesheet link opts you out entirely. React reads them as a declaration that you are managing this resource yourself, so it stops hoisting, stops de-duplicating and stops suspending. An itemProp has the same effect, since the link then describes part of the page rather than the document. Props are also frozen after the first render, so swapping href on a mounted link does nothing.
Example
import { Suspense } from "react";
function ReportStyles() {
return (
<>
{/* De-duplicated by href, ordered by precedence, and suspends
this component until the sheet has loaded. */}
<link rel="stylesheet" href="/css/report.css" precedence="default" />
<link rel="stylesheet" href="/css/report-print.css" precedence="high" media="print" />
</>
);
}
function ReportTable() {
const rows = [
{ region: "EMEA", total: 41200 },
{ region: "APAC", total: 28650 },
{ region: "AMER", total: 55310 }
];
return (
<>
<ReportStyles />
<table className="report">
<thead>
<tr><th>Region</th><th>Total</th></tr>
</thead>
<tbody>
{rows.map((row) => (
<tr key={row.region}>
<td>{row.region}</td>
<td>{row.total.toLocaleString("en-US")}</td>
</tr>
))}
</tbody>
</table>
</>
);
}
export default function ReportPage() {
return (
<main>
<link rel="icon" href="/favicon-32.png" sizes="32x32" />
<link rel="preload" as="font" type="font/woff2" href="/fonts/inter.woff2" crossOrigin="anonymous" />
<h1>Quarterly report</h1>
<Suspense fallback={<p>Loading report styles...</p>}>
<ReportTable />
</Suspense>
</main>
);
}When to use it
- Letting a lazily loaded route declare the stylesheet it needs, deduplicated across every entry point
- Preloading a web font or hero image before the browser discovers it in the markup
- Setting a favicon or apple-touch-icon from the app shell component
- Emitting a canonical or alternate-language link for the current page during a server render
Frequently asked questions
Why is my link tag not being moved into the head?
Four things disable hoisting: a rel="stylesheet" without a precedence prop, an itemProp prop, and an onLoad or onError handler. In each case React renders the link exactly where it sits in the tree. For stylesheets specifically, precedence is the switch that turns on hoisting, de-duplication and suspense together — omitting it disables all three.
How do I load CSS for one route only in React 19?
Render <link rel="stylesheet" href="..." precedence="default" /> inside the route component itself. React hoists it into <head>, de-duplicates it against any other component asking for the same href, and suspends until it loads, so the route never flashes unstyled. React may leave the node in the DOM after the route unmounts, which is harmless.
What does the precedence prop actually do?
It ranks stylesheets against each other inside <head>, which decides who wins a specificity tie. React treats precedence values it encounters first as lower and later ones as higher, and the ranking is shared across <link>, <style> and preinit. One value is enough when your rules are atomic; add a second only when a theme layer must reliably override a base layer.
More examples
De-duplication across independent components
Two sections independently declare that they need grid.css, and the browser ends up with exactly one <link href="/css/grid.css"> in <head> and one network request. React keys de-duplication on href alone, which is what makes it safe for a shared component to declare its own stylesheet: callers never have to know whether some other subtree already asked for it. Remove precedence from DataGridStyles and you get two link nodes rendered inline, one inside each <section>.
function DataGridStyles() {
return <link rel="stylesheet" href="/css/grid.css" precedence="default" />;
}
function UserGrid() {
return (
<section>
<DataGridStyles />
<h2>Users</h2>
<div className="grid">...</div>
</section>
);
}
function InvoiceGrid() {
return (
<section>
<DataGridStyles />
<h2>Invoices</h2>
<div className="grid">...</div>
</section>
);
}
export default function Dashboard() {
return (
<main>
<UserGrid />
<InvoiceGrid />
</main>
);
}Opting out to manage loading yourself
Because onLoad and onError are present, React treats the resource as yours and skips every special behaviour: the node is not moved to <head>, not de-duplicated against other links to the same href, and the component does not suspend. The paragraph reports idle, then loaded or failed. This is the escape hatch for an optional stylesheet whose failure should degrade gracefully rather than break a Suspense boundary.
import { useState } from "react";
export default function OptionalChartTheme() {
const [status, setStatus] = useState("idle");
return (
<div>
{/* onLoad/onError disable hoisting, de-duplication and suspense.
This link stays right here in the DOM. */}
<link
rel="stylesheet"
href="/css/chart-theme.css"
onLoad={() => setStatus("loaded")}
onError={() => setStatus("failed")}
/>
<p>Chart theme: {status}</p>
{status === "failed" && <p>Falling back to the default palette.</p>}
</div>
);
}Preloading what the parser will not find in time
Both links are hoisted into <head>, where the browser's preload scanner sees them before it has parsed down to the <img>. imageSrcSet and imageSizes must mirror the img exactly, or the preload fetches a different candidate and you pay for two downloads instead of one. Note the crossOrigin on the preconnect: for fonts and any as="fetch" preload, a mismatched CORS mode also produces a duplicate request.
export default function ProductHero() {
const hero = {
src: "/img/product-1200.avif",
srcSet: "/img/product-600.avif 600w, /img/product-1200.avif 1200w",
sizes: "(max-width: 700px) 100vw, 1200px"
};
return (
<section>
<link
rel="preload"
as="image"
href={hero.src}
imageSrcSet={hero.srcSet}
imageSizes={hero.sizes}
fetchPriority="high"
/>
<link rel="preconnect" href="https://cdn.example.com" crossOrigin="anonymous" />
<img src={hero.src} srcSet={hero.srcSet} sizes={hero.sizes} alt="Product" />
</section>
);
}
Discussion