20 CSS Social Buttons08 / 20
Share Button with Live Count Badge
A share control with the running total beside it. The figure uses tabular-nums so digits occupy identical widths and the badge never jitters as the number changes, a short scale pulse marks each increment, and counts above a thousand collapse to 1.2K. State lives in aria-pressed and every change is announced through a polite live region.
Published
The code
<div class="soc-08">
<div class="soc-08__stage">
<div class="soc-08__card">
<p class="soc-08__eyebrow">Engagement</p>
<h2 class="soc-08__title">The state of CSS layout, 2027 edition</h2>
<div class="soc-08__row">
<button class="soc-08__btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="17.5" cy="5.5" r="2.6"/><circle cx="6.5" cy="12" r="2.6"/><circle cx="17.5" cy="18.5" r="2.6"/><path d="m8.9 10.7 6.2-3.6"/><path d="m8.9 13.3 6.2 3.6"/></svg>
<span class="soc-08__label">Share</span>
</button>
<p class="soc-08__badge">
<span class="soc-08__count">1.2K</span>
<span class="soc-08__unit">shares</span>
</p>
</div>
<p class="soc-08__live" role="status" aria-live="polite">1.2K shares</p>
</div>
</div>
</div><div class="soc-08">
<div class="soc-08__stage">
<div class="soc-08__card">
<p class="soc-08__eyebrow">Engagement</p>
<h2 class="soc-08__title">The state of CSS layout, 2027 edition</h2>
<div class="soc-08__row">
<button class="soc-08__btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="17.5" cy="5.5" r="2.6"/><circle cx="6.5" cy="12" r="2.6"/><circle cx="17.5" cy="18.5" r="2.6"/><path d="m8.9 10.7 6.2-3.6"/><path d="m8.9 13.3 6.2 3.6"/></svg>
<span class="soc-08__label">Share</span>
</button>
<p class="soc-08__badge">
<span class="soc-08__count">1.2K</span>
<span class="soc-08__unit">shares</span>
</p>
</div>
<p class="soc-08__live" role="status" aria-live="polite">1.2K shares</p>
</div>
</div>
</div>@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap');
.soc-08 {
--bg: #f4f4f6;
--surface: #ffffff;
--fg: #0e0e12;
--muted: #6a6a76;
--line: #e5e5ea;
--ring: #3d63ff;
--brand: #3d63ff;
--on-brand: #ffffff;
--pulse-scale: 1.18;
--sans: "Geist",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--fg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.soc-08 {
--bg: oklch(96.4% .003 285);
--surface: oklch(100% 0 0);
--fg: oklch(17% .012 285);
--muted: oklch(55% .012 285);
--line: oklch(91.5% .005 285);
--brand: oklch(56% .19 265);
}
}
@media (prefers-color-scheme: dark) {
.soc-08:not([data-theme="light"]) {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
}
.soc-08[data-theme="dark"] {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
.soc-08,
.soc-08 *,
.soc-08 *::before,
.soc-08 *::after {
box-sizing: border-box;
}
.soc-08__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.25rem,5vw,3.5rem);
}
.soc-08__card {
inline-size: min(32rem,100%);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 1.25rem;
padding: clamp(1.5rem,4vw,2.25rem);
box-shadow: 0 1px 2px rgb(0 0 0/.04), 0 18px 40px -28px rgb(9 9 20/.4);
}
.soc-08__eyebrow {
margin: 0 0 .55rem;
font-size: .72rem;
font-weight: 600;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.soc-08__title {
margin: 0 0 1.5rem;
font-size: clamp(1.1rem,1rem + .9vw,1.4rem);
font-weight: 600;
line-height: 1.3;
letter-spacing: -.02em;
text-wrap: balance;
}
.soc-08__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .7rem;
}
.soc-08__btn {
display: inline-flex;
align-items: center;
gap: .55rem;
min-block-size: 2.875rem;
padding: 0 1.15rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: var(--surface);
color: var(--fg);
font: inherit;
font-size: .92rem;
font-weight: 550;
cursor: pointer;
transition: background-color .2s ease, color .2s ease, border-color .2s ease, translate .2s cubic-bezier(.16,1,.3,1);
}
.soc-08__btn svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.soc-08__btn:hover {
translate: 0 -2px;
border-color: color-mix(in oklab,var(--brand) 45%,var(--line));
}
.soc-08__btn:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 3px;
}
.soc-08__btn[aria-pressed="true"] {
background: var(--brand);
border-color: var(--brand);
color: var(--on-brand);
}
.soc-08__badge {
display: inline-flex;
align-items: baseline;
gap: .4rem;
margin: 0;
min-block-size: 2.875rem;
padding: 0 .9rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: color-mix(in oklab,var(--fg) 4%,var(--surface));
align-self: stretch;
}
.soc-08__count {
display: inline-block;
min-inline-size: 3.2ch;
text-align: end;
align-self: center;
font-size: 1.05rem;
font-weight: 700;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
transform-origin: center;
}
.soc-08__count[data-pulse] {
animation: soc-08-pulse .42s cubic-bezier(.16,1,.3,1);
}
@keyframes soc-08-pulse {
0% {
scale: 1;
}
38% {
scale: var(--pulse-scale);
color: var(--brand);
}
100% {
scale: 1;
}
}
.soc-08__unit {
align-self: center;
font-size: .8rem;
color: var(--muted);
}
.soc-08__live {
margin: 1.2rem 0 0;
font-size: .8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
.soc-08__count[data-pulse] {
animation: none;
}
.soc-08__btn {
transition-duration: 1ms;
}
.soc-08__btn:hover {
translate: none;
}
}
@media (forced-colors: active) {
.soc-08__btn {
border: 1px solid ButtonText;
color: ButtonText;
}
.soc-08__btn[aria-pressed="true"] {
background: Highlight;
color: HighlightText;
}
}@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap');
.soc-08 {
--bg: #f4f4f6;
--surface: #ffffff;
--fg: #0e0e12;
--muted: #6a6a76;
--line: #e5e5ea;
--ring: #3d63ff;
--brand: #3d63ff;
--on-brand: #ffffff;
--pulse-scale: 1.18;
--sans: "Geist",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--fg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.soc-08 {
--bg: oklch(96.4% .003 285);
--surface: oklch(100% 0 0);
--fg: oklch(17% .012 285);
--muted: oklch(55% .012 285);
--line: oklch(91.5% .005 285);
--brand: oklch(56% .19 265);
}
}
@media (prefers-color-scheme: dark) {
.soc-08:not([data-theme="light"]) {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
}
.soc-08[data-theme="dark"] {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
.soc-08,
.soc-08 *,
.soc-08 *::before,
.soc-08 *::after {
box-sizing: border-box;
}
.soc-08__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.25rem,5vw,3.5rem);
}
.soc-08__card {
inline-size: min(32rem,100%);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 1.25rem;
padding: clamp(1.5rem,4vw,2.25rem);
box-shadow: 0 1px 2px rgb(0 0 0/.04), 0 18px 40px -28px rgb(9 9 20/.4);
}
.soc-08__eyebrow {
margin: 0 0 .55rem;
font-size: .72rem;
font-weight: 600;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.soc-08__title {
margin: 0 0 1.5rem;
font-size: clamp(1.1rem,1rem + .9vw,1.4rem);
font-weight: 600;
line-height: 1.3;
letter-spacing: -.02em;
text-wrap: balance;
}
.soc-08__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .7rem;
}
.soc-08__btn {
display: inline-flex;
align-items: center;
gap: .55rem;
min-block-size: 2.875rem;
padding: 0 1.15rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: var(--surface);
color: var(--fg);
font: inherit;
font-size: .92rem;
font-weight: 550;
cursor: pointer;
transition: background-color .2s ease, color .2s ease, border-color .2s ease, translate .2s cubic-bezier(.16,1,.3,1);
}
.soc-08__btn svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.soc-08__btn:hover {
translate: 0 -2px;
border-color: color-mix(in oklab,var(--brand) 45%,var(--line));
}
.soc-08__btn:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 3px;
}
.soc-08__btn[aria-pressed="true"] {
background: var(--brand);
border-color: var(--brand);
color: var(--on-brand);
}
.soc-08__badge {
display: inline-flex;
align-items: baseline;
gap: .4rem;
margin: 0;
min-block-size: 2.875rem;
padding: 0 .9rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: color-mix(in oklab,var(--fg) 4%,var(--surface));
align-self: stretch;
}
.soc-08__count {
display: inline-block;
min-inline-size: 3.2ch;
text-align: end;
align-self: center;
font-size: 1.05rem;
font-weight: 700;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
transform-origin: center;
}
.soc-08__count[data-pulse] {
animation: soc-08-pulse .42s cubic-bezier(.16,1,.3,1);
}
@keyframes soc-08-pulse {
0% {
scale: 1;
}
38% {
scale: var(--pulse-scale);
color: var(--brand);
}
100% {
scale: 1;
}
}
.soc-08__unit {
align-self: center;
font-size: .8rem;
color: var(--muted);
}
.soc-08__live {
margin: 1.2rem 0 0;
font-size: .8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
.soc-08__count[data-pulse] {
animation: none;
}
.soc-08__btn {
transition-duration: 1ms;
}
.soc-08__btn:hover {
translate: none;
}
}
@media (forced-colors: active) {
.soc-08__btn {
border: 1px solid ButtonText;
color: ButtonText;
}
.soc-08__btn[aria-pressed="true"] {
background: Highlight;
color: HighlightText;
}
}(() => {
const root = document.querySelector('.soc-08');
if (!root) return;
const btn = root.querySelector('.soc-08__btn');
const out = root.querySelector('.soc-08__count');
const live = root.querySelector('.soc-08__live');
let n = 1247, on = false;
const fmt = (v) => {
if (v < 1000) return String(v);
const s = (v / 1000).toFixed(1);
return (s.endsWith('.0') ? s.slice(0, -2) : s) + 'K';
};
out.addEventListener('animationend', () => { out.removeAttribute('data-pulse'); });
btn.addEventListener('click', () => {
on = !on;
n += on ? 1 : -1;
btn.setAttribute('aria-pressed', String(on));
btn.querySelector('.soc-08__label').textContent = on ? 'Shared' : 'Share';
out.textContent = fmt(n);
out.setAttribute('data-pulse', '');
live.textContent = (on ? 'Shared. ' : 'Share removed. ') + fmt(n) + ' shares';
});
})();(() => {
const root = document.querySelector('.soc-08');
if (!root) return;
const btn = root.querySelector('.soc-08__btn');
const out = root.querySelector('.soc-08__count');
const live = root.querySelector('.soc-08__live');
let n = 1247, on = false;
const fmt = (v) => {
if (v < 1000) return String(v);
const s = (v / 1000).toFixed(1);
return (s.endsWith('.0') ? s.slice(0, -2) : s) + 'K';
};
out.addEventListener('animationend', () => { out.removeAttribute('data-pulse'); });
btn.addEventListener('click', () => {
on = !on;
n += on ? 1 : -1;
btn.setAttribute('aria-pressed', String(on));
btn.querySelector('.soc-08__label').textContent = on ? 'Shared' : 'Share';
out.textContent = fmt(n);
out.setAttribute('data-pulse', '');
live.textContent = (on ? 'Shared. ' : 'Share removed. ') + fmt(n) + ' shares';
});
})();Here's a working CSS Social Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Share Button with Live Count Badge
Source: https://codefronts.com/snippets/css-social-buttons/share-button-with-live-count-badge/
A share control with the running total beside it. The figure uses tabular-nums so digits occupy identical widths and the badge never jitters as the number changes, a short scale pulse marks each increment, and counts above a thousand collapse to 1.2K. State lives in aria-pressed and every change is announced through a polite live region.
## HTML
```html
<div class="soc-08">
<div class="soc-08__stage">
<div class="soc-08__card">
<p class="soc-08__eyebrow">Engagement</p>
<h2 class="soc-08__title">The state of CSS layout, 2027 edition</h2>
<div class="soc-08__row">
<button class="soc-08__btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="17.5" cy="5.5" r="2.6"/><circle cx="6.5" cy="12" r="2.6"/><circle cx="17.5" cy="18.5" r="2.6"/><path d="m8.9 10.7 6.2-3.6"/><path d="m8.9 13.3 6.2 3.6"/></svg>
<span class="soc-08__label">Share</span>
</button>
<p class="soc-08__badge">
<span class="soc-08__count">1.2K</span>
<span class="soc-08__unit">shares</span>
</p>
</div>
<p class="soc-08__live" role="status" aria-live="polite">1.2K shares</p>
</div>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap');
.soc-08 {
--bg: #f4f4f6;
--surface: #ffffff;
--fg: #0e0e12;
--muted: #6a6a76;
--line: #e5e5ea;
--ring: #3d63ff;
--brand: #3d63ff;
--on-brand: #ffffff;
--pulse-scale: 1.18;
--sans: "Geist",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--fg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.soc-08 {
--bg: oklch(96.4% .003 285);
--surface: oklch(100% 0 0);
--fg: oklch(17% .012 285);
--muted: oklch(55% .012 285);
--line: oklch(91.5% .005 285);
--brand: oklch(56% .19 265);
}
}
@media (prefers-color-scheme: dark) {
.soc-08:not([data-theme="light"]) {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
}
.soc-08[data-theme="dark"] {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
.soc-08,
.soc-08 *,
.soc-08 *::before,
.soc-08 *::after {
box-sizing: border-box;
}
.soc-08__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.25rem,5vw,3.5rem);
}
.soc-08__card {
inline-size: min(32rem,100%);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 1.25rem;
padding: clamp(1.5rem,4vw,2.25rem);
box-shadow: 0 1px 2px rgb(0 0 0/.04), 0 18px 40px -28px rgb(9 9 20/.4);
}
.soc-08__eyebrow {
margin: 0 0 .55rem;
font-size: .72rem;
font-weight: 600;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.soc-08__title {
margin: 0 0 1.5rem;
font-size: clamp(1.1rem,1rem + .9vw,1.4rem);
font-weight: 600;
line-height: 1.3;
letter-spacing: -.02em;
text-wrap: balance;
}
.soc-08__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .7rem;
}
.soc-08__btn {
display: inline-flex;
align-items: center;
gap: .55rem;
min-block-size: 2.875rem;
padding: 0 1.15rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: var(--surface);
color: var(--fg);
font: inherit;
font-size: .92rem;
font-weight: 550;
cursor: pointer;
transition: background-color .2s ease, color .2s ease, border-color .2s ease, translate .2s cubic-bezier(.16,1,.3,1);
}
.soc-08__btn svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.soc-08__btn:hover {
translate: 0 -2px;
border-color: color-mix(in oklab,var(--brand) 45%,var(--line));
}
.soc-08__btn:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 3px;
}
.soc-08__btn[aria-pressed="true"] {
background: var(--brand);
border-color: var(--brand);
color: var(--on-brand);
}
.soc-08__badge {
display: inline-flex;
align-items: baseline;
gap: .4rem;
margin: 0;
min-block-size: 2.875rem;
padding: 0 .9rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: color-mix(in oklab,var(--fg) 4%,var(--surface));
align-self: stretch;
}
.soc-08__count {
display: inline-block;
min-inline-size: 3.2ch;
text-align: end;
align-self: center;
font-size: 1.05rem;
font-weight: 700;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
transform-origin: center;
}
.soc-08__count[data-pulse] {
animation: soc-08-pulse .42s cubic-bezier(.16,1,.3,1);
}
@keyframes soc-08-pulse {
0% {
scale: 1;
}
38% {
scale: var(--pulse-scale);
color: var(--brand);
}
100% {
scale: 1;
}
}
.soc-08__unit {
align-self: center;
font-size: .8rem;
color: var(--muted);
}
.soc-08__live {
margin: 1.2rem 0 0;
font-size: .8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
.soc-08__count[data-pulse] {
animation: none;
}
.soc-08__btn {
transition-duration: 1ms;
}
.soc-08__btn:hover {
translate: none;
}
}
@media (forced-colors: active) {
.soc-08__btn {
border: 1px solid ButtonText;
color: ButtonText;
}
.soc-08__btn[aria-pressed="true"] {
background: Highlight;
color: HighlightText;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.soc-08');
if (!root) return;
const btn = root.querySelector('.soc-08__btn');
const out = root.querySelector('.soc-08__count');
const live = root.querySelector('.soc-08__live');
let n = 1247, on = false;
const fmt = (v) => {
if (v < 1000) return String(v);
const s = (v / 1000).toFixed(1);
return (s.endsWith('.0') ? s.slice(0, -2) : s) + 'K';
};
out.addEventListener('animationend', () => { out.removeAttribute('data-pulse'); });
btn.addEventListener('click', () => {
on = !on;
n += on ? 1 : -1;
btn.setAttribute('aria-pressed', String(on));
btn.querySelector('.soc-08__label').textContent = on ? 'Shared' : 'Share';
out.textContent = fmt(n);
out.setAttribute('data-pulse', '');
live.textContent = (on ? 'Shared. ' : 'Share removed. ') + fmt(n) + ' shares';
});
})();
```Here's a working CSS Social Button from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Share Button with Live Count Badge
Source: https://codefronts.com/snippets/css-social-buttons/share-button-with-live-count-badge/
A share control with the running total beside it. The figure uses tabular-nums so digits occupy identical widths and the badge never jitters as the number changes, a short scale pulse marks each increment, and counts above a thousand collapse to 1.2K. State lives in aria-pressed and every change is announced through a polite live region.
## HTML
```html
<div class="soc-08">
<div class="soc-08__stage">
<div class="soc-08__card">
<p class="soc-08__eyebrow">Engagement</p>
<h2 class="soc-08__title">The state of CSS layout, 2027 edition</h2>
<div class="soc-08__row">
<button class="soc-08__btn" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="17.5" cy="5.5" r="2.6"/><circle cx="6.5" cy="12" r="2.6"/><circle cx="17.5" cy="18.5" r="2.6"/><path d="m8.9 10.7 6.2-3.6"/><path d="m8.9 13.3 6.2 3.6"/></svg>
<span class="soc-08__label">Share</span>
</button>
<p class="soc-08__badge">
<span class="soc-08__count">1.2K</span>
<span class="soc-08__unit">shares</span>
</p>
</div>
<p class="soc-08__live" role="status" aria-live="polite">1.2K shares</p>
</div>
</div>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap');
.soc-08 {
--bg: #f4f4f6;
--surface: #ffffff;
--fg: #0e0e12;
--muted: #6a6a76;
--line: #e5e5ea;
--ring: #3d63ff;
--brand: #3d63ff;
--on-brand: #ffffff;
--pulse-scale: 1.18;
--sans: "Geist",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--fg);
font-family: var(--sans);
-webkit-font-smoothing: antialiased;
}
@supports (color: oklch(50% 0 0)) {
.soc-08 {
--bg: oklch(96.4% .003 285);
--surface: oklch(100% 0 0);
--fg: oklch(17% .012 285);
--muted: oklch(55% .012 285);
--line: oklch(91.5% .005 285);
--brand: oklch(56% .19 265);
}
}
@media (prefers-color-scheme: dark) {
.soc-08:not([data-theme="light"]) {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
}
.soc-08[data-theme="dark"] {
--bg: #0a0a0d;
--surface: #131318;
--fg: #f2f2f5;
--muted: #9a9aa6;
--line: #26262e;
--brand: #7c9bff;
--on-brand: #0b0b12;
}
.soc-08,
.soc-08 *,
.soc-08 *::before,
.soc-08 *::after {
box-sizing: border-box;
}
.soc-08__stage {
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
padding: clamp(1.25rem,5vw,3.5rem);
}
.soc-08__card {
inline-size: min(32rem,100%);
background: var(--surface);
border: 1px solid var(--line);
border-radius: 1.25rem;
padding: clamp(1.5rem,4vw,2.25rem);
box-shadow: 0 1px 2px rgb(0 0 0/.04), 0 18px 40px -28px rgb(9 9 20/.4);
}
.soc-08__eyebrow {
margin: 0 0 .55rem;
font-size: .72rem;
font-weight: 600;
letter-spacing: .12em;
text-transform: uppercase;
color: var(--muted);
}
.soc-08__title {
margin: 0 0 1.5rem;
font-size: clamp(1.1rem,1rem + .9vw,1.4rem);
font-weight: 600;
line-height: 1.3;
letter-spacing: -.02em;
text-wrap: balance;
}
.soc-08__row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .7rem;
}
.soc-08__btn {
display: inline-flex;
align-items: center;
gap: .55rem;
min-block-size: 2.875rem;
padding: 0 1.15rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: var(--surface);
color: var(--fg);
font: inherit;
font-size: .92rem;
font-weight: 550;
cursor: pointer;
transition: background-color .2s ease, color .2s ease, border-color .2s ease, translate .2s cubic-bezier(.16,1,.3,1);
}
.soc-08__btn svg {
inline-size: 1.15rem;
block-size: 1.15rem;
}
.soc-08__btn:hover {
translate: 0 -2px;
border-color: color-mix(in oklab,var(--brand) 45%,var(--line));
}
.soc-08__btn:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 3px;
}
.soc-08__btn[aria-pressed="true"] {
background: var(--brand);
border-color: var(--brand);
color: var(--on-brand);
}
.soc-08__badge {
display: inline-flex;
align-items: baseline;
gap: .4rem;
margin: 0;
min-block-size: 2.875rem;
padding: 0 .9rem;
border: 1px solid var(--line);
border-radius: .85rem;
background: color-mix(in oklab,var(--fg) 4%,var(--surface));
align-self: stretch;
}
.soc-08__count {
display: inline-block;
min-inline-size: 3.2ch;
text-align: end;
align-self: center;
font-size: 1.05rem;
font-weight: 700;
letter-spacing: -.02em;
font-variant-numeric: tabular-nums;
transform-origin: center;
}
.soc-08__count[data-pulse] {
animation: soc-08-pulse .42s cubic-bezier(.16,1,.3,1);
}
@keyframes soc-08-pulse {
0% {
scale: 1;
}
38% {
scale: var(--pulse-scale);
color: var(--brand);
}
100% {
scale: 1;
}
}
.soc-08__unit {
align-self: center;
font-size: .8rem;
color: var(--muted);
}
.soc-08__live {
margin: 1.2rem 0 0;
font-size: .8rem;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
.soc-08__count[data-pulse] {
animation: none;
}
.soc-08__btn {
transition-duration: 1ms;
}
.soc-08__btn:hover {
translate: none;
}
}
@media (forced-colors: active) {
.soc-08__btn {
border: 1px solid ButtonText;
color: ButtonText;
}
.soc-08__btn[aria-pressed="true"] {
background: Highlight;
color: HighlightText;
}
}
```
## JavaScript
```js
(() => {
const root = document.querySelector('.soc-08');
if (!root) return;
const btn = root.querySelector('.soc-08__btn');
const out = root.querySelector('.soc-08__count');
const live = root.querySelector('.soc-08__live');
let n = 1247, on = false;
const fmt = (v) => {
if (v < 1000) return String(v);
const s = (v / 1000).toFixed(1);
return (s.endsWith('.0') ? s.slice(0, -2) : s) + 'K';
};
out.addEventListener('animationend', () => { out.removeAttribute('data-pulse'); });
btn.addEventListener('click', () => {
on = !on;
n += on ? 1 : -1;
btn.setAttribute('aria-pressed', String(on));
btn.querySelector('.soc-08__label').textContent = on ? 'Shared' : 'Share';
out.textContent = fmt(n);
out.setAttribute('data-pulse', '');
live.textContent = (on ? 'Shared. ' : 'Share removed. ') + fmt(n) + ' shares';
});
})();
```How this works
Tabular figures are why it looks stable. In a proportional font the digit 1 is narrower than 8, so a counter ticking from 1,199 to 1,200 physically resizes and shoves the layout beside it. One declaration fixes it for good:
.soc-08__count{ font-variant-numeric:tabular-nums; }The pulse is a one-shot animation, not a class dance. Incrementing sets a data attribute; the keyframe runs once and an animationend listener removes the attribute so the next increment can retrigger it. No timers, no double-buffering, and nothing left in the DOM once the animation finishes.
Visual change is not an accessible event. A number swapping in place fires nothing for a screen reader. The button carries aria-pressed so its state is exposed, and a role="status" region announces the new total in words — the state is never carried by colour or motion alone.
The trap: reserving space for the badge. If the count arrives from an API after paint, the badge appears and pushes the button sideways. Give the badge a min-inline-size sized for your realistic maximum so the layout is stable before the first byte of data arrives.
Make it yours
- Change the starting figure and the compact threshold in the formatter — swap 1000 for 10000 if you prefer full numbers longer.
- Set
--pulse-scaleto tune how emphatic the increment feels. - Move the badge inside the button instead of beside it by moving one element in the markup.
- Restyle the badge as a pill, a superscript, or a stacked block with a label underneath.
- Feed the count from your API on load — the formatter and pulse are already decoupled from the click handler.
- Remove the toggle behaviour and make the control fire-and-forget if your backend does not support un-sharing.
Gotchas — read before shipping
- Without
tabular-numsthe badge visibly twitches on every increment, which is the single detail that makes a share counter feel cheap. - Removing and re-adding an animation attribute in the same frame does not restart the animation; wait for
animationendor force a reflow. - A count that changes with no live region is silent to assistive technology — the visual pulse communicates nothing to a screen reader.
- Sharing counts fetched per page view are a real performance cost; cache them server-side rather than firing a request per social channel.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.
font-variant-numeric and CSS animations are universal; the stated floor is color-mix() for the tinted surfaces, which falls back to the hex tokens declared first.