Social links
A row of icon links to social profiles, each with an accessible name.
npx shadcn@latest add https://ui.goodtabs.co/r/social-links.json
With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/social-links
Five ghost icon pills; the name is the aria-label.
With labels
showLabels puts the name next to each icon.
Outline, small
The outline variant at 28px.
Usage
import { SocialLinks } from "@/components/ui/social-links"
<SocialLinks
links={[
{ name: "GitHub", href: "https://github.com/goodtabs", icon: <GitHubIcon /> },
{ name: "X", href: "https://x.com/goodtabs", icon: <XIcon /> },
]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| links | { name: string; href: string; icon: ReactNode }[] | The profiles, in order. name is the accessible name. | |
| size | "sm" | "default" | "default" | 28px or 32px tall. |
| showLabels | boolean | false | Show the name next to the icon. |
| variant | "ghost" | "outline" | "ghost" | Ghost for footers, outline to stand alone. |
Source
social-links/social-links.tsx
import * as React from "react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
export type SocialLink = {
/** The network or profile name, used as the accessible name and the visible label. */
name: string;
/** Profile url. */
href: string;
/** Any icon node, for example an inline SVG with fill currentColor. */
icon: React.ReactNode;
};
type SocialLinksProps = Omit<React.ComponentProps<"nav">, "children"> & {
/** The profiles, in order. */
links: SocialLink[];
/** 28px or 32px tall. */
size?: "sm" | "default";
/** Show the name next to the icon. Icon only otherwise, with the name as aria-label. */
showLabels?: boolean;
/** Ghost for footers and toolbars, outline to stand on its own. */
variant?: "ghost" | "outline";
};
/**
* A row of icon links to social profiles. Every link has an accessible name.
* Icons come from you, so any network works and the file stays small.
*/
export function SocialLinks({
links,
size = "default",
showLabels = false,
variant = "ghost",
className,
...props
}: SocialLinksProps) {
const buttonSize = showLabels ? (size === "sm" ? "sm" : "default") : size === "sm" ? "icon-sm" : "icon";
return (
<nav aria-label="Social links" className={cn("flex items-center", className)} {...props}>
<ul className="m-0 flex list-none items-center gap-1 p-0">
{links.map((link) => (
<li key={link.href}>
<Button asChild variant={variant} size={buttonSize} className="rounded-full">
<a
href={link.href}
target="_blank"
rel="noreferrer"
aria-label={showLabels ? undefined : link.name}
className="text-muted-foreground"
>
<span data-icon={showLabels ? "inline-start" : undefined} className="contents [&_svg]:size-4">
{link.icon}
</span>
{showLabels && <span>{link.name}</span>}
</a>
</Button>
</li>
))}
</ul>
</nav>
);
}
For AI agents
Hand your agent this page, the markdown at /components/social-links/prompt.md, or the Copy AI prompt button above.
When to use
- In a footer or an about page, under the name or logo
- On a profile or team card where two to six networks are listed
- Next to a newsletter signup as the other way to follow along
When not to use
- For share buttons, which need the current url and a different action
- For in-app navigation; these open new tabs
- With more than about six links, where a list with labels reads better
Agent notes
- It ships no icons: pass inline SVGs with fill currentColor, since lucide has no brand marks; the demos include X, GitHub, LinkedIn, YouTube and Bluesky
- Every link opens in a new tab with rel noreferrer; the name is the aria-label when labels are hidden
- Icons are muted-foreground and turn foreground on hover through the Button variant
- Server component; the nav takes any nav props, including its own aria-label