独家
Share Button <mv-share-button>
分享按钮:在 Web Share API 能分享该数据的环境中(通过 navigator.canShare 检查)一次轻触即打开原生分享面板,其他情况下则显示一个可键盘操作的小菜单,包含 Copy link、Email 以及你列出的社交网络,均以普通 intent 链接构建。
工作原理
分享按钮:在 Web Share API 能分享该数据的环境中(通过 navigator.canShare 检查)一次轻触即打开原生分享面板,其他情况下则显示一个可键盘操作的小菜单,包含 Copy link、Email 以及你列出的社交网络,均以普通 intent 链接构建。其他方案忽略的地方:在用户选择目标之前,没有任何第三方请求或追踪(没有脚本、像素、iframe 或品牌图标,链接以 noopener noreferrer 打开);分享的 URL 是绝对的规范地址,并去除 utm_*、fbclid、gclid 等类似的点击 id(keep-params 可关闭此行为);关闭原生面板时静默不做任何事,面板被阻止时回退到菜单;可取消的 mv-share 事件加上 mv-shared 事件,让应用可以自行统计分享次数。
| 分类 | 微交互 |
|---|---|
| 类型 | Web Component(<mv-share-button>) |
| 状态 | 稳定版 |
| Keywords | exclusive, light, share, web-share, social, copy-link, privacy, no-tracking, canonical, menu, invite |
When to use
- An article, recipe or product page needs a share button that works on phones and desktops alike
- An invite or referral link must be easy to send by any app, by email or as a copied link
- Shares should be counted by the app itself without loading third-party share widgets
- Links reached from newsletters or ads must be shared without their campaign parameters
Avoid when
- Only a copy-to-clipboard action is needed, with no other share targets → use Copy Button instead
- The trigger opens a list of general actions rather than share targets → use Menu instead
- The page must show live share counts or embedded posts from social networks
安装
node scripts/add.mjs share-button --out ./src/marvelous使用 Marvelous UI MCP 服务器的 AI 智能体:install_components({ slugs: ["share-button"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。
复制的文件(含依赖):tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/focus.js, core/position.js, components/share-button/share-button.js, components/share-button/share-button.css。
用法
快速开始,最小可运行标记:
<mv-share-button></mv-share-button>标准标记,可在此基础上通过属性、data-* 和 CSS 变量进行定制:
<div id="mv-share-demo" style="display:grid;gap:1rem;width:100%;max-width:560px">
<article style="padding:1.1rem 1.2rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface);box-shadow:var(--mv-shadow-sm)">
<p style="margin:0 0 .3rem;font-size:.75rem;color:var(--mv-fg-subtle)">Journal · September 24, 2026 · 6 min read</p>
<h3 style="margin:0;font-size:1.15rem;line-height:1.3">Autumn in the Higashiyama hills</h3>
<p style="margin:.4rem 0 1rem;font-size:.88rem;color:var(--mv-fg-muted)">Temple gardens at first light, a tea lesson in Gion with Aiko Tanaka, and the night train to Kanazawa.</p>
<div style="display:flex;align-items:center;justify-content:space-between;gap:.75rem;flex-wrap:wrap">
<span style="font-size:.8rem;color:var(--mv-fg-subtle)">By Léa Moreau</span>
<mv-share-button id="mv-share-demo-article" menu
url="https://lumen.studio/journal/higashiyama-autumn?utm_source=newsletter&utm_medium=email&fbclid=IwAR2xQ"
share-title="Autumn in the Higashiyama hills"
text="Temple gardens, a tea lesson in Gion and the night train to Kanazawa."
networks="x linkedin whatsapp bluesky"></mv-share-button>
</div>
</article>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:.6rem .9rem;padding:.75rem .9rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface)">
<span aria-hidden="true" style="flex:none;width:3rem;height:3rem;border-radius:var(--mv-radius-md);background:linear-gradient(135deg,var(--mv-accent-subtle),var(--mv-bg-muted))"></span>
<div style="flex:1 1 9rem;min-width:0">
<strong style="display:block;font-size:.9rem">Kanazawa lacquer tray</strong>
<span style="font-size:.8rem;color:var(--mv-fg-muted)">$68.00 · Ships in 2 days</span>
</div>
<div style="display:flex;align-items:center;gap:.9rem;margin-inline-start:auto">
<mv-share-button data-variant="icon" url="https://lumen.studio/shop/lacquer-tray?ref=home&gclid=Cj0KCQ" share-title="Kanazawa lacquer tray" networks="facebook reddit"></mv-share-button>
<mv-share-button data-variant="ghost" menu url="https://lumen.studio/invite/7QX2" share-title="Join Lumen Studio" networks="whatsapp telegram">Invite a friend</mv-share-button>
</div>
</div>
<p id="mv-share-demo-log" style="margin:0;font-size:.78rem;color:var(--mv-fg-subtle)">Shares counted by the app: 0</p>
</div>
<script type="module">
const root = document.getElementById("mv-share-demo");
const log = document.getElementById("mv-share-demo-log");
let count = 0;
root.addEventListener("mv-shared", (e) => {
count += 1;
log.textContent = `Shares counted by the app: ${count} (last: ${e.detail.target})`;
});
</script>API
Attributes
| Name | 类型 | Description |
|---|---|---|
url | string | URL to share, absolute or relative (resolved against the page). Default: the page’s <link rel="canonical">, else the current URL. |
share-title | string | Title to share (native sheet, email subject, Reddit and Hacker News title). Default: document.title. Named share-title so it never sets the global title attribute (no browser tooltip over the button). |
text | string | Optional message sent with the link (native sheet, email body, X, WhatsApp, Bluesky, Telegram, Threads); the title is used when it is missing. |
networks | string | Space or comma separated share targets added to the menu, shown in the order given. Available: x, bluesky, linkedin, facebook, reddit, whatsapp, telegram, threads, hackernews. Unknown names are ignored. Copy link and Email are always there. |
keep-params | boolean | Share the URL as is. By default, campaign and click-id parameters (utm_*, mtm_*, pk_*, hsa_*, mc_*, fbclid, gclid, gbraid, wbraid, msclkid, dclid, yclid, ttclid, twclid, igshid, li_fat_id, _ga, _gl, srsltid…) are removed; every other parameter and the #hash are kept. |
menu | boolean | Always open the menu, even where the native sheet exists; the native sheet is then its last item (“More options”). |
data-variant | ghost | icon | ghost: no border or background (toolbars). icon: round icon button, the label stays as its accessible name. |
data-open | boolean | Set by the component while the menu is open (for styling). |
Properties
| Name | 类型 | Description |
|---|---|---|
shareData | { url, title, text? } | What will be shared, with the cleaned absolute URL (read-only). |
strings | object | Translatable labels, merged with the defaults: { share: "Share", menu: "Share" (menu accessible name), copy: "Copy link", copied: "Link copied", copyFailed: "Couldn’t copy the link", email: "Email", more: "More options" }. Network names are brand names and stay as is. Can be set before the element is defined. |
Methods
| Name | Description |
|---|---|
share() | Opens the native sheet (or the menu where it can’t be used). Promise<boolean>: true when the native sheet reported a share. |
cleanUrl(href, { keepParams, base }?) | Named export: pure, server-safe URL cleaner used by the element (pass base on the server). |
Events
| Name | Description |
|---|---|
mv-share | Cancelable, fired when a target is chosen and before anything happens: detail = { target, url, title, text? }, target = native | copy | email | x | linkedin | …. preventDefault() stops the action (e.g. to run your own flow). |
mv-shared | The share went through: the native sheet resolved, the link was copied, or the email or network link was opened. Same detail. Not fired when the person closes the native sheet. |
Content structure
| Name | Description |
|---|---|
(text) | Visible label of the default button (default “Share”). |
button | Or your own child <button> (e.g. .mv-button): the share icon is added and the styling stays yours. |
CSS classes
| Name | Description |
|---|---|
mv-share-button-trigger / -label / -icon | Default button, its label and the icons. |
mv-share-button-menu / -url / -sep / -item / -text / -mono | The popover menu, the cleaned URL preview (aria-hidden), its separator, the items and the neutral network monograms. Items carry data-target and, after a copy, data-state="copied | error". |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-share-button-height | var(--mv-control-md) | Button height (and icon button size). |
--mv-share-button-menu-bg | var(--mv-surface-raised) | Menu background. |
--mv-share-button-highlight | color-mix(in oklab, var(--mv-fg) 8%, transparent) | Hovered or focused item background. |
--mv-share-button-success | var(--mv-success) | Color of the “Link copied” confirmation. |
Accessibility
A real <button> labelled “Share” (visually hidden, never removed, in the icon variant). Where it opens the native sheet it is a plain button; where it opens the menu it gets aria-haspopup="menu" and aria-expanded and is the popover invoker, so the platform handles light dismiss and Escape and returns focus to it. The menu is a top-layer popover with role="menu": opening focuses the first item (ArrowDown or ArrowUp on the button open it on the first or last item); ArrowUp/ArrowDown move with wrap-around, Home/End jump, typing a letter jumps to a matching item, Enter or Space activates (links included), Tab closes the menu and moves on, Escape closes it and returns focus. The pointer moves the focus too, so only one item is ever highlighted. Items are real <button>s and <a href> links (right-click, copy link address and middle-click still work), each with a visible focus ring. The URL preview, the monograms and the icons are aria-hidden, so each item reads once by its label. “Link copied” (or “Couldn’t copy the link”) is announced once by a polite role="status" region outside the menu, then the menu closes and focus returns to the button. Clipboard API with an execCommand fallback. Forced colors: CanvasText border, Highlight on the active item. Reduced motion: no scale on the menu or the check, fades are instant. Without JavaScript nothing is rendered, so no dead control is shown.