Social Button

Sign-in and connect buttons for identity providers and workspace connectors. The provider's mark is handed in as an element rather than named by a provider enum, so a page only ships the marks it actually uses. The system ships no third-party logos — a provider's mark is its owner's trademark, so it comes from that provider's own brand kit — and the demos here use lucide icons and one abstract stand-in. A provider mark keeps its own colours, while every part of the chrome around it (surface, radius, focus ring, press motion, disabled treatment) comes from our tokens through Button. There is no brand-filled surface variant on purpose: a provider-coloured surface would need a raw colour, and it would stand a second full-weight CTA next to the form's real lime submit button.

tsx
import { SocialButton, SocialButtonGroup } from "@pestoui/core"

Providers

Pass the provider's mark as icon and the label as children. The mark is sized, hidden from assistive tech and toned by the button, so it goes in unstyled — a provider's own SVG from its brand kit, a lucide icon or a bare <svg>.

Tone

color is the default and the canonical auth look: a provider's mark keeps its own palette on a neutral surface. mono collapses it to a single-colour silhouette in the foreground text colour — for dense connector lists and toolbars where a row of full-colour logos would fight each other. The button passes the mark monochrome, so the mark has to support it; the two-tone stand-in here does, the way a provider's mark component would. A lucide icon already follows the text colour in both tones.

Chrome

outline is the default — a hairline and a card surface, so the button holds its shape against the form around it. subtle drops the hairline and the surface and keeps only the hover fill, for secondary rows and menus where a stack of outlines would read as a table.

Sizes

Four heights, matching Button's own. The mark is re-sized by injection rather than by a descendant rule, because a brand mark usually ships with its own size class and would otherwise render at its intrinsic size inside a smaller box.

Icon only

Drops the label for a square squircle button — a compact row of sign-in or connect targets. The type is discriminated, so iconOnly will not compile without an aria-label: there is no visible text left to name the control. xl clamps to the largest square Button offers rather than restating a radius from the outside.

Group

SocialButtonGroup spaces a set of independent auth targets. It is deliberately not ButtonGroup — that wrapper collapses shared borders into one seam, which reads as a single segmented control, and these are separate destinations that each need their own edge. Vertical and stretched is the default, which is what a sign-in column wants; horizontal splits the track evenly, and stretch={false} leaves each child at its natural width for an icon-only row.

In a sign-in form

The realistic shape: a stretched provider stack above a divider, the email path below it, and exactly one lime --primary button at the bottom. Providers stay neutral so the submit button keeps the only full-weight CTA on the screen. A provider handoff is a navigation rather than a form submit, so asChild renders each provider as an <a> — the surface, focus ring and press motion survive the swap, and the disabled attribute is exchanged for its aria form, which is the one a link can carry.

or sign in with email

Redirect in flight

loading disables the control and cross-fades the label out for a spinner. The spinner is stacked over the label rather than replacing it, so the box never reflows as the redirect starts and a stretched column does not jump. aria-busy alone is not announced on a control that has just left the tab order, so loadingLabel is spoken through a polite live region instead. Press either button.

Props

PropTypeDefaultDescription
iconSocialButtonMark—Provider mark. Sized, hidden from assistive tech and, with tone mono, switched to monochrome by the component — pass it unstyled. A provider's own SVG, a lucide icon or a bare <svg> satisfies it; the system ships no third-party logos.
childrenReactNode—The label. Required unless iconOnly is set; truncates rather than widening the button.
variant"outline" | "subtle""outline"Button chrome. subtle drops the hairline and the card surface, keeping the hover fill.
tone"color" | "mono""color"How the mark renders — its own colours, or a single-colour silhouette in the foreground text colour. mono passes the mark monochrome, so pair it with marks that take that prop.
size"sm" | "default" | "lg" | "xl""default"Control height. Also sets the mark size, and picks the matching square size when iconOnly is set.
iconOnlybooleanfalseDrops the label for a square button. Requires aria-label and cannot be combined with asChild.
aria-labelstring—Accessible name. Required by the type when iconOnly is set, since no visible label remains.
loadingbooleanfalseRedirect in flight: disables the control and swaps the label for a spinner without letting the box change width.
loadingLabelstring"Signing in"Announced in a polite live region while loading.
asChildbooleanfalseRender as the passed child, e.g. an <a> for a sign-in redirect. Not available alongside iconOnly.
SocialButtonGroup orientation"vertical" | "horizontal""vertical"Stack direction. Horizontal wraps; vertical is the sign-in default.
SocialButtonGroup stretchbooleantrueStretch every child to fill the track — equal widths across a row, full width down a column.

Pesto UI · 117 documented components