@lilydesignsystem/vue-share-picker

Lily Design System Vue 3 share picker: an icon button opening the native share sheet, or a disclosure of consumer-supplied links plus copy-to-clipboard. Headless, SSR-safe, no CSS.
1,090 3
Install
npm install @lilydesignsystem/vue-share-picker
Latest Version:0.3.0
Vue:3
License:MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause
Last Updated:Oct 21, 2026
Links: GitHub  ·  npm
Maintainer: joelparkerhenderson

SharePicker (Vue helper)

A headless Vue 3 share control: a single-glyph button (➤) that opens the native share sheet where the browser has one, and otherwise shows a list of destinations you supply, plus copy the page URL.

The single source of truth is spec/index.md. This file is the human-readable guide.

Install

import SharePicker from "./lily-design-system-vue-share-picker/SharePicker.vue";
// or via the barrel:
import {
  SharePicker,
  canShareNatively,
  type ShareTarget,
} from "./lily-design-system-vue-share-picker";

Quick start

<script setup lang="ts">
import SharePicker, {
  type ShareTarget,
} from "./lily-design-system-vue-share-picker/SharePicker.vue";

const targets: ShareTarget[] = [
  {
    id: "mastodon",
    label: "Mastodon",
    href: (url, title) =>
      `https://mastodon.social/share?text=${encodeURIComponent(title)}%20${encodeURIComponent(url)}`,
  },
  {
    id: "email",
    label: "Email",
    href: (url, title) =>
      `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(url)}`,
    newTab: false,
  },
];
</script>

<template>
  <SharePicker
    label="Share this page"
    title="An article worth reading"
    :targets="targets"
    copy-label="Copy link"
    copied-label="Link copied"
    copy-failed-label="Could not copy — copy it from the address bar"
    @share="(id, url) => console.log('shared to', id, url)"
  />
</template>

url defaults to the current page, so the common case needs no wiring.

You supply the destinations

This package ships no social-network URLs. That is deliberate: which networks belong in your product is an editorial and privacy decision, the share endpoints change, and networks die. You pass targets, so the labels localise with the rest of your copy and no third-party endpoint is baked into a design system.

href is a function, so you own the whole URL and its encoding:

{ id: "linkedin", label: "LinkedIn",
  href: (url) => `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(url)}` }

Events

Event Payload Fires when
@share (targetId, url) A destination is chosen.
@copy (url) The URL was copied successfully.
@native-share (url) The native sheet was used instead of the list.

There is no v-model: this helper owns an action, not a value.

Native share sheet

With strategy="auto" (the default), pressing the button on a device with navigator.share opens the OS sheet — the user gets their real installed apps, and nothing is disclosed to a third party by the act of opening it. Where there is no sheet, the list opens instead.

This means behaviour differs by platform, which is worth knowing when you write help text or test scripts. Force one path with strategy="list" or strategy="native".

A dismissed sheet ends the interaction — the list does not then pop open, which would resurrect UI the user just dismissed.

Copy to clipboard

Supply copyLabel and a copy item appears. There is no default label, because a default would be a hardcoded English string. copiedLabel and copyFailedLabel are announced in a polite live region — copying is otherwise silent, so without them the user gets no confirmation.

Failure is handled, not assumed away: a denied permission, an insecure context, or a browser with no async clipboard all announce copyFailedLabel rather than throwing.

Why links, not a menu

Destinations render as real <a> elements, not role="menuitem". A menuitem role strips middle-click, open-in-new-tab, and copy-link-address — affordances users genuinely reach for on a share list. The WAI-ARIA APG suggests a disclosure when the items are links. Copy is a real action, so it is a <button>.

This also makes SharePicker the one helper in this catalog that is not an APG listbox: theme-picker, locale-picker and text-size-picker use aria-activedescendant over virtual options, while here focus moves for real between real elements.

Custom glyph

The default scoped slot replaces the button glyph and receives { open, url }:

<SharePicker label="Share this page" :targets="targets">
  <template #default="{ open }">
    <span aria-hidden="true">{{ open ? "×" : "➤" }}</span>
  </template>
</SharePicker>

Slot content renders inside the <button>, so keep it non-interactive; the accessible name always comes from label.

Props

Full table in spec/index.md §4.1. Required: label. Everything else is optional.

Accessibility

  • The glyph is aria-hidden; the name comes from aria-label, which also names the list, so a screen reader entering it hears what it is for.
  • Escape closes and returns focus to the button; arrows move between items and clamp; Home / End jump; Tab closes via the button — without cancelling the key — so the default Tab proceeds from the picker's position.
  • The status region is polite and empty on load.
  • Tradeoff: an icon-only control's name rests entirely on aria-label — there is no visible text fallback. See docs/accessibility.md.

Styling

Class hooks: .share-picker (root), .share-picker-button, .share-picker-icon, .share-picker-list, .share-picker-list-item, .share-picker-target, .share-picker-copy, .share-picker-status.

The package ships no CSS. The root themes/ stylesheets style the button and popup, including the optical glyph sizing that keeps ➤ visually the same size as the other helpers' glyphs.

Tests

npx vitest run @lilydesignsystem/vue-share-picker from the catalog root — 35 cases, one or more per §7 clause.


Lily™ and Lily Design System™ are trademarks.

Related Packages

@lilydesignsystem/vue-locale-picker

Lily Design System Vue 3 locale picker: an icon button opening an APG listbox th...

1,260 3
@lilydesignsystem/vue-theme-picker

Lily Design System Vue 3 theme picker: an icon button opening an APG listbox tha...

1,137 3
@lilydesignsystem/vue-text-size-picker

Lily Design System - Vue 3 text size picker

1,113 3
@lilydesignsystem/vue-search-picker

Lily Design System Vue 3 search picker: a magnifying-glass icon button opening a...

644 3
@lilydesignsystem/vue-motion-picker

Lily Design System - Vue 3 motion (reduced-motion) picker

816 3

Version History

Version Released Vue License
0.3.0 ^3.0.0 MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause
0.2.0 ^3.0.0 MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause
0.1.2 ^3.0.0 MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause
0.1.1 ^3.0.0 MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause
0.1.0 ^3.0.0 MIT OR Apache-2.0 OR GPL-2.0-only OR GPL-3.0-only OR BSD-3-Clause