ShareButtons
Share links in pill, floating-sidebar and sticky-bottom styles.
On this page
ShareButtons renders a "Share" label and one button per network. Facebook, X, LinkedIn, WhatsApp and email open a share link; nativeShare opens the device's share sheet through the Web Share API. Payload's single-post template renders it from the General Settings share options, and you can use it on any page.
import { ShareButtons } from '@systhemaui/next'
export default function Demo() {
return (
<div className="w-full max-w-md">
<ShareButtons
title="Designing with tokens from day one"
url="https://example.com/journal/designing-with-tokens"
networks={['facebook', 'x', 'linkedin', 'email']}
/>
</div>
)
}ImportLink to this section
import { ShareButtons } from '@systhemaui/next'In a React app without Next.js, import it from @systhemaui/react.
NetworksLink to this section
networks lists the buttons in order. An empty array renders nothing.
| Network | Opens | New tab |
|---|---|---|
facebook | Facebook's share dialog with the URL | yes |
x | An X post with the URL and title | yes |
linkedin | LinkedIn's share dialog with the URL | yes |
whatsapp | WhatsApp with title and the URL as the message | yes |
email | A mailto: link with title as the subject, URL as body | no |
nativeShare | The Web Share API sheet with title and the URL | no |
url defaults to the current page (window.location.href), read after hydration; until then the link buttons are not rendered, so the server HTML never contains a share link with an empty target. The nativeShare button only appears in browsers that support navigator.share. Support is good on mobile and patchy on desktop, so pair it with link networks.
Button stylesLink to this section
buttonStyle sets the look of the buttons: icon (default, round icon buttons), icon-label (icon and network name) or pills (bordered pills with icon and name). It is called buttonStyle so it does not clash with React's style prop.
import { ShareButtons } from '@systhemaui/next'
export default function Demo() {
return (
<div className="flex w-full max-w-xl flex-col gap-8">
<ShareButtons
title="Designing with tokens from day one"
url="https://example.com/journal/designing-with-tokens"
networks={['facebook', 'linkedin', 'email']}
buttonStyle="icon-label"
/>
<ShareButtons
title="Designing with tokens from day one"
url="https://example.com/journal/designing-with-tokens"
networks={['facebook', 'linkedin', 'email']}
buttonStyle="pills"
/>
</div>
)
}PositionLink to this section
position pins the bar:
inline(default): in the page flow, label on the left and buttons on the right.sticky-bottom: a full-width bar fixed to the bottom of the viewport, on the surface background with a top border.floating-sidebar: a vertical rail fixed in the left gutter, vertically centered, fromlgup. Belowlgit stays in the page flow as a vertical stack.
Both pinned positions use position: fixed, not sticky, so the bar escapes the centered container it is rendered in. Leave room at the bottom of the page for a sticky-bottom bar so it does not cover the last lines.
import { Paragraph, ShareButtons } from '@systhemaui/next'
export default function Demo() {
return (
<div className="flex min-h-80 w-full max-w-xl flex-col gap-4 pb-24">
<Paragraph>The share bar stays at the bottom of the viewport while the post scrolls.</Paragraph>
<ShareButtons
title="Designing with tokens from day one"
url="https://example.com/journal/designing-with-tokens"
networks={['facebook', 'x', 'linkedin', 'email']}
position="sticky-bottom"
/>
</div>
)
}import { Paragraph, ShareButtons } from '@systhemaui/next'
export default function Demo() {
return (
<div className="flex min-h-80 w-full max-w-xl flex-col gap-4">
<Paragraph>From the lg breakpoint up, the share rail floats in the left gutter, outside the reading column.</Paragraph>
<ShareButtons
title="Designing with tokens from day one"
url="https://example.com/journal/designing-with-tokens"
networks={['facebook', 'x', 'linkedin', 'email']}
position="floating-sidebar"
/>
</div>
)
}ExamplesLink to this section
Translating the labelsLink to this section
labels sets the heading (default "Share"), each network's accessible name (default "Share on Facebook" and so on) and opensInNewTab, the cue added in parentheses to every link that opens a new tab. Pass opensInNewTab without parentheses. Payload's single-post template fills these from the frontend message catalog.
<ShareButtons
title={post.title}
networks={['facebook', 'linkedin', 'email']}
labels={{
heading: 'Megosztás',
facebook: 'Megosztás Facebookon',
linkedin: 'Megosztás LinkedInen',
email: 'Küldés e-mailben',
opensInNewTab: 'új lapon nyílik meg',
}}
/>Your own iconsLink to this section
The buttons show built-in SVG glyphs. renderNetwork(network, label) replaces a button's content, for example with your icon set. It is a function prop, so pass it from a client module.
'use client'
import { ShareButtons } from '@systhemaui/next'
import { BrandIcon } from '@/components/BrandIcon'
export function PostShare({ title }: { title: string }) {
return (
<ShareButtons
title={title}
networks={['facebook', 'x', 'linkedin']}
renderNetwork={(network) => <BrandIcon name={network} aria-hidden="true" />}
/>
)
}PropsLink to this section
| Prop | Type | Default | Description |
|---|---|---|---|
url | string | - | Absolute URL of the page being shared. Optional: when omitted, the component resolves the current page URL (window.location.href) on the client — which is exactly the post URL for a single-post page. |
title (required) | string | - | Title used by email/whatsapp/native share. |
networks (required) | ShareNetwork[] | - | Networks to render, in order. Empty ⇒ nothing renders. |
className | string | - | |
labels | (Partial<Record<ShareNetwork, string>> & { heading?: string | undefined; opensInNewTab?: string | undefined; }) | - | Accessible labels (override for i18n). opensInNewTab is the suffix folded into the accessible name of every network that opens a share intent in a new tab; it is rendered in parentheses, so pass it without them ("opens in new tab", "új lapon nyílik meg"). |
renderNetwork | ((network: ShareNetwork, label: string) => ReactNode) | - | Render-prop for a network's inner content (e.g. an icon). Defaults to the network label text so the component stays presentational-icon-agnostic. |
position | ShareButtonsPosition | 'inline' | Where the share bar sits. 'inline' (default) is the in-flow row; the others pin it via CSS hooks (.post-share-{position} in core posts.ts): 'sticky-bottom' = a sticky bar at the viewport bottom; 'floating-sidebar' = a vertical sticky rail beside the reading column (collapses to inline below md). |
buttonStyle | ShareButtonsStyle | 'icon' | Button appearance: 'icon' (default, icon-only round buttons), 'icon-label' (icon + the network name), or 'pills' (labeled pill buttons). Drives the .post-share-{buttonStyle} hook + reveals the per-button label span. (Named buttonStyle to avoid clashing with the React style prop; the General- Settings editor field is style.) |
HTML and CSSLink to this section
ShareButtons is built from Stack, the text-label type and Icon, so it has no CSS of its own except the label, style and position hooks (.post-share-button-label, .post-share-icon-label, .post-share-pills, .post-share-sticky-bottom, .post-share-floating-sidebar) in the posts CSS layer.
<div class="stack flex flex-row items-center justify-between gap-md post-share post-share-pills">
<span class="post-share-heading text-label color-label">Share</span>
<div class="stack flex flex-row items-center gap-sm post-share-list">
<a
class="icon"
href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fexample.com%2Fpost"
target="_blank"
rel="noopener noreferrer"
aria-label="Share on Facebook (opens in new tab)"
>
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">…</svg>
<span class="post-share-button-label">Share on Facebook</span>
</a>
</div>
</div>| Class | Styles |
|---|---|
posts-list | width: 100%;max-width: var(--container-width);margin-inline: auto; |
posts-list-grid | & { display: grid; grid-template-columns: minmax(0, 1fr); column-gap: var(--gap-sm, 1.25rem); row-gap: var(--gap-md, 2.5rem); }& > .post-card { animation-delay: var(--post-card-stagger-delay, 0ms); }& > * > .post-card { animation-delay: var(--post-card-stagger-delay, 0ms); } |
posts-list-row | display: flex;flex-direction: column;row-gap: var(--gap-md, 2.5rem); |
posts-list-grid-2 | @media (width >= 640px) { & { grid-template-columns: repeat(2, minmax(0, 1fr)); } }@media (width >= 640px) { & > :nth-child(2n - 1) { --post-card-stagger-delay: 0ms; } }@media (width >= 640px) { & > :nth-child(2n) { --post-card-stagger-delay: calc(var(--tw-stagger-delay, 120ms) * 1); } } |
posts-list-grid-3 | @media (width >= 640px) { & { grid-template-columns: repeat(2, minmax(0, 1fr)); } }@media (width >= 1192px) { & { grid-template-columns: repeat(3, minmax(0, 1fr)); } }@media (width >= 640px) and (width < 1192px) { & > :nth-child(2n - 1) { --post-card-stagger-delay: 0ms; } }@media (width >= 640px) and (width < 1192px) { & > :nth-child(2n) { --post-card-stagger-delay: calc(var(--tw-stagger-delay, 120ms) * 1); } }+ 3 more rules |
posts-list-grid-4 | @media (width >= 640px) { & { grid-template-columns: repeat(2, minmax(0, 1fr)); } }@media (width >= 1192px) { & { grid-template-columns: repeat(4, minmax(0, 1fr)); } }@media (width >= 640px) and (width < 1192px) { & > :nth-child(2n - 1) { --post-card-stagger-delay: 0ms; } }@media (width >= 640px) and (width < 1192px) { & > :nth-child(2n) { --post-card-stagger-delay: calc(var(--tw-stagger-delay, 120ms) * 1); } }+ 4 more rules |
posts-list-interspace | width: 100%; |
posts-list-empty | color: color-mix(in srgb, currentColor 60%, transparent);text-align: center;padding-block: var(--gap-md, 2.5rem); |
posts-list-actions | display: flex;align-items: center;justify-content: center;gap: var(--gap-sm, 1.25rem); |
posts-list-load-more | &:disabled, .posts-list-load-more[disabled] { opacity: 0.4; cursor: not-allowed; } |
posts-list-pagination | & { display: flex; justify-content: center; align-items: center; gap: var(--gallery-navigation-gap-x, 10px); }+ 13 more rules |
post-card | & { color: var(--color-typography-body); }.posts-list-grid > .post-card { animation-delay: var(--post-card-stagger-delay, 0ms); }.posts-list-grid > * > .post-card { animation-delay: var(--post-card-stagger-delay, 0ms); } |
post-card-label | & { color: color-mix(in srgb, currentColor 60%, transparent); }&.text-label { color: color-mix(in srgb, currentColor 60%, transparent); } |
post-highlight-label | & { color: color-mix(in srgb, currentColor 60%, transparent); }&.text-label { color: color-mix(in srgb, currentColor 60%, transparent); } |
post-type | color: color-mix(in srgb, currentColor 60%, transparent); |
text-label | & { font-family: var(--font-label-font-family); font-size: var(--typography-label-font-size); font-weight: var(--font-label-font-weight); letter-spacing: var(--typography-label-letter-spacing); line-height: var(--typography-label-line-height); text-transform: uppercase; text-decoration: none; font-style: var(--font-label-font-style); }.post-card-label.text-label { color: color-mix(in srgb, currentColor 60%, transparent); }+ 1 more rule |
post-card-title | text-wrap: balance; |
post-card-read-more | color: var(--color-link-text-default, currentColor);font-weight: 600;text-decoration-line: underline; |
post-highlight-read-more | color: var(--color-link-text-default, currentColor);font-weight: 600;text-decoration-line: underline; |
post-card-media-placeholder | width: 100%;height: 100%;background-color: color-mix(in srgb, currentColor 6%, transparent); |
post-card-row | @media (width >= 640px) { & .post-card-body { align-self: start; justify-content: flex-start; } } |
post-card-body | @media (width >= 640px) { .post-card-row .post-card-body { align-self: start; justify-content: flex-start; } } |
post-highlight | position: relative;overflow: hidden;border-radius: var(--card-default-border-radius, 12px); |
highlight-card | position: relative;overflow: hidden;border-radius: var(--card-default-border-radius, 12px); |
post-highlight-scrim | position: absolute;inset: 0;background: linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.35) 35%, rgba(0,0,0,0) 70%);pointer-events: none; |
post-highlight-overlay | position: relative;z-index: 1; |
post-highlight-excerpt | color: color-mix(in srgb, #ffffff 85%, transparent); |
post-meta | display: flex;align-items: center;flex-wrap: wrap;gap: 0.5em;color: color-mix(in srgb, currentColor 60%, transparent); |
post-byline-meta | display: flex;align-items: center;flex-wrap: wrap;gap: 0.5em;color: color-mix(in srgb, currentColor 60%, transparent); |
post-meta-avatar | width: var(--post-byline-avatar-size, 1.75em);height: var(--post-byline-avatar-size, 1.75em);border-radius: 9999px;object-fit: cover;flex-shrink: 0; |
post-meta-separator | opacity: 0.5; |
post-meta-author-role | & { opacity: 0.7; }&::before { content: ", "; } |
archive-filter | display: flex;flex-direction: column;gap: var(--gap-sm, 1.25rem); |
archive-filter-bar | display: flex;flex-wrap: wrap;align-items: center;gap: var(--gap-sm, 1.25rem); |
archive-filter-label | flex: 0 0 auto;color: color-mix(in srgb, currentColor 60%, transparent); |
archive-filter-search | flex: 1 1 16rem;min-width: 0;margin: 0; |
archive-filter-select | flex: 1 1 16rem;min-width: 0;margin: 0; |
archive-filter-chips | display: flex;flex-wrap: wrap;align-items: center;gap: var(--chip-gap, 0.5rem);flex: 1 1 16rem;min-width: 0; |
archive-filter-chip-toggle | cursor: pointer;appearance: none; |
archive-filter-chip-toggle-active | background-color: var(--color-chip-hover-background);border-color: var(--color-chip-hover-border);color: var(--color-chip-hover-text); |
archive-filter-active | display: flex;flex-wrap: wrap;align-items: center;gap: var(--chip-gap, 0.5rem); |
archive-filter-chip | & { appearance: none; cursor: pointer; display: inline-flex; align-items: center; gap: var(--chip-gap); font-size: var(--chip-font-size); line-height: var(--chip-line-height); letter-spacing: calc(var(--chip-letter-spacing, 0) * 1px); padding-inline: var(--chip-padding-x); padding-block: var(--chip-padding-y); border-radius: var(--chip-border-radius); border-style: solid; border-width: var(--chip-border-width); background-color: var(--color-chip-normal-background); border-co…+ 1 more rule |
archive-filter-chip-remove | font-size: 1.2em;line-height: 1;opacity: 0.7; |
post-related-title | margin-bottom: var(--gap-sm, 1.25rem); |
post-single | & .article > :not(section, .figure-w-full, .figure-w-screen, .figure-w-container, a:not([class*="card"]), div:not(.columns, .stack, [class*="card"], [class*="accordion"])) { max-width: var(--post-reading-width, calc(var(--container-width) - (var(--article-padding-x) * 2))); } |
post-share-button-label | font-size: var(--text-small-font-size, 0.875rem);line-height: 1; |
post-share-icon-label | & .post-share-list > a { display: inline-flex; align-items: center; gap: var(--gap-xs, 0.5rem); width: auto; }& .post-share-list > button { display: inline-flex; align-items: center; gap: var(--gap-xs, 0.5rem); width: auto; } |
post-share-list | .post-share-icon-label .post-share-list > a { display: inline-flex; align-items: center; gap: var(--gap-xs, 0.5rem); width: auto; }.post-share-icon-label .post-share-list > button { display: inline-flex; align-items: center; gap: var(--gap-xs, 0.5rem); width: auto; }.post-share-pills .post-share-list > a { display: inline-flex; align-items: center; gap: var(--gap-xs, 0.5rem); width: auto; }+ 3 more rules |
post-share-pills | & .post-share-list > a { display: inline-flex; align-items: center; gap: var(--gap-xs, 0.5rem); width: auto; }& .post-share-list > button { display: inline-flex; align-items: center; gap: var(--gap-xs, 0.5rem); width: auto; }& .post-share-list > a { padding: var(--gap-xs, 0.5rem) var(--gap-sm, 1.25rem); border-radius: 9999px; border: 1px solid var(--color-separator-line, currentColor); }+ 1 more rule |
post-share-sticky-bottom | position: fixed;left: 0;right: 0;bottom: 0;z-index: 40;justify-content: center;background-color: var(--color-foundations-surface-bg);border-top: 1px solid var(--color-separator-line, currentColor);padding-block: var(--gap-sm, 1.25rem); |
post-share-floating-sidebar | @media (width >= 1192px) { & { position: fixed; left: var(--gap-lg, 1.5rem); top: 50%; transform: translateY(-50%); flex-direction: column; align-items: center; z-index: 40; } } |
Next.jsLink to this section
@systhemaui/next re-exports ShareButtons from @systhemaui/react unchanged. It is a client component.
AccessibilityLink to this section
- Every button has an accessible name: the network label, plus "(opens in new tab)" for links that open one. The icons are
aria-hiddenwithfocusable="false". - In the
icon-labelandpillsstyles the visible text matches the start of the accessible name, so voice-control users can say what they see. - New-tab links carry
rel="noopener noreferrer".