익스클루시브

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를 제거한 절대 canonical URL입니다(keep-params로 해제 가능). 네이티브 시트를 닫으면 아무 일도 일어나지 않고, 시트가 차단되면 메뉴로 대체되며, 취소 가능한 mv-share 이벤트와 mv-shared 이벤트로 앱이 공유 횟수를 직접 집계할 수 있습니다.

카테고리마이크로 인터랙션
유형Web Component (<mv-share-button>)
상태안정
Keywordsexclusive, 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&amp;utm_medium=email&amp;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&amp;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
urlstringURL to share, absolute or relative (resolved against the page). Default: the page’s <link rel="canonical">, else the current URL.
share-titlestringTitle 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).
textstringOptional message sent with the link (native sheet, email body, X, WhatsApp, Bluesky, Telegram, Threads); the title is used when it is missing.
networksstringSpace 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-paramsbooleanShare 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.
menubooleanAlways open the menu, even where the native sheet exists; the native sheet is then its last item (“More options”).
data-variantghost | iconghost: no border or background (toolbars). icon: round icon button, the label stays as its accessible name.
data-openbooleanSet 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).
stringsobjectTranslatable 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

NameDescription
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

NameDescription
mv-shareCancelable, 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-sharedThe 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

NameDescription
(text)Visible label of the default button (default “Share”).
buttonOr your own child <button> (e.g. .mv-button): the share icon is added and the styling stays yours.

CSS classes

NameDescription
mv-share-button-trigger / -label / -iconDefault button, its label and the icons.
mv-share-button-menu / -url / -sep / -item / -text / -monoThe 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

NameDefaultDescription
--mv-share-button-heightvar(--mv-control-md)Button height (and icon button size).
--mv-share-button-menu-bgvar(--mv-surface-raised)Menu background.
--mv-share-button-highlightcolor-mix(in oklab, var(--mv-fg) 8%, transparent)Hovered or focused item background.
--mv-share-button-successvar(--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.

이 페이지는 AI로 번역되었습니다. 번역 문제 신고