Skip to content
ilinxa/pro-ui

Share Bar

alphav0.2.0

Social share button row — nine built-in platforms, custom targets, and copy-link with success feedback.

Category: MarketingUpdated: 2026-08-11Created: 2026-05-02Author: ilinxa

Context

Reach for it on news article footers, blog post footers, product page social rows, video player share clusters, doc page share affordances. Built-in URL templates for Twitter / Facebook / LinkedIn / Reddit / WhatsApp / Telegram / Email / Threads / Bluesky. Copy-link button uses navigator.clipboard with execCommand fallback for older / insecure-context browsers; success/error feedback is visual (icon flip) + audible (aria-live). Custom targets via 'kind: custom' with arbitrary onClick. Analytics hook via onShare(targetKind). SSR-safe — window.location.href read at click time, not render time.

Installation

Initialize shadcn (once per project)Seeds lib/utils.ts and components.json. Skip if you've already used any shadcn component.
pnpm dlx shadcn@latest init
Install the component
pnpm dlx shadcn@latest add @ilinxa/share-bar

Add -fixtures for dummy data:

pnpm dlx shadcn@latest add @ilinxa/share-bar-fixtures

CLI can't resolve @ilinxa? The namespace is listed in the official shadcn registry directory, so current CLIs need no configuration. If yours can't resolve it (older or pinned versions, self-hosted mirrors), register it manually in components.json:

"registries": {
  "@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}

Preview

Share

Click Copy link — icon flips to a check for 2s.

Demo source

demo.tsxtsx
"use client"; import { useState } from "react";import { Send } from "lucide-react";import { Tabs, TabsContent, TabsTrigger } from "@/components/ui/tabs";import { SwipeTabsList } from "@/components/site/swipe-tabs-list";import { ShareBar } from "./share-bar";import {  SHARE_BAR_DUMMY_COMPACT,  SHARE_BAR_DUMMY_DEFAULT,  SHARE_BAR_DUMMY_FULL,  SHARE_BAR_DUMMY_TITLE,  SHARE_BAR_DUMMY_TR,  SHARE_BAR_DUMMY_URL,} from "./dummy-data";import type { ShareTarget } from "./types"; export default function ShareBarDemo() {  const [lastShared, setLastShared] = useState<string | null>(null);  const [copyEvents, setCopyEvents] = useState(0);  const [internalDialog, setInternalDialog] = useState<string | null>(null);   const customTargets: ReadonlyArray<ShareTarget> = [    { kind: "twitter" },    { kind: "linkedin" },    {      kind: "custom",      id: "send-to-teammate",      icon: Send,      ariaLabel: "Send to teammate",      onClick: () => setInternalDialog("Internal share dialog opened (demo)"),    },    { kind: "copy" },  ];   return (    <Tabs defaultValue="default" className="w-full">      <SwipeTabsList>        <TabsTrigger value="default">Default</TabsTrigger>        <TabsTrigger value="full">Full + analytics</TabsTrigger>        <TabsTrigger value="custom">Custom target</TabsTrigger>        <TabsTrigger value="compact">Compact</TabsTrigger>        <TabsTrigger value="i18n">Localized (TR)</TabsTrigger>      </SwipeTabsList>       <TabsContent value="default" className="mt-6 max-w-md">        <ShareBar          targets={SHARE_BAR_DUMMY_DEFAULT}          url={SHARE_BAR_DUMMY_URL}          title={SHARE_BAR_DUMMY_TITLE}          headingAs="h4"          divider        />        <p className="mt-3 text-xs text-muted-foreground">          Click <strong>Copy link</strong> — icon flips to a check for 2s.        </p>      </TabsContent>       <TabsContent value="full" className="mt-6 max-w-2xl">        <ShareBar          targets={SHARE_BAR_DUMMY_FULL}          url={SHARE_BAR_DUMMY_URL}          title={SHARE_BAR_DUMMY_TITLE}          via="ilinxa_news"          hashtags={["ilinxa", "shadcn", "react"]}          headingAs="h4"          divider          onShare={(target) => setLastShared(target)}          onCopySuccess={() => setCopyEvents((n) => n + 1)}        />        <p className="mt-3 text-xs text-muted-foreground">          {lastShared            ? <>Last shared: <code>{lastShared}</code></>            : <>Click any button to log via <code>onShare</code>.</>}        </p>        <p className="mt-1 text-xs text-muted-foreground">          Copy successes (analytics): <code>{copyEvents}</code> — fires only on          successful clipboard write, distinct from <code>onShare</code> which          fires on every share-target click.        </p>      </TabsContent>       <TabsContent value="custom" className="mt-6 max-w-md">        <ShareBar          targets={customTargets}          url={SHARE_BAR_DUMMY_URL}          title={SHARE_BAR_DUMMY_TITLE}        />        <p className="mt-3 text-xs text-muted-foreground">          Custom target with <code>onClick</code>:{" "}          {internalDialog ? (            <span className="text-primary">{internalDialog}</span>          ) : (            <span>click the paper-plane icon.</span>          )}        </p>      </TabsContent>       <TabsContent value="compact" className="mt-6 max-w-md">        <ShareBar          targets={SHARE_BAR_DUMMY_COMPACT}          url={SHARE_BAR_DUMMY_URL}          title={SHARE_BAR_DUMMY_TITLE}        />      </TabsContent>       <TabsContent value="i18n" className="mt-6 max-w-md">        <ShareBar          targets={SHARE_BAR_DUMMY_TR}          url={SHARE_BAR_DUMMY_URL}          title="ShareBar — pro-ui paylaşım çubuğu"          headingAs="h4"          divider          labels={{            heading: "Paylaş",            copyAria: "Bağlantıyı kopyala",            copySuccess: "Bağlantı kopyalandı",            copyError: "Bağlantı kopyalanamadı",          }}        />      </TabsContent>    </Tabs>  );} 

Usage

When to use

A horizontal cluster of social-share buttons + a copy-link button, typically at the bottom of an article (or pinned to the side as a share rail). Each social button opens the platform's share intent in a new window with the page's URL prefilled; the copy button writes the URL to the clipboard with a 2-second visual + audible success affordance.

Basic example

import { ShareBar } from "@/components/share-bar"

export function ArticleFooter() {
  return (
    <ShareBar
      url="https://example.com/news/sustainable-cities"
      title="Sustainable cities, then and now"
      headingAs="h4"
      divider
      targets={[
        { kind: "twitter" },
        { kind: "facebook" },
        { kind: "linkedin" },
        { kind: "copy" },
      ]}
    />
  )
}

Built-in platforms

Twitter / Facebook / LinkedIn / Reddit / WhatsApp / Telegram / Email / Threads / Bluesky — each ships with a share-intent URL template + a default Lucide icon + a default English aria-label. Override the icon or aria-label per target if you want a different look or different text.

Custom targets

import { Send } from "lucide-react"

<ShareBar
  url={article.canonicalUrl}
  targets={[
    { kind: "twitter" },
    { kind: "linkedin" },
    {
      kind: "custom",
      id: "send-to-teammate",
      icon: Send,
      ariaLabel: "Send to teammate",
      onClick: () => openInternalShareDialog(article.id),
    },
    { kind: "copy" },
  ]}
/>

Analytics hook

<ShareBar
  targets={[{ kind: "twitter" }, { kind: "facebook" }, { kind: "copy" }]}
  onShare={(target) => analytics.track("article.share", { id: article.id, target })}
/>

onShare(targetKind) fires after a successful share-intent open or successful copy. For granular hooks use onCopySuccess / onCopyError.

URL resolution

  • If urlis provided, that's what gets shared.
  • Otherwise, window.location.href is read at click time on the client — never during render. SSR-safe.
  • Pass title / text / via / hashtags for templates that accept them (Twitter, Email, WhatsApp, etc.).

Copy fallback

Copy uses navigator.clipboard.writeTextfirst; if that isn't available (older browsers, insecure HTTP context, embedded webviews), it falls back to the deprecated document.execCommand("copy") on a hidden textarea. Both paths are inside a try/catch so the error state surfaces to onCopyError + the icon flip.

Notes

  • External links open in a new window with noopener,noreferrer for safety.
  • Copy success/error feedback is dual-channel: visible icon flip + aria-live announcement (polite for success, alert for errors).
  • The 2-second success-reset timeout is cleared on unmount — no setState after unmount warning.
  • Threads + Bluesky icons fall back to Share2 from lucide-react until first-party icons land. Override via target.icon if you ship custom SVGs.
  • The component is exported as React.memo. Memoize the targets array (or use a module-level constant) for the memo to bite — inline arrays defeat it.
  • Don't reach for this if you need share-count metrics — pure action surface, no API calls.

Features

  • 9 built-in platforms with URL templates (Twitter / Facebook / LinkedIn / Reddit / WhatsApp / Telegram / Email / Threads / Bluesky)
  • Copy-link button with success/error feedback (icon flip + aria-live)
  • Clipboard fallback to document.execCommand for older / insecure browsers
  • Custom targets via 'kind: custom' (arbitrary icon + onClick)
  • Analytics hook via onShare(targetKind)
  • Configurable URL / title / text / via / hashtags
  • Optional section heading (h2 / h3 / h4) with i18n labels
  • Optional top divider (`pt-8 border-t border-border`)
  • External links use target=_blank rel=noopener,noreferrer
  • Memoized; SSR-safe; <ul>/<li> semantics

Tags

share-barsharesocialcopy-linktwitterfacebooklinkedinmarketing

Dependencies

shadcn primitives: button
npm peer deps: lucide-react@^1.11.0