Social share buttons
Plain links to each network's share URL, plus the native share sheet where it exists.
<div class="share">
<p class="label" id="share-label">Share this</p>
<ul aria-labelledby="share-label">
<li><a class="net x" href="#" data-share="x" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M18.9 2H22l-6.8 7.8L23 22h-6.3l-4.9-6.4L6.2 22H3l7.3-8.3L2 2h6.4l4.4 5.8zm-1.1 18h1.7L7.3 3.7H5.5z"/></svg>
<span>X</span>
</a></li>
<li><a class="net fb" href="#" data-share="facebook" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M22 12a10 10 0 1 0-11.6 9.9v-7h-2.5V12h2.5V9.8c0-2.5 1.5-3.9 3.8-3.9 1.1 0 2.2.2 2.2.2v2.5h-1.3c-1.2 0-1.6.8-1.6 1.6V12h2.8l-.4 2.9h-2.4v7A10 10 0 0 0 22 12z"/></svg>
<span>Facebook</span>
</a></li>
<li><a class="net li" href="#" data-share="linkedin" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.98 3.5A2.5 2.5 0 1 1 5 8.5a2.5 2.5 0 0 1 0-5zM3 9h4v12H3zM10 9h3.8v1.7h.05c.53-.95 1.83-1.95 3.77-1.95 4.03 0 4.78 2.5 4.78 5.76V21h-4v-5.6c0-1.34-.02-3.06-1.9-3.06-1.9 0-2.2 1.45-2.2 2.96V21h-4z"/></svg>
<span>LinkedIn</span>
</a></li>
<li><a class="net wa" href="#" data-share="whatsapp" target="_blank" rel="noopener">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2a10 10 0 0 0-8.6 15l-1.3 4.7 4.8-1.3A10 10 0 1 0 12 2zm5.3 14.1c-.2.6-1.3 1.2-1.8 1.2-.5.1-1 .1-1.7-.1a12 12 0 0 1-5.7-5c-.4-.6-1-1.5-1-2.9 0-1.3.7-2 1-2.3.2-.2.5-.3.7-.3h.5c.2 0 .4 0 .6.5l.8 2c.1.2.1.4 0 .5l-.3.5-.3.3c-.1.1-.3.3-.1.6.2.3.7 1.2 1.5 1.9 1 .9 1.9 1.2 2.2 1.3.2.1.4.1.6-.1l.7-.9c.2-.2.4-.2.6-.1l2 .9c.2.1.4.2.4.3.1.2.1.6-.1 1.2z"/></svg>
<span>WhatsApp</span>
</a></li>
<li><a class="net mail" href="#" data-share="email">
<svg viewBox="0 0 24 24" aria-hidden="true" class="stroke"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></svg>
<span>Email</span>
</a></li>
<li><button class="net copy" type="button" id="copy">
<svg viewBox="0 0 24 24" aria-hidden="true" class="stroke"><rect x="9" y="9" width="12" height="12" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/></svg>
<span>Copy link</span>
</button></li>
<li hidden id="native-wrap"><button class="net native" type="button" id="native">
<svg viewBox="0 0 24 24" aria-hidden="true" class="stroke"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.6 13.5l6.8 4M15.4 6.5l-6.8 4"/></svg>
<span>Share</span>
</button></li>
</ul>
</div>* { box-sizing: border-box; margin: 0; }
body { font: 16px/1.6 system-ui, -apple-system, sans-serif; padding: 24px; background: #fff; }
.share { max-width: 620px; margin: 0 auto; }
.label { font-size: .85rem; font-weight: 600; color: #16202e; margin-bottom: 10px; }
.share ul { list-style: none; padding: 0; margin: 0; display: flex; gap: 8px; flex-wrap: wrap; }
.net {
display: inline-flex;
align-items: center;
gap: 8px;
font: inherit;
font-size: .87rem;
font-weight: 500;
text-decoration: none;
padding: 9px 15px;
min-height: 40px;
border: 1px solid #e2e7ef;
border-radius: 9px;
background: #fff;
color: #5b6b83;
cursor: pointer;
}
.net:hover { color: #16202e; border-color: #c9d3e2; background: #f9fbfd; }
.net:focus-visible { outline: 2px solid #3b4fe4; outline-offset: 2px; }
.net svg { width: 16px; height: 16px; fill: currentColor; flex-shrink: 0; }
.net svg.stroke { fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* brand colours only on hover, so the row is not a rainbow at rest */
.x:hover { color: #16202e; border-color: #16202e; }
.fb:hover { color: #1877f2; border-color: #1877f2; }
.li:hover { color: #0a66c2; border-color: #0a66c2; }
.wa:hover { color: #1f8a5b; border-color: #1f8a5b; }
.copy.done { color: #1f8a5b; border-color: #1f8a5b; }
@media (max-width: 420px) {
.net span { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
.net { padding: 0; width: 42px; justify-content: center; }
}const url = location.href;
const title = document.title;
const TARGETS = {
x: (u, t) => `https://twitter.com/intent/tweet?url=${u}&text=${t}`,
facebook: (u) => `https://www.facebook.com/sharer/sharer.php?u=${u}`,
linkedin: (u) => `https://www.linkedin.com/sharing/share-offsite/?url=${u}`,
whatsapp: (u, t) => `https://wa.me/?text=${t}%20${u}`,
email: (u, t) => `mailto:?subject=${t}&body=${u}`,
};
// encode once, here — building these by hand is where share links break
const u = encodeURIComponent(url);
const t = encodeURIComponent(title);
document.querySelectorAll("[data-share]").forEach((a) => {
a.href = TARGETS[a.dataset.share](u, t);
});
// copy link
const copy = document.getElementById("copy");
copy.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(url);
copy.classList.add("done");
copy.querySelector("span").textContent = "Copied";
setTimeout(() => {
copy.classList.remove("done");
copy.querySelector("span").textContent = "Copy link";
}, 1800);
} catch { /* clipboard blocked; the other buttons still work */ }
});
// the native share sheet, where the browser has one — mostly mobile
if (navigator.share) {
document.getElementById("native-wrap").hidden = false;
document.getElementById("native").addEventListener("click", () => {
navigator.share({ title, url }).catch(() => {}); // cancelling rejects; ignore it
});
}How it works
These are plain links, not widgets. Every network publishes a share URL that takes your page address as a query parameter. No SDK, no script from Facebook, no iframe — which means no third-party cookies, no tracking of your readers, and nothing added to your page weight.
The official share buttons cost between 50 and 200 KB each and report every page view back to the network whether or not anyone clicks. For a feature that typically gets clicked by well under one percent of visitors, that is a bad trade.
Encode the URL and title once. encodeURIComponent on both, then build the links from the result. Share links break most often on a title containing an ampersand or a question mark, and this is why.
navigator.share is progressive enhancement. The native sheet is far better than a row of buttons on a phone — it offers every app the person actually uses, including ones you have never heard of. It is hidden entirely on browsers without it rather than shown broken.
Brand colours on hover only. Five saturated brand colours in a row shout louder than your content. Keeping them neutral until hover keeps the section quiet while still giving recognition.
Labels collapse to icons below 420 pixels using the visually-hidden pattern, so the accessible names survive even when the words do not.
Accessibility notes
The list has aria-labelledby pointing at the "Share this" heading, so a screen reader announces the group before its six items.
Icons are aria-hidden because each control already has visible text. When that text is hidden at small sizes it is clipped rather than removed, so the accessible name is unaffected.
Links that open a new tab carry rel="noopener". It prevents the opened page from reaching back through window.opener, and modern browsers imply it — but stating it is still correct.
The copy button reports success by changing its own label, not by colour alone.
Making it yours
Add or remove networks by editing the TARGETS object and the matching list item. Reddit is https://www.reddit.com/submit?url=…&title=…; Telegram is https://t.me/share/url?url=…&text=…; Bluesky is https://bsky.app/intent/compose?text=….
Twitter's twitter.com/intent/tweet endpoint still works and still redirects, which is why it is used above rather than an x.com path.
What appears when someone shares is decided by your Open Graph tags, not by these buttons. If the preview is wrong, that is a meta tag problem.
Related templates
Google Maps embed
A responsive map with a title, lazy loading and an optional click-to-load consent gate.
HTMLCSSJavaScriptResponsive video embed
YouTube and Vimeo that stay 16:9, with a facade that saves about a megabyte per page.
HTMLCSSJavaScriptStyled audio player
A custom player over the native audio element — keyboard accessible, no library.
HTMLCSSJavaScriptMulti-column footer
Link columns, a brand block, a newsletter box and a legal line.
HTMLCSSCheck your version
Once you have edited this, the HTML validator will catch any tag you left unclosed, and the formatter will tidy the indentation. Both run in your browser.