UI Components Easy
Smart Share Button
A high-utility share button that uses the Web Share API when available, with a tailored fallback menu for desktop browsers.
Open in Lab
MCP
vanilla-js css react tailwind vue svelte
Targets: TS JS HTML React Vue Svelte
Code
body {
margin: 0;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
:root {
--share-bg: rgba(15, 23, 42, 0.95);
--share-border: rgba(255, 255, 255, 0.1);
--share-text: #f8fafc;
--share-muted: #94a3b8;
--share-accent: #8b5cf6;
--share-accent-glow: rgba(139, 92, 246, 0.3);
--share-opt-hover: rgba(255, 255, 255, 0.06);
}
.share-widget {
position: relative;
display: inline-block;
font-family: "Inter", system-ui, sans-serif;
}
.share-btn {
display: flex;
align-items: center;
gap: 0.5rem;
background: linear-gradient(135deg, var(--share-accent), #a78bfa);
color: white;
border: none;
padding: 0.7rem 1.375rem;
border-radius: 12px;
font-weight: 700;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 0 24px var(--share-accent-glow);
}
.share-btn:hover {
opacity: 0.9;
transform: translateY(-1px);
box-shadow: 0 6px 28px var(--share-accent-glow);
}
.share-menu {
position: absolute;
bottom: calc(100% + 12px);
left: 0;
width: 232px;
background: var(--share-bg);
border: 1px solid var(--share-border);
border-radius: 16px;
padding: 1rem;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.04);
display: none;
z-index: 100;
animation: menuIn 0.25s cubic-bezier(0.16, 1, 0.3, 1);
backdrop-filter: blur(20px);
}
.share-menu.active {
display: block;
}
.menu-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.menu-header h4 {
margin: 0;
font-size: 0.75rem;
color: var(--share-muted);
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 600;
}
.close-btn {
background: none;
border: none;
font-size: 1rem;
cursor: pointer;
color: var(--share-muted);
line-height: 1;
padding: 2px;
border-radius: 4px;
transition: color 0.2s;
}
.close-btn:hover {
color: var(--share-text);
}
.share-options {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.share-opt {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.6rem 0.75rem;
text-decoration: none;
color: var(--share-muted);
font-size: 0.9rem;
font-weight: 500;
border-radius: 10px;
transition: all 0.15s;
background: none;
border: none;
text-align: left;
cursor: pointer;
width: 100%;
}
.share-opt:hover {
background: var(--share-opt-hover);
color: var(--share-text);
}
.share-opt.copy {
border-top: 1px solid var(--share-border);
margin-top: 0.5rem;
padding-top: 0.75rem;
}
.share-opt.copy:hover {
background: rgba(139, 92, 246, 0.1);
color: #a78bfa;
}
@keyframes menuIn {
from {
opacity: 0;
transform: translateY(8px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}const shareToggle = document.getElementById("share-toggle");
const shareMenu = document.getElementById("share-menu");
const closeShare = document.getElementById("close-share");
const copyLinkBtn = document.getElementById("copy-link");
const shareData = {
title: "Check this out!",
text: "I found this amazing component on Stealthis.",
url: window.location.href,
};
async function handleShare() {
if (navigator.share) {
try {
await navigator.share(shareData);
} catch (err) {
console.log("Error sharing:", err);
toggleMenu();
}
} else {
toggleMenu();
}
}
function toggleMenu() {
shareMenu.classList.toggle("active");
}
shareToggle.addEventListener("click", handleShare);
closeShare.addEventListener("click", toggleMenu);
// Social Links Implementation
document.querySelectorAll(".share-opt").forEach((opt) => {
const platform = opt.dataset.platform;
if (!platform) return;
opt.addEventListener("click", (e) => {
e.preventDefault();
let url = "";
const encodedUrl = encodeURIComponent(shareData.url);
const encodedText = encodeURIComponent(shareData.text);
switch (platform) {
case "twitter":
url = `https://twitter.com/intent/tweet?text=${encodedText}&url=${encodedUrl}`;
break;
case "facebook":
url = `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`;
break;
case "linkedin":
url = `https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}`;
break;
}
window.open(url, "_blank", "width=600,height=400");
toggleMenu();
});
});
copyLinkBtn.addEventListener("click", () => {
navigator.clipboard.writeText(shareData.url).then(() => {
const originalText = copyLinkBtn.textContent;
copyLinkBtn.textContent = "Link Copied!";
setTimeout(() => {
copyLinkBtn.textContent = originalText;
toggleMenu();
}, 1500);
});
});
// Close menu when clicking outside
window.addEventListener("click", (e) => {
if (!shareMenu.contains(e.target) && !shareToggle.contains(e.target)) {
shareMenu.classList.remove("active");
}
});<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Share Button</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="share-widget">
<button id="share-toggle" class="share-btn">
<span class="share-icon">📤</span>
<span>Share Result</span>
</button>
<div id="share-menu" class="share-menu">
<div class="menu-header">
<h4>Share via</h4>
<button id="close-share" class="close-btn">×</button>
</div>
<div class="share-options">
<a href="#" class="share-opt" data-platform="twitter">𝕏 (Twitter)</a>
<a href="#" class="share-opt" data-platform="facebook">Facebook</a>
<a href="#" class="share-opt" data-platform="linkedin">LinkedIn</a>
<button id="copy-link" class="share-opt copy">Copy Link</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>import { useState, useRef, useEffect } from "react";
const OPTIONS = [
{ label: "Copy link", icon: "🔗", action: "copy" },
{ label: "Twitter / X", icon: "✦", action: "twitter" },
{ label: "LinkedIn", icon: "in", action: "linkedin" },
{ label: "Facebook", icon: "f", action: "facebook" },
{ label: "WhatsApp", icon: "✉", action: "whatsapp" },
];
export default function ShareButtonRC() {
const [open, setOpen] = useState(false);
const [copied, setCopied] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
function handler(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
}
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, []);
function handleOption(action: string) {
if (action === "copy") {
navigator.clipboard.writeText(window.location.href).catch(() => {});
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
setOpen(false);
}
return (
<div className="min-h-screen bg-[#0d1117] flex items-center justify-center gap-8 p-6">
<div ref={ref} className="relative">
<button
onClick={() => setOpen((o) => !o)}
className={`flex items-center gap-2 px-5 py-2.5 rounded-xl font-semibold text-sm transition-all duration-200 ${
open
? "bg-[#58a6ff] text-[#0d1117]"
: "bg-[#21262d] border border-[#30363d] text-[#e6edf3] hover:border-[#8b949e]/40"
}`}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
>
<circle cx="18" cy="5" r="3" />
<circle cx="6" cy="12" r="3" />
<circle cx="18" cy="19" r="3" />
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49" />
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />
</svg>
Share
</button>
{open && (
<div className="absolute top-full mt-2 right-0 w-44 bg-[#161b22] border border-[#30363d] rounded-xl overflow-hidden shadow-xl z-10">
{OPTIONS.map(({ label, icon, action }) => (
<button
key={action}
onClick={() => handleOption(action)}
className="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-[#e6edf3] hover:bg-[#21262d] transition-colors"
>
<span className="w-5 h-5 flex items-center justify-center text-xs font-bold bg-[#30363d] rounded text-[#8b949e]">
{icon}
</span>
{action === "copy" && copied ? "Copied!" : label}
</button>
))}
</div>
)}
</div>
{copied && (
<div className="fixed bottom-6 left-1/2 -translate-x-1/2 bg-[#238636] text-white text-sm px-4 py-2 rounded-lg shadow-lg">
Link copied to clipboard!
</div>
)}
</div>
);
}<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const OPTIONS = [
{ label: "Copy link", icon: "🔗", action: "copy" },
{ label: "Twitter / X", icon: "✦", action: "twitter" },
{ label: "LinkedIn", icon: "in", action: "linkedin" },
{ label: "Facebook", icon: "f", action: "facebook" },
{ label: "WhatsApp", icon: "✉", action: "whatsapp" },
];
const open = ref(false);
const copied = ref(false);
const containerEl = ref(null);
function handleOption(action) {
if (action === "copy") {
navigator.clipboard.writeText(window.location.href).catch(() => {});
copied.value = true;
setTimeout(() => (copied.value = false), 2000);
}
open.value = false;
}
function handleClickOutside(e) {
if (containerEl.value && !containerEl.value.contains(e.target)) open.value = false;
}
onMounted(() => document.addEventListener("mousedown", handleClickOutside));
onUnmounted(() => document.removeEventListener("mousedown", handleClickOutside));
</script>
<template>
<div class="min-h-screen bg-[#0d1117] flex items-center justify-center gap-8 p-6">
<div ref="containerEl" class="relative">
<button @click="open = !open" :class="['flex items-center gap-2 px-5 py-2.5 rounded-xl font-semibold text-sm transition-all duration-200', open ? 'bg-[#58a6ff] text-[#0d1117]' : 'bg-[#21262d] border border-[#30363d] text-[#e6edf3] hover:border-[#8b949e]/40']">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
Share
</button>
<div v-if="open" class="absolute top-full mt-2 right-0 w-44 bg-[#161b22] border border-[#30363d] rounded-xl overflow-hidden shadow-xl z-10">
<button v-for="o in OPTIONS" :key="o.action" @click="handleOption(o.action)" class="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-[#e6edf3] hover:bg-[#21262d] transition-colors">
<span class="w-5 h-5 flex items-center justify-center text-xs font-bold bg-[#30363d] rounded text-[#8b949e]">{{ o.icon }}</span>
{{ o.action === "copy" && copied ? "Copied!" : o.label }}
</button>
</div>
</div>
<div v-if="copied" class="fixed bottom-6 left-1/2 -translate-x-1/2 bg-[#238636] text-white text-sm px-4 py-2 rounded-lg shadow-lg">Link copied to clipboard!</div>
</div>
</template><script>
import { onMount, onDestroy } from "svelte";
const OPTIONS = [
{ label: "Copy link", icon: "🔗", action: "copy" },
{ label: "Twitter / X", icon: "✦", action: "twitter" },
{ label: "LinkedIn", icon: "in", action: "linkedin" },
{ label: "Facebook", icon: "f", action: "facebook" },
{ label: "WhatsApp", icon: "✉", action: "whatsapp" },
];
let open = false;
let copied = false;
let containerEl;
function handleOption(action) {
if (action === "copy") {
navigator.clipboard.writeText(window.location.href).catch(() => {});
copied = true;
setTimeout(() => (copied = false), 2000);
}
open = false;
}
function handleClickOutside(e) {
if (containerEl && !containerEl.contains(e.target)) open = false;
}
onMount(() => document.addEventListener("mousedown", handleClickOutside));
onDestroy(() => document.removeEventListener("mousedown", handleClickOutside));
</script>
<div class="min-h-screen bg-[#0d1117] flex items-center justify-center gap-8 p-6">
<div bind:this={containerEl} class="relative">
<button on:click={() => open = !open} class="flex items-center gap-2 px-5 py-2.5 rounded-xl font-semibold text-sm transition-all duration-200 {open ? 'bg-[#58a6ff] text-[#0d1117]' : 'bg-[#21262d] border border-[#30363d] text-[#e6edf3] hover:border-[#8b949e]/40'}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
Share
</button>
{#if open}
<div class="absolute top-full mt-2 right-0 w-44 bg-[#161b22] border border-[#30363d] rounded-xl overflow-hidden shadow-xl z-10">
{#each OPTIONS as { label, icon, action }}
<button on:click={() => handleOption(action)} class="w-full flex items-center gap-3 px-4 py-2.5 text-sm text-[#e6edf3] hover:bg-[#21262d] transition-colors">
<span class="w-5 h-5 flex items-center justify-center text-xs font-bold bg-[#30363d] rounded text-[#8b949e]">{icon}</span>
{action === "copy" && copied ? "Copied!" : label}
</button>
{/each}
</div>
{/if}
</div>
{#if copied}
<div class="fixed bottom-6 left-1/2 -translate-x-1/2 bg-[#238636] text-white text-sm px-4 py-2 rounded-lg shadow-lg">Link copied to clipboard!</div>
{/if}
</div>Smart Share Button
This component adapts to the user’s device capabilities. On mobile, it triggers the native sharing sheet. On desktop, it unveils a beautiful, motion-driven menu with common sharing destinations like X, Facebook, and LinkedIn.
Features
- Native Web Share API integration
- Elegant desktop fallback menu
- Copy-to-clipboard functionality
- SVGs for social platforms
- Framer-motion like entrance animations