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.
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.
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
Pesto UI · 117 documented components