Components · Actions
Link
Inline in text, standalone with an arrow, or external with an arrow out and a new-tab note for screen readers.
3.7% of the 2,062 sites in The UI Index have a link (77 sites).
Deploys run on every push. Read how previews work before you invite your team.
See all integrationsStatus page (opens in a new tab)Deploys run on every push. Read how previews work before you invite your team.
See all integrationsStatus page (opens in a new tab)Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ link.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ link.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS.
import type { AnchorHTMLAttributes } from 'react';
import { cn } from '@/lib/utils';
import { ARROW_PATH, EXTERNAL_PATH, linkVariants, type LinkVariants } from './link.variants';
export type LinkProps = AnchorHTMLAttributes<HTMLAnchorElement> & LinkVariants & { href: string; newTab?: boolean };
/** A link: inline in text, standalone with an arrow, or external with an arrow out. */
export function Link({ variant, href, newTab, className, children, ...props }: LinkProps) {
const icon = variant === 'standalone' ? ARROW_PATH : variant === 'external' ? EXTERNAL_PATH : null;
return (
<a href={href} target={newTab ? '_blank' : undefined} rel={newTab ? 'noopener' : undefined} className={cn(linkVariants({ variant }), className)} {...props}>
{children}
{icon && <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d={icon} /></svg>}
{newTab && <span className="sr-only"> (opens in a new tab)</span>}
</a>
);
}
export { linkVariants };import { cva, type VariantProps } from 'class-variance-authority';
/** Links. inline: in running text, underlined (the underline darkens on hover and thickens when pressed).
* standalone: on its own line, with an arrow that steps forward on hover. external: opens elsewhere, marked with
* an arrow out and "(opens in a new tab)" for screen readers when it does. */
export const linkVariants = cva(
'rounded-sm font-medium text-foreground underline-offset-4 transition-[color,text-decoration-color,background-color] active:bg-accent [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:transition-transform',
{
variants: {
variant: {
inline: 'underline decoration-foreground/30 hover:decoration-foreground active:decoration-2',
standalone: 'group inline-flex min-h-6 items-center gap-1.5 hover:underline active:decoration-2 hover:[&_svg]:translate-x-0.5',
external: 'inline-flex min-h-6 items-center gap-1 underline decoration-foreground/30 hover:decoration-foreground active:decoration-2 hover:[&_svg]:-translate-y-px hover:[&_svg]:translate-x-px',
},
},
defaultVariants: { variant: 'inline' },
},
);
export type LinkVariants = VariantProps<typeof linkVariants>;
export const ARROW_PATH = 'M3 8h10M9 4l4 4-4 4';
export const EXTERNAL_PATH = 'M5 11l6-6M6 5h5v5';---
import type { HTMLAttributes } from 'astro/types';
import { cn } from '@/lib/utils';
import { ARROW_PATH, EXTERNAL_PATH, linkVariants, type LinkVariants } from './link.variants';
type Props = HTMLAttributes<'a'> & LinkVariants & { href: string; newTab?: boolean };
const { variant, href, newTab, class: className, ...rest } = Astro.props;
const icon = variant === 'standalone' ? ARROW_PATH : variant === 'external' ? EXTERNAL_PATH : null;
---
<a href={href} target={newTab ? '_blank' : undefined} rel={newTab ? 'noopener' : undefined} class={cn(linkVariants({ variant }), className)} {...rest}><slot />{icon && <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d={icon} /></svg>}{newTab && <span class="sr-only"> (opens in a new tab)</span>}</a>import { cva, type VariantProps } from 'class-variance-authority';
/** Links. inline: in running text, underlined (the underline darkens on hover and thickens when pressed).
* standalone: on its own line, with an arrow that steps forward on hover. external: opens elsewhere, marked with
* an arrow out and "(opens in a new tab)" for screen readers when it does. */
export const linkVariants = cva(
'rounded-sm font-medium text-foreground underline-offset-4 transition-[color,text-decoration-color,background-color] active:bg-accent [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:transition-transform',
{
variants: {
variant: {
inline: 'underline decoration-foreground/30 hover:decoration-foreground active:decoration-2',
standalone: 'group inline-flex min-h-6 items-center gap-1.5 hover:underline active:decoration-2 hover:[&_svg]:translate-x-0.5',
external: 'inline-flex min-h-6 items-center gap-1 underline decoration-foreground/30 hover:decoration-foreground active:decoration-2 hover:[&_svg]:-translate-y-px hover:[&_svg]:translate-x-px',
},
},
defaultVariants: { variant: 'inline' },
},
);
export type LinkVariants = VariantProps<typeof linkVariants>;
export const ARROW_PATH = 'M3 8h10M9 4l4 4-4 4';
export const EXTERNAL_PATH = 'M5 11l6-6M6 5h5v5';<div class="grid max-w-md justify-items-start gap-5 text-sm">
<p class="text-muted-foreground">
Deploys run on every push. Read
<a href="#" class="rounded-sm font-medium text-foreground underline-offset-4 transition-[color,text-decoration-color,background-color] active:bg-accent [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:transition-transform underline decoration-foreground/30 hover:decoration-foreground active:decoration-2">
how previews work
</a>
before you invite your team.
</p>
<a href="#" class="rounded-sm font-medium text-foreground underline-offset-4 transition-[color,text-decoration-color,background-color] active:bg-accent [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:transition-transform group inline-flex min-h-6 items-center gap-1.5 hover:underline active:decoration-2 hover:[&_svg]:translate-x-0.5">
See all integrations
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8h10M9 4l4 4-4 4"></path></svg>
</a>
<a href="#" target="_blank" rel="noopener" class="rounded-sm font-medium text-foreground underline-offset-4 transition-[color,text-decoration-color,background-color] active:bg-accent [&_svg]:size-3.5 [&_svg]:shrink-0 [&_svg]:transition-transform inline-flex min-h-6 items-center gap-1 underline decoration-foreground/30 hover:decoration-foreground active:decoration-2 hover:[&_svg]:-translate-y-px hover:[&_svg]:translate-x-px">
Status page
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 11l6-6M6 5h5v5"></path></svg>
<span class="sr-only"> (opens in a new tab)</span>
</a>
</div>