packages/codemod/docs/LINK_MIGRATION.md
This guide covers the migration of Chakra UI v2 Link component to the v3 API.
In v3, the Link component's isExternal prop has been removed in favor of
standard HTML attributes. The codemod automatically transforms isExternal to
the appropriate target and rel attributes.
isExternal prop removed: The convenience prop is replaced with explicit
target="_blank" and rel="noopener noreferrer" attributes for better
standards compliance and clarity.
When isExternal is present:
isExternal prop is removedtarget="_blank" is added (if not already present)rel="noopener noreferrer" is added (if not already present)target and rel values
if neededrel="noopener noreferrer" is always added for security
best practicesv2:
import { Link } from "@chakra-ui/react"
;<Link href="https://chakra-ui.com" isExternal>
Chakra UI
</Link>
v3:
import { Link } from "@chakra-ui/react"
;<Link href="https://chakra-ui.com" target="_blank" rel="noopener noreferrer">
Chakra UI
</Link>
v2:
;<Link href="https://chakra-ui.com" isExternal={true}>
Chakra UI
</Link>
v3:
;<Link href="https://chakra-ui.com" target="_blank" rel="noopener noreferrer">
Chakra UI
</Link>
If you already have a target attribute, it's preserved:
v2:
;<Link href="https://chakra-ui.com" isExternal target="_self">
Chakra UI
</Link>
v3:
;<Link href="https://chakra-ui.com" target="_self" rel="noopener noreferrer">
Chakra UI
</Link>
If you already have a rel attribute, it's preserved:
v2:
;<Link href="https://chakra-ui.com" isExternal rel="nofollow">
Chakra UI
</Link>
v3:
;<Link href="https://chakra-ui.com" rel="nofollow" target="_blank">
Chakra UI
</Link>
v2:
import { Link, Stack } from "@chakra-ui/react"
function Links() {
return (
<Stack>
<Link href="https://chakra-ui.com" isExternal>
Chakra UI
</Link>
<Link href="https://github.com" isExternal>
GitHub
</Link>
<Link href="/docs">Documentation</Link>
</Stack>
)
}
v3:
import { Link, Stack } from "@chakra-ui/react"
function Links() {
return (
<Stack>
<Link
href="https://chakra-ui.com"
target="_blank"
rel="noopener noreferrer"
>
Chakra UI
</Link>
<Link href="https://github.com" target="_blank" rel="noopener noreferrer">
GitHub
</Link>
<Link href="/docs">Documentation</Link>
</Stack>
)
}
The transformation also applies to LinkOverlay:
v2:
import { LinkBox, LinkOverlay } from "@chakra-ui/react"
;<LinkBox>
<LinkOverlay href="https://chakra-ui.com" isExternal>
Chakra UI
</LinkOverlay>
</LinkBox>
v3:
import { LinkBox, LinkOverlay } from "@chakra-ui/react"
;<LinkBox>
<LinkOverlay
href="https://chakra-ui.com"
target="_blank"
rel="noopener noreferrer"
>
Chakra UI
</LinkOverlay>
</LinkBox>
import { Box, Link, Stack, Text } from "@chakra-ui/react"
function Footer() {
return (
<Box as="footer" py={8}>
<Stack spacing={4}>
<Text>External Links:</Text>
<Stack direction="row" spacing={4}>
<Link href="https://chakra-ui.com" isExternal color="blue.500">
Chakra UI
</Link>
<Link href="https://github.com/chakra-ui" isExternal color="blue.500">
GitHub
</Link>
<Link
href="https://twitter.com/chakra_ui"
isExternal
color="blue.500"
>
Twitter
</Link>
</Stack>
<Text>Internal Links:</Text>
<Stack direction="row" spacing={4}>
<Link href="/about">About</Link>
<Link href="/docs">Docs</Link>
<Link href="/blog">Blog</Link>
</Stack>
</Stack>
</Box>
)
}
import { Box, Link, Stack, Text } from "@chakra-ui/react"
function Footer() {
return (
<Box as="footer" py={8}>
<Stack spacing={4}>
<Text>External Links:</Text>
<Stack direction="row" spacing={4}>
<Link
href="https://chakra-ui.com"
target="_blank"
rel="noopener noreferrer"
color="blue.500"
>
Chakra UI
</Link>
<Link
href="https://github.com/chakra-ui"
target="_blank"
rel="noopener noreferrer"
color="blue.500"
>
GitHub
</Link>
<Link
href="https://twitter.com/chakra_ui"
target="_blank"
rel="noopener noreferrer"
color="blue.500"
>
Twitter
</Link>
</Stack>
<Text>Internal Links:</Text>
<Stack direction="row" spacing={4}>
<Link href="/about">About</Link>
<Link href="/docs">Docs</Link>
<Link href="/blog">Blog</Link>
</Stack>
</Stack>
</Box>
)
}
All other Link props are preserved unchanged:
| Prop | v2 | v3 | Notes |
|---|---|---|---|
href | ✅ | ✅ | Unchanged |
color | ✅ | ✅ | Unchanged |
fontSize | ✅ | ✅ | Unchanged |
fontWeight | ✅ | ✅ | Unchanged |
textDecoration | ✅ | ✅ | Unchanged |
isExternal | ✅ | ❌ | → target + rel |
target | ✅ | ✅ | Preserved if already present |
rel | ✅ | ✅ | Preserved if already present |
| All other props | ✅ | ✅ | Unchanged |
npx @chakra-ui/codemod@latest --transform link src/**/*.tsx
After running the codemod, review:
Custom target values: If you had isExternal with a custom target
value, ensure it's still correct:
// Before: Opens in same window despite isExternal
<Link href="..." isExternal target="_self">
// After: target="_self" is preserved
<Link href="..." target="_self" rel="noopener noreferrer">
Custom rel values: If you need additional rel values beyond
noopener noreferrer, add them:
// Add additional rel values if needed
<Link
href="https://example.com"
target="_blank"
rel="noopener noreferrer nofollow"
>
Example
</Link>
Conditional isExternal: If isExternal was conditional, you may need to
refactor:
// v2: Conditional external link
<Link href={url} isExternal={isExternalUrl}>
{text}
</Link>
// v3: Consider a helper or conditional attributes
<Link
href={url}
{...(isExternalUrl && {
target: "_blank",
rel: "noopener noreferrer",
})}
>
{text}
</Link>
Link analytics: If you track external link clicks, update your tracking
to use target="_blank" instead of isExternal:
// Update analytics/tracking code
const handleClick = (e) => {
if (e.currentTarget.target === "_blank") {
trackExternalLinkClick(e.currentTarget.href)
}
}
The rel="noopener noreferrer" attribute is important for security when opening
links in new tabs. It prevents the new page from accessing the window.opener
object and protects against tabnabbing attacks.
noopener: Prevents the new page from accessing window.openernoreferrer: Prevents the browser from sending the referrer headerThe codemod automatically adds these for all isExternal links. If you
customize the rel attribute, ensure you include these values for external
links.
Problem: External links don't open in new tabs after migration.
Solution: Verify target="_blank" was added:
// ✅ Correct - will open in new tab
<Link href="..." target="_blank" rel="noopener noreferrer">
// ❌ Wrong - missing target
<Link href="..." rel="noopener noreferrer">
Problem: TypeScript complains about target or rel props.
Solution: Ensure you're using the latest @chakra-ui/react types. These are
standard HTML attributes and should be supported.
Problem: Using Chakra Link with Next.js Link wrapper.
Solution: Next.js Link handles the <a> tag, so apply attributes to Chakra
Link:
import NextLink from "next/link"
import { Link as ChakraLink } from "@chakra-ui/react"
// Next.js 13+ with App Router
<ChakraLink href="..." target="_blank" rel="noopener noreferrer">
External Link
</ChakraLink>
// Or with explicit NextLink wrapper
<NextLink href="..." passHref legacyBehavior>
<ChakraLink target="_blank" rel="noopener noreferrer">
External Link
</ChakraLink>
</NextLink>