Reference · UX pattern catalogueSocial patterns

28 patterns in this category — each with its illustration, what it is, and the fields that matter when you build or review one. Part of the UX pattern catalogue; the raw, agent-readable version is ux-pattern-catalogue/social-patterns.json.

Profile links

Floating Social Sidebar social-004

A fixed-position vertical strip of social icons along the left or right edge of the viewport. Stays visible while scrolling long content pages. Desktop only — hidden on mobile to preserve screen real estate. Icons are plain anchor links with no tracking scripts. Common on blogs, documentation sites, and content-heavy marketing pages.

Implementation

position: fixed with top: 50%; transform: translateY(-50%) for vertical centering. Use a <nav> with flex-direction: column. Hide with display: none below 1024px breakpoint (mobile/tablet). Icons: 20px inline SVGs in <a> tags. Subtle background/border on hover. z-index below modals but above content. Add a slight left/right offset (16-24px) from viewport edge.

Accessibility

<nav aria-label="Social links">. Each link: aria-label="Follow on {Platform}". The sidebar must not overlap or obscure main content. Do not trap keyboard focus — users should be able to Tab past it. Consider aria-hidden="true" when sidebar is display:none on mobile.

Share

Native Web Share API social-006

Uses the browser's built-in navigator.share() API to trigger the OS-native share sheet. Supports sharing to any installed app (social media, messaging, email, notes). Progressive enhancement: the share button only renders if the API is available, with a fallback to manual share links. Zero third-party code — the browser handles everything.

Implementation

Feature-detect with if (navigator.share). Call navigator.share({ title, text, url }) on button click. Wrap in try/catch to handle AbortError (user cancelled) gracefully. Fallback: if navigator.share is undefined, show Static Share Links instead. Use navigator.canShare() to validate data before calling share(). Available on mobile browsers, Safari, and Chromium desktop.

Accessibility

Button: <button aria-label="Share this page">. Include visible text or icon+text. Fallback links must be equally accessible. Do not auto-trigger share — always require explicit user click.

Click 1 Click 2

Two-Click Share (Shariff) social-008

A privacy-first share pattern developed by the German tech publisher c't/Heise. Shows a static, non-tracking preview button as the first click (consent). Only loads the actual platform share widget or navigates to the share URL on the second click (action). No third-party scripts are loaded until the user explicitly opts in. Compliant with strict European privacy regulations.

Implementation

First state: static button with platform icon and "Share on X" text. No external resources loaded. On first click, swap to active state: either redirect to share URL (simple) or load the platform embed JS (widget mode). Add a visual transition (button color change, checkmark) to indicate activation. Store consent preference in sessionStorage to avoid re-consent on the same visit. Use the Shariff library or implement the two-state pattern manually.

Accessibility

First state: <button aria-label="Activate {Platform} sharing">. After activation: aria-label changes to "Share on {Platform}" or link is rendered. Announce state change with aria-live="polite" or by changing button text. Keyboard: Enter/Space activates both clicks.

Share Preview Card social-011

Shows a visual preview of how the shared post will appear on social platforms before the user clicks share. Displays the OG image, title, and description as a mock social card. Builds user confidence by showing exactly what recipients will see. Data is sourced entirely from the page's own meta tags — no external API calls.

Implementation

Read og:title, og:description, og:image from the page's <meta> tags (or pass as props). Render a card matching the typical social preview format: image on top (1.91:1 aspect ratio), title below, description truncated to 2 lines, domain name. Style with subtle border and shadow. Position above or alongside the share buttons. Update preview if the page has different OG tags per section.

Accessibility

Preview card is decorative/informational — not interactive itself. Image: alt text from og:title or empty alt if title is displayed as text. Ensure text contrast meets WCAG AA within the preview card.

Embeds

Facade Embed (Click-to-Load) social-012

Shows a static screenshot or placeholder image of the social content instead of the actual embed. The real iframe or embed script loads only when the user explicitly clicks. Massively improves page load performance (embeds can add 500KB-2MB of JS) and blocks all tracking until consent. Works for Twitter/X posts, YouTube videos, Instagram posts, TikTok, and any iframe-based embed.

Implementation

Render a <button> or clickable <div> containing: a static image (screenshot or thumbnail), the platform logo overlay, and a "Load embed" or play button icon. On click, replace the placeholder with the actual <iframe> or <script>-based embed. For YouTube: use the video thumbnail (https://img.youtube.com/vi/{id}/maxresdefault.jpg) and load the iframe on click. Use loading="lazy" on the iframe. Add a subtle "Click to load content from {Platform}" label.

Accessibility

Placeholder: <button aria-label="Load {Platform} embed: {content title}">. After loading: the iframe should have title="{content description}". Announce load completion with aria-live="polite". Keyboard: Enter/Space triggers load. Focus should move into the loaded embed if it is interactive.

youtube-nocookie.com

Privacy-Proxied Embed social-013

Uses privacy-enhanced embed URLs that strip tracking cookies and scripts. YouTube's youtube-nocookie.com domain is the canonical example — it serves video without setting cookies until the user plays. For Twitter/X, use a static screenshot + link instead of the widget JS. For other platforms, self-hosted proxies or server-rendered snapshots replace tracking-heavy embeds.

Implementation

YouTube: replace youtube.com/embed with youtube-nocookie.com/embed. Add &rel=0 to disable related video suggestions. Vimeo: add ?dnt=1 (Do Not Track parameter). Twitter: render tweet text as a styled <blockquote> with a "View on Twitter" link. Instagram: server-render the post image + caption at build time. Set the iframe sandbox attribute to restrict capabilities: sandbox="allow-scripts allow-same-origin".

Accessibility

Same requirements as standard embeds: iframe title attribute, keyboard accessibility. Privacy-proxied URLs should not change the user-facing content or controls. If using blockquote fallback, ensure the attribution link is clearly labeled.

Blockquote Fallback Embed social-014

Renders social media content as a styled HTML <blockquote> with attribution, instead of using an iframe or embed script. Zero JavaScript, zero tracking, zero external requests. The content is static text with a link to the original post. Accessible, fast, and works everywhere. This is the ideal fallback for any social embed and the default for content that does not require interactivity (tweets, quotes, text posts).

Implementation

Structure: <blockquote cite="{post-url}"><p>{post text}</p><footer>— <a href="{post-url}" rel="noopener" target="_blank">{author} on {Platform}, {date}</a></footer></blockquote>. Style with a left border accent, subtle background, platform icon. Include the author avatar as a small <img> if available (self-hosted copy). For tweets: preserve @mentions and #hashtags as plain text or links. Sanitize all user-generated content.

Accessibility

<blockquote> with cite attribute for the source URL. Attribution in <footer> or <figcaption>. If wrapped in <figure>, use <figcaption> for attribution. Screen readers announce blockquotes natively. Ensure link text is descriptive ("View original post by @author").

Server-Rendered Social Card social-016

Fetches social media post data at build time (via API or scraping) and renders it as static HTML. The card looks like a social media post (avatar, name, text, images, timestamp) but is entirely self-hosted with zero runtime external requests. Ideal for documentation, case studies, and testimonial pages where embed content is known ahead of time and does not need to be live.

Implementation

At build time: fetch post data from the platform API (Twitter API v2, Instagram oEmbed, etc.). Store the response in a local JSON file. Render the card with: avatar image (self-hosted copy), author name, post text, media (self-hosted copies), and timestamp. Link to original with "View on {Platform}". Cache API responses to avoid rate limits. Re-fetch on deploy or on a schedule. Handle deleted posts gracefully (show "This post is no longer available").

Accessibility

Card is a <figure> with <figcaption> for attribution. Images: descriptive alt text. Timestamp: <time datetime="{ISO}">. Text content: use semantic HTML (paragraphs, links). The card is not interactive beyond the "View original" link.

Feeds

Static Feed Snapshot social-017

Fetches the latest N posts from a social media API at build time and renders them as static HTML cards. The feed only refreshes when the site is rebuilt/deployed. No client-side API calls, no real-time updates, no tracking scripts. Fast, private, and reliable. Ideal for homepages, sidebars, and marketing pages where freshness within hours is acceptable.

Implementation

At build time: call the platform API (Twitter v2, Mastodon, Bluesky, LinkedIn) for the latest 3-5 posts. Transform response into a normalized data format. Render as a grid or list of static cards (avatar, name, text, media, timestamp, link to original). Self-host all images. Set a Rebuild schedule (hourly, daily) via CI/CD. Handle API failures gracefully (use cached data). Show "Last updated: {date}" footer.

Accessibility

Feed container: <section aria-label="Latest posts from {Platform}">. Each post: <article> with heading (author name) and content. Timestamps: <time> element. Images: alt text. Links: "View post on {Platform}". Avoid auto-scrolling or auto-updating.

RSS-Powered Feed social-018

Consumes an RSS or Atom feed instead of a proprietary platform API. Platform-agnostic — works with Mastodon, Bluesky, WordPress, Ghost, Medium, Substack, YouTube channels, and any service that publishes RSS. Rendered server-side at build time. No API keys required for most feeds, making this the most maintainable feed pattern.

Implementation

Parse the RSS/Atom XML at build time with a lightweight parser. Extract: title, link, description/content, pubDate, author, and enclosure (media). Render as styled post cards. For Mastodon: use the /users/{handle}.rss endpoint. For YouTube: use the channel RSS feed (https://www.youtube.com/feeds/videos.xml?channel_id={id}). Sanitize all HTML content from feeds. Limit to 5-10 items. Cache parsed feed data.

Accessibility

Same as Static Feed Snapshot. Content from RSS may contain raw HTML — sanitize and ensure it meets contrast and structure requirements. Strip inline styles from feed content. Ensure all feed-sourced images have alt text (use title as fallback).

Hybrid Feed (Static + Client Refresh) social-019

Combines a build-time static snapshot with an optional client-side "Load latest" button. The page loads instantly with pre-rendered content (no loading spinners). Users who want fresher data can explicitly request it. No auto-polling, no background fetches. The client refresh is user-initiated only, keeping the pattern privacy-respecting.

Implementation

Render the build-time snapshot as the default view. Add a "Load latest" button below the feed. On click: fetch from your own API proxy (never directly from the social platform client-side) and replace/prepend new items. Show a loading indicator during fetch. If no new items, show "You're up to date". Rate-limit the refresh button (disable for 30 seconds after click). The proxy endpoint should cache and rate-limit upstream API calls.

Accessibility

Refresh button: <button aria-label="Load latest posts">. During loading: aria-busy="true" on the feed container. After update: announce "{count} new posts loaded" via aria-live="polite". New items should be added to the DOM without disrupting focus or scroll position.

Testimonial Wall social-020

A curated collection of social media mentions, reviews, or endorsements displayed as testimonial cards. Content is manually curated or semi-automatically fetched and approved. No live connection to any platform. Attribution links point to the original post. Used on landing pages, about pages, and case study sections for social proof.

Implementation

Store testimonials in a data file (JSON, YAML, or CMS). Each entry: author name, handle, avatar (self-hosted), quote text, platform, original post URL, and date. Render as a responsive grid (2-3 columns) or masonry layout. Include platform icon for attribution. Optionally rotate/randomize a subset on each build. Keep testimonials curated — auto-importing all mentions leads to quality control issues.

Accessibility

Each testimonial: <blockquote> with <footer> for attribution. If using a carousel, provide prev/next buttons and pause auto-rotation. Avatar: alt="{Name}". Grid: ensure logical tab order. Do not use auto-playing animations or auto-scrolling.

Social proof

Static Counter Display social-021

Displays follower counts, GitHub stars, download numbers, or user counts as formatted static numbers. Values are fetched at build time and baked into the HTML. No real-time counter widgets, no client-side API calls, no animated counting. Stale-while-revalidate is acceptable — exact real-time counts add complexity without meaningful UX benefit.

Implementation

Fetch counts from APIs at build time (GitHub API, npm registry, social platform APIs). Format with Intl.NumberFormat or abbreviate (e.g., "12.3k"). Display with a label and icon. Layout: horizontal row of metric cards or inline badges. Refresh on each deploy. Cache API responses. Handle API failures by falling back to the last known value. Show "as of {date}" for transparency.

Accessibility

Each counter: <span aria-label="{count} {metric} on {Platform}">. Abbreviated numbers (12.3k) need the full number in aria-label. Do not use decorative animations (no counting-up effect unless prefers-reduced-motion is checked).

GitHub Star Badge social-022

A compact badge showing the GitHub star count for a repository. Links to the repo. Uses either a shields.io badge image (build-time snapshot) or a static count rendered in HTML. Avoids the official GitHub star button widget which loads external JS and tracks users.

Implementation

Option A (shields.io): <img src="https://img.shields.io/github/stars/{owner}/{repo}?style=social" alt="{count} GitHub stars">. Build-time: fetch via GitHub API and render as static HTML with star icon + number. Link: <a href="https://github.com/{owner}/{repo}">. Style: pill-shaped badge with star icon. Self-host the shields.io image at build time for full privacy.

Accessibility

Badge image: alt="{repo} has {count} stars on GitHub". If using HTML: <a aria-label="{count} GitHub stars — view repository">. Star icon: aria-hidden="true". Ensure badge text meets minimum contrast ratio.

User Count Banner social-024

"Join 5,000+ developers" — a simple banner or inline element displaying the user/customer count. Placed above the fold, near primary CTAs, or in the site header. The count is updated manually or at build time. No live counter widget. Combined with a call-to-action, this is one of the highest-impact social proof patterns for conversion.

Implementation

Display format: "Join {count}+ {audience}" or "Trusted by {count} teams". Use a round/floor number (5,000 not 5,127) for cleaner presentation. Update the count in a data file or environment variable. Placement: directly above or below the primary CTA button. Pair with small avatar stack (3-5 overlapping circular avatars) for visual reinforcement. Keep the banner text concise — one line maximum.

Accessibility

Text: plain HTML paragraph or heading. No special ARIA needed — it is regular content. If using an avatar stack, images should have alt="" (decorative) since the text carries the meaning. Ensure the count is not the only indication of value — pair with descriptive text.

Identity

rel=me

rel="me" Verification social-025

Bidirectional identity verification between your website and social profiles using the rel="me" HTML attribute. When your site links to a social profile with rel="me" AND that profile links back to your site, platforms like Mastodon display a verified checkmark. This is the IndieWeb standard for decentralized identity verification — no central authority required.

Implementation

Add rel="me" to all social profile links on your site: <a href="https://mastodon.social/@you" rel="me">. Then add your website URL to each social profile's bio/website field. Mastodon verifies automatically by checking for the bidirectional link. GitHub: add your site URL to your profile. For maximum verification coverage, place rel="me" links on your homepage (not just a subpage). Combine with rel="noopener" for external links: rel="me noopener".

Accessibility

rel="me" is invisible to users — it is metadata for machines. No accessibility impact. The visible link text and aria-label should describe the destination, not the rel attribute.

Schema.org sameAs social-026

Lists all official social media profile URLs in the site's structured data (JSON-LD) using the Schema.org sameAs property. Search engines use this to connect your website identity with your social accounts, enabling Knowledge Panel population and entity disambiguation. AI agents also parse sameAs to identify official channels.

Implementation

Add a JSON-LD block to the homepage (or every page) with Organization or Person type. Include sameAs as an array of URLs: "sameAs": ["https://twitter.com/you", "https://github.com/you", "https://linkedin.com/in/you", "https://mastodon.social/@you"]. Keep the list comprehensive — include all official accounts. Place in <head> via a <script type="application/ld+json"> tag. Validate with Google Rich Results Test.

Accessibility

JSON-LD is invisible to users and screen readers. No accessibility impact. It is purely machine-readable metadata.

Open Graph Profile Tags social-027

Specialized Open Graph meta tags for personal or team profile pages. Uses og:type="profile" with additional profile-specific properties: profile:first_name, profile:last_name, profile:username, and profile:gender. When a profile page is shared on social media, platforms display it with a person-specific card format rather than a generic article card.

Implementation

Add to <head> on profile/about/team pages: <meta property="og:type" content="profile" />, <meta property="profile:first_name" content="{First}" />, <meta property="profile:last_name" content="{Last}" />, <meta property="profile:username" content="{handle}" />. Keep standard OG tags too (og:title, og:description, og:image). For team pages with multiple people, the page-level og:type should be "website" — use profile type only on individual profile pages.

Accessibility

OG tags are invisible metadata. No accessibility impact. They only affect how the page appears when shared on social platforms.

@you@yourdomain.com

Fediverse Discovery (Webfinger) social-028

A .well-known/webfinger endpoint that enables ActivityPub/fediverse discovery from your domain. Users on Mastodon, Pixelfed, or other fediverse platforms can follow @you@yourdomain.com, which resolves to your actual fediverse account via the Webfinger protocol. Establishes your domain as your decentralized identity anchor.

Implementation

Create a .well-known/webfinger endpoint that returns JSON. For static sites: create a static JSON file at /.well-known/webfinger with the appropriate response. The resource parameter should map your-domain to your fediverse account. Response: { "subject": "acct:user@yourdomain.com", "aliases": ["https://mastodon.social/@you"], "links": [{ "rel": "self", "type": "application/activity+json", "href": "https://mastodon.social/users/you" }] }. Add Content-Type: application/jrd+json header (or rely on file extension for static hosting). For dynamic sites, implement as an API route that handles the resource query parameter.

Accessibility

Webfinger is an invisible protocol endpoint. No user-facing UI required. No accessibility impact.