@page-speed/social-share


Installation
pnpm add @page-speed/social-share
npm install @page-speed/social-share
Peer dependencies
react | >=18.0.0 |
react-dom | >=18.0.0 |
@opensite/hooks | >=2.0.0 |
Quick start
import { SocialShare } from "@page-speed/social-share";
export default function BlogPost() {
return (
<SocialShare
postTitle="My Blog Post"
shareUrl="https://example.com/my-post"
summaryContent="A short summary of the article for email sharing."
imgUrls={["https://example.com/og-image.jpg"]}
hashtags={["react", "webdev"]}
/>
);
}
Exports
The package exposes four sub-path entry points for tree-shaking:
import { SocialShare, useMobileShare } from "@page-speed/social-share";
import type { SocialShareProps, ShareParams, ShareResult } from "@page-speed/social-share";
import { SocialShare } from "@page-speed/social-share/core";
import { useMobileShare } from "@page-speed/social-share/hooks";
import type { SocialShareProps } from "@page-speed/social-share/types";
. | SocialShare, useMobileShare, all types |
./core | SocialShare component + re-exported SocialShareProps type |
./hooks | useMobileShare hook |
./types | SocialShareProps, ShareParams, ShareResult type definitions |
Component: SocialShare
The primary export. Renders social share buttons for X (Twitter), Facebook, Pinterest, LinkedIn, Email, and the native Web Share API.
Props (SocialShareProps)
postTitle | string | -- | Yes | Title text used in share intents (tweet text, email subject, Pinterest description). |
shareUrl | string | -- | Yes | The canonical URL to share. |
summaryContent | string | -- | Yes | Short summary included in the email body. |
imgUrls | string[] | undefined | No | Image URLs. The first image is used for Pinterest media param. All images are attached to native share payloads when supported. Pinterest button only appears when at least one image is provided. |
hashtags | string[] | [] | No | Hashtags for X/Twitter (rendered without #, comma-separated). |
variant | "standard" | "sticky" | "combo" | "standard" | No | Layout variant (see Variants below). |
inlineSize | number | 42 | No | Width and height in pixels for each inline (standard) button. Icon size is automatically derived as half this value. |
containerClassName | string | "" | No | Additional CSS class applied to the outer <section> wrapper of the standard variant. |
disableImageAttachments | boolean | false | No | When true, skips image fetching/conversion and does not attach images to native share payloads. Useful when images are behind auth or CORS restrictions. |
Variants
"standard" (default)
Renders a horizontal row of circular share buttons inline with your content.
<SocialShare
variant="standard"
postTitle="My Post"
shareUrl="https://example.com/post"
summaryContent="Post summary"
/>
"sticky"
Renders a vertical floating sidebar on the right edge of the viewport. The bar appears after the user scrolls past 200px (or past a triggerRef element) and hides when a <footer> element comes into view. Rendered via ReactDOM.createPortal to avoid layout conflicts.
<SocialShare
variant="sticky"
postTitle="My Post"
shareUrl="https://example.com/post"
summaryContent="Post summary"
/>
"combo"
Renders both the standard inline buttons and the sticky sidebar simultaneously.
<SocialShare
variant="combo"
postTitle="My Post"
shareUrl="https://example.com/post"
summaryContent="Post summary"
/>
Adaptive behavior
The component automatically adapts to the device:
| Mobile/tablet + touch + Web Share API | Shows a single native share button (opens the OS share sheet). |
| Desktop + Web Share API | Shows all social buttons plus a native share button at the end. |
| Desktop without Web Share API | Shows social buttons only (X, Facebook, Pinterest, LinkedIn, Email). |
Hook: useMobileShare
Low-level hook that wraps the Web Share API. Use this directly if you need custom share UI.
Parameters (ShareParams)
title | string | -- | Share title (required). |
url | string | undefined | URL to share. |
imageUrls | string[] | undefined | Image URLs to fetch, convert to base64, and attach as files. |
images | string[] | undefined | Pre-converted base64 image strings (fallback when imageUrls is empty). |
attachImages | boolean | true | When false, skips all image processing and never attaches files. |
Return value (ShareResult)
share | () => Promise<void> | Trigger the native share dialog. Gracefully falls back to sharing without files if file sharing is unsupported. |
canShare | boolean | true when navigator.share is available on the current device. |
error | string | null | Error message from the last failed share attempt, or null. |
Example
import { useMobileShare } from "@page-speed/social-share/hooks";
function CustomShareButton() {
const { share, canShare, error } = useMobileShare({
title: "Check this out",
url: "https://example.com",
imageUrls: ["https://example.com/photo.jpg"],
});
if (!canShare) return null;
return (
<>
<button onClick={share}>Share</button>
{error && <p>Error: {error}</p>}
</>
);
}
Supported platforms
Social share URL endpoints
| X (Twitter) | https://twitter.com/intent/tweet | text, url, hashtags |
| Facebook | https://www.facebook.com/sharer.php | u (URL only) |
| Pinterest | https://pinterest.com/pin/create/button | url, media, description |
| LinkedIn | https://www.linkedin.com/sharing/share-offsite/ | url (title/description read from page OG tags) |
| Email | mailto: | subject, body |
Web Share API browser compatibility
| Chrome 128+ | Full |
| Edge 104+ | Full |
| Safari 12.1+ (macOS), 12.2+ (iOS) | Full |
| Samsung Internet 8.2+ | Full |
| Opera 114+ | Full |
| Firefox Android 147+ | Full |
| Firefox Desktop | Not supported (experimental flag only) |
When the Web Share API is unavailable, the component gracefully falls back to showing only the URL-based social buttons.
Styling
The component uses Tailwind CSS 4 with semantic design tokens from the @theme configuration. All styling is done via utility classes -- there are no inline <style> tags or CSS files to import.
Design tokens used
bg-card | Sticky bar background |
text-card-foreground | Icon hover color |
bg-muted | Inline button background, sticky button hover pseudo-element |
text-muted-foreground | Icon default color |
border-border | Sticky bar border |
To customize the appearance, override these CSS custom properties in your theme. The component inherits all colors dynamically, making it compatible with light/dark mode and custom brand themes.
Performance
Bundle size
index.js (ESM) | 26.39 KB | 6.02 KB |
core/index.js | 26.35 KB | 6.01 KB |
hooks/index.js | 4.49 KB | 1.48 KB |
Optimizations
- Module-level constants -- All CSS class strings and share URL constants are hoisted outside components to eliminate per-render allocations.
useCallback throughout -- getSocialUrl, handleSocialShare, handleNativeShare, and the share function in useMobileShare are all stabilized with useCallback.
- Pure utility extraction --
fetchBlob, urlToBase64, and dataURLtoFile are defined at module scope (not inside hooks), so they are never recreated.
- Passive scroll listeners -- The sticky variant uses
{ passive: true } scroll event listeners for zero scroll-jank.
- Portal rendering -- The sticky sidebar renders via
ReactDOM.createPortal to avoid triggering parent re-layouts.
- Image preloading -- Images are preloaded into the browser cache on mount so they are ready when the user clicks share.
- Graceful degradation -- If
IntersectionObserver is unavailable, the sticky bar shows immediately rather than breaking.
- Tree-shakable -- Sub-path exports allow consumers to import only what they need. The package is marked
"sideEffects": false.
Image handling
When imgUrls is provided and disableImageAttachments is false:
- Images are preloaded into the browser cache via
new Image() on component mount.
- For native sharing, images are fetched as blobs and converted to base64
File objects.
- S3 URLs matching the allowed bucket are fetched through a CORS proxy (
/media_proxy). All other URLs use a direct fetch with no-cors fallback.
- If file sharing is not supported by the device (checked via
navigator.canShare), the share falls back to URL-only sharing automatically.
Set disableImageAttachments={true} to skip all image processing when images are behind authentication or when you want faster share interactions.
Architecture
src/
index.ts Main barrel export
core/
index.ts Re-exports SocialShare + SocialShareProps
social-share.tsx SocialShare component + StickyShareBar subcomponent
hooks/
index.ts Re-exports useMobileShare
useMobileShare.tsx Web Share API hook + image proxy utilities
types/
index.ts Re-exports all type definitions
social-share.ts SocialShareProps, ShareParams, ShareResult
Build
Built with tsup. Outputs ESM + CJS with TypeScript declarations and source maps.
pnpm build
pnpm dev
pnpm type-check
pnpm bundle-analysis
pnpm test
License
BSD-3-Clause