GODRICH
#css-animation#button#loader#transition#scroll#dashboard#ui#design-trends

Social Share Buttons: 9 Copy-Paste UI Patterns

Social share buttons are the last door a page leaves through, and the one tap a fake demo gives away.

Auto-plays · click a tile to jump to its section · all nine in one zip

The nine social share buttons html patterns are ordered by when a visitor meets them, not by what is trendy. They start with the inline row found at the end of an article (01), move to the rail that rides beside the text while they read (02) and the toast that answers the moment they press copy (03). After the share-count badges that show proof (04) come the two ways to save space — an icon that fans out of a single button (05) and a bar pinned to the bottom (07) — while phones get the sheet that rises from below (06). The last two close the loop: a QR popup that bridges offline paper (08) and a box that lets the sender pick mail or embed (09). All nine share the same ink #15131a, cream #fdf8f0, and sky #7fd4f5, so mixing two or three never drifts out of palette. Returning the visitor to their place afterward is breadcrumb UI's job, and finishing the job without leaving the keys is the axis keyboard shortcut UI owns.

01Inline share row

What this share button ui design shows

A label and four round 44px icon buttons in one row at the bottom of the card — the shape everyone builds first. On hover and focus a button lifts 4px while a circle laid over it fills from scale(.72) to scale(1), and 44px is the minimum fingertip target.

Inline share row
Open in a new tab

01 · Inline share row — Measured area 4.712% · moving frames 15/23Original by GODRICH · SCSS and React ship in the zip · See the password

scale 링translateY 들림:focus-visible

Where to look in the code

A pressed button stays pressed via aria-pressed, showing what was picked.

/* The ring is a stacked circle, not a border — scaling it never relayouts */
.ir__ring {
  position: absolute;
  inset: 0;
  border: 2px solid $color;
  border-radius: 50%;
  transform: scale(.72);
  opacity: 0;
  transition: transform $dur-quick $easing, opacity $dur-quick $easing;
}
.ir__btn:focus-visible .ir__ring {
  transform: scale(1);
  opacity: 1;
}
.ir__btn[aria-pressed="true"] {
  background: $color;
  color: $sh-paper;
}

02Floating side share rail

What happens on screen

On magazine-style sites with long articles, the rail sits vertically beside the text and follows the scroll with position: sticky. Buttons keep their labels hidden until hover or focus unfolds them sideways — the unfolding reveals a right side kept cut off by clip-path: inset(0 100px 0 0 round 999px), so layout never recalculates.

Floating side share rail
Open in a new tab

02 · Floating side share rail — Measured area 15.565% · moving frames 17/23Original by GODRICH · SCSS and React ship in the zip · See the password

clip-path: inset()position: sticky시차 등장

How to read the code

After the 100px cut, 36px stay visible while the label only starts at 40px (12px padding, 20px icon, 8px gap), so no letter peeks while collapsed.

/* Reserve the full 136px width, then cut 100px off the right */
.sr__link {
  width: 136px;
  height: 44px;
  clip-path: inset(0 100px 0 0 round $r-pill);
  transition: clip-path $dur-base $easing;
}
.sr__link:hover,
.sr__link:focus-visible {
  clip-path: inset(0 0 0 0 round $r-pill);
}

03Copy-link toast

What moves

Half of sharing is copying, so pressing the button actually writes the URL through navigator.clipboard.writeText and a toast rising from below reports the result. When the clipboard is missing it selects the input, and either way the toast says "copy it yourself" with a cross mark rather than a check — showing a check for a failure would be a lie.

Copy-link toast
Open in a new tab

03 · Copy-link toast — Measured area 19.812% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

navigator.clipboardrole=statussteps(1, end)

What to change

One role="status" region reads the outcome.

function show(msg, failed) {
  text.textContent = msg;
  root.classList.toggle('is-bad', !!failed);   // blocked: cross mark, not a check
  root.classList.add('is-done');
}
navigator.clipboard.writeText(url).then(
  function () { show('Link copied', false); },
  function () { show('Copying was blocked. Select it yourself', true); }
);

04Share-count count-up

What you see

Badges showing how often a page traveled are proof, yet rolling the number from zero up to the goal needs no JavaScript counter — a custom property registered with @property and a CSS counter do it in place. Using it without registration leaves it untyped, un-interpolated, and good for one mid-flight blip; that is the crux of this pattern.

Share-count count-up
Open in a new tab

04 · Share-count count-up — Measured area 1.206% · moving frames 20/23Original by GODRICH · SCSS and React ship in the zip · See the password

@propertycounter-resetscale 튐

Where to look in the code

Because it is registered with inherits: false, the value must sit on the badge itself.

@property --shareCount {
  syntax: "<integer>";
  inherits: false;
  initial-value: 0;
}
.cb__badge {
  counter-reset: shareCount var(--shareCount);
}
.cb__badge::after { content: counter(shareCount); }
.cb__badge--a { --shareCount: 128; }   /* final value outside .is-demo */

05Expanding icon share button

What happens on screen

On a one-page portfolio with a narrow header, you keep a single share button, and pressing it fans four platform buttons out sideways with a stagger. Collapsing is not shrinking the width to zero but cutting the buttons fully off with clip-path, so their places stay reserved and neighboring text never gets pushed.

Expanding icon share button
Open in a new tab

05 · Expanding icon share button — Measured area 5.952% · moving frames 14/23Original by GODRICH · SCSS and React ship in the zip · See the password

aria-expandedclip-path: inset()translateX

How to read the code

While collapsed, the buttons drop out of the Tab order with tabIndex -1, and Escape folds them back and returns focus to the share button.

.ex__item {
  transform: translateX(-16px);
  clip-path: inset(0 100% 0 0 round 50%);
  transition: clip-path $dur-base $easing, transform $dur-base $easing;
}
.ex.is-open .ex__item {
  transform: translateX(0);
  clip-path: inset(0 0 0 0 round 50%);
}
.ex.is-open .ex__item:nth-child(2) { transition-delay: 60ms; }

06Mobile share sheet

What moves

On phones, the sheet rising from the bottom is the familiar shape: pressing share raises a role="dialog" sheet, dims the backdrop, and the four cells surface a beat after the sheet. The backbone is the focus trap.

Mobile share sheet
Open in a new tab

06 · Mobile share sheet — Measured area 39.759% · moving frames 10/23Original by GODRICH · SCSS and React ship in the zip · See the password

translateY 바텀시트role=dialogfocus trap

What to change

Opening moves focus to the first button, Tab wraps from last back to first, Escape and a backdrop click close it, and closing returns focus to the share button.

// Focus trap — while the sheet is open, Tab stays inside it
sheet.addEventListener('keydown', function (e) {
  if (e.key === 'Escape') { setOpen(false); return; }
  if (e.key !== 'Tab') return;
  var list = focusables(); var last = list.length - 1;
  var at = list.indexOf(document.activeElement);
  if (e.shiftKey && at <= 0)      { e.preventDefault(); list[last].focus({ preventScroll: true }); }
  else if (!e.shiftKey && at === last) { e.preventDefault(); list[0].focus({ preventScroll: true }); }
});

07Sticky bottom share bar

What you see

To keep the reading flow intact, the bar pins to the bottom of the document with position: sticky; bottom: 0, and pressing the handle or swiping up more than 12px unfolds five icons. The unfolded height is not a fixed pixel but a grid-template-rows: 0fr → 1fr track — height: auto has no intermediate value while 0fr and 1fr do.

Sticky bottom share bar
Open in a new tab

07 · Sticky bottom share bar — Measured area 14.452% · moving frames 15/23Original by GODRICH · SCSS and React ship in the zip · See the password

grid-template-rowsaria-expandedtouch-action: pan-y

Where to look in the code

The inner wrapper needs min-height: 0 or the track never truly collapses.

.sb__panel {
  display: grid;
  grid-template-rows: 0fr;                 /* collapsed */
  overflow: hidden;
  transition: grid-template-rows $dur-base $easing;
}
.sb__inner { min-height: 0; }              /* the one line that makes 0fr fold */
.sb.is-open .sb__panel { grid-template-rows: 1fr; }

08QR code share popup

What happens on screen

Where hands cannot reach — cards, booths, signs — the screen passes the address to a camera, and this popup's QR is not an image file but a 29×29 module matrix of black and white grid cells, so it never blurs when enlarged and cannot be tinted by mistake. Reading the actually rendered frame back through a camera library returned https://godrichstory.com.

QR code share popup
Open in a new tab

08 · QR code share popup — Measured area 13.812% · moving frames 22/23Original by GODRICH · SCSS and React ship in the zip · See the password

role=dialogtranslateY 스캔 라인scale 팝업

How to read the code

The scan line sweeping the code exists only in the preview.

// A 29×29 module matrix — black-cell coordinates, not an image.
// The full 29 rows ship in the zip's original file.
var rows = [
  "00000000000000000000000000000",
  "00111111101100010010111111100",
  "00100000100000010000100000100"
];

rows.forEach(function (line) {
  line.split('').forEach(function (bit) {
    var cell = document.createElement('span');
    cell.className = bit === '1' ? 'qp__cell is-on' : 'qp__cell';
    cells.appendChild(cell);
  });
});
grid.appendChild(cells);

09Email and embed share box

What moves

When the recipient is decided, mail lands directly: the mail tab's link is a mailto: with subject and body percent-encoded, so pressing it opens a mostly written email, while the embed tab offers a read-only code field and a copy button. Tabs switch by click and by the arrow keys.

Email and embed share box
Open in a new tab

09 · Email and embed share box — Measured area 13.145% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · See the password

mailto:readonlytranslateX

What to change

The underline travels by cell width, translateX(calc(var(--n) * 100%)).

// The underline moves by cell index only — never by a guessed px
function move(i) {
  at = (i + tabs.length) % tabs.length;
  tabs.forEach(function (t, k) {
    t.classList.toggle('is-on', k === at);
    t.setAttribute('aria-selected', k === at ? 'true' : 'false');
    t.tabIndex = k === at ? 0 : -1;        // roving tabindex
    panes[k].hidden = k !== at;
  });
  root.style.setProperty('--n', at);
}

Where it breaks — traps

The first break was the poster frame. In the count-up, the moment the numbers reach their goals (128, 64, 39) and the moment the badges pop must never share a sample cell: twenty-four frames slice two seconds at 4.1667% intervals, and when both events land in one cell the "still counting" frame wins as the biggest change. The shipped keyframes seat the numbers at 50% and pop the badges at 58% — an 8% gap, two whole sample cells — so the poster shows the full values.

Second was the grid column of the side rail. Sizing the rail's column at the collapsed 44px meant the unfolding label climbed on top of the article title — the column had to reserve the full unfolded width. In the same file the collapsed cut was 92px, which left 44px visible — past the 40px where the label starts, letting its first letter peek; widening the cut to 100px pushed the visible 36px back inside the 40px and closed it.

Third was the static state. Three of the nine that looked fine in the .is-demo loop came up blank or doubled once the loop was lifted — 09 had two panels visible at once because the class set display: flex and beat [hidden]'s default display: none, 08 kept its scan line on the hand path, and 03 had no icon on the failure notice. An .ee__panel[hidden] { display: none } redeclaration, a default opacity: 0 on the scan line, and an added cross-mark icon fixed each. Measurement and eyeballing only ever see the loop state — that lesson is recorded here.

Last was the sheet backdrop in 06. A linear fade during the rise let a half-raised, clipped sheet win as the poster frame. Making the backdrop switch in one frame with steps(1, end) after the sheet settles turned the whole-card change into the biggest difference, and the poster became the fully raised sheet with all four cells standing. When the count-up's landing-and-popping motion needs real weight behind it, physics simulation works the same problem with springs and inertia.

Accessibility

Color pairs were computed directly with the WCAG 2.1 relative luminance formula rather than picked by feel; the two rows at the bottom are the pairs this palette refuses to use.

Foreground Background Measured Body-text bar
ink #15131a white #ffffff 18.42:1 pass
ink #15131a cream #fdf8f0 17.42:1 pass
ink #15131a sky #7fd4f5 11.09:1 pass
slate #5b5566 white #ffffff 7.15:1 pass
slate #5b5566 cream #fdf8f0 6.76:1 pass
white #ffffff ink #15131a 18.42:1 pass
mist #bdb7c6 ink #15131a 9.43:1 pass
white #ffffff deep blue #0f5f8a 6.94:1 pass
mint #78e0b4 ink #15131a 11.51:1 pass
apricot #ffb3a0 ink #15131a 10.71:1 pass
ink #15131a orange #ff4d1f 5.56:1 pass
white #ffffff orange #ff4d1f 3.32:1 fails — never used
white #ffffff yellow #ffd23f 1.44:1 fails — never used

The nine sets re-rendered with only the passing pairs are in the zip exactly as measured, and the password that opens it is xbet8bb8. Under prefers-reduced-motion: reduce all nine turn off motion and keep state and information. 01 keeps its ring on hover and focus and aria-pressed announcing the pick. 02 stays folded with hover and focus still unfolding labels. 03 reports the copy result through the toast the moment it happens. 04 jumps straight to the declared final values. 05 and 07 sit collapsed while opening still responds instantly. 06 stays open with the focus trap working. 08 keeps the code standing and hides only the scan line. 09 keeps the chosen tab and its panel visible.

The structural devices actually in place: every icon button has an accessible name (text where the label shows, aria-label where only the icon does), the toggle in 01 uses aria-pressed, the openers in 05 and 07 carry aria-expanded with aria-controls, the sheet in 06 is a role="dialog" with aria-modal, a focus trap, and Escape, and the popup in 08 carries the same role. The badges in 04 are decorative aria-hidden with the counts spoken inside each button's accessible name. 09 is a role="tablist" with a roving tabindex so the keyboard alone moves between tabs, and its code field announces that it is read-only. The outcome sentence in 03 lives in a single role="status" with aria-live="polite" so it is read exactly once, in place, for people who cannot see the screen. Copy and dialog guidance follows MDN's Clipboard API docs and the ARIA dialog pattern; contrast against W3C's minimum contrast explanation. For notifications during reading, continue to the toast stack, and for a bar you operate from the bottom of the screen see the floating dock navigation. How this site builds and checks its code is on the about page.

FAQ

How many social media buttons should I keep?

Three or four. Four that each do a different job — link, mail, message, QR — as in 01 is plenty; eight platform logos in a row means none of them get pressed. Where space is tight, fold them behind one button as in 05. Pulling platform names and logos in wholesale is also worth avoiding — using trademarks and brand colors uninvited is borrowing trouble. The hinge on the vessel that folds and rises — QR popup, share sheet — gets its own treatment in modal animation patterns.

How do I know the copy actually happened?

Ask the browser. navigator.clipboard.writeText returns a promise, so receive success and failure separately as in 03 and report the outcome in words, with a cross mark for failure. Clipboards do get silently blocked by private modes and permission settings, so an announcement that assumes success becomes a lie. A fallback for browsers without the API at all costs one more branch.

Can't I just drop the QR in as an image?

You can, but this collection does not. Drawing the module matrix as a grid like 08 means it stays crisp at any size, cannot be tinted by mistake, and needs no extra file to carry around. Whichever way you build it, the rules hold — keep the black-on-white contrast, overlay nothing on top, and give the grid an aria-label naming what it holds — 08 uses "QR code holding the URL" for exactly that.

Enter the archive password

The password is inside this article. You will find it as you read.