Base components
Social button
A sign-in button for a third-party account, with the provider logo and a label or the logo alone, in gray, color or brand style, which eases its colors on hover and gives a small press like Button.
Live preview
Loading preview
A sign-in card with a SocialButtonGroup of Google, Facebook and Apple (gray, full width) above an email field, a row of the same three as logo-only buttons, and the color and brand themes for comparison. The demo draws simple placeholder marks in the logo slot. Replay presses the first button.
Installation
Run this in your app after the one-time setup on the Getting started page.
$ npx @ganisigit/lunia-ds add social-buttonUsage
The demo above, as code.
'use client';
import * as React from 'react';
import { Button } from '../button/button';
import { Input } from '../input/input';
import { SocialButton, SocialButtonGroup } from './social-button';
import type { SocialProvider } from './social-button-variants';
// Neutral placeholder marks: an app passes the provider's official logo instead.
const MARKS: Record<'google' | 'facebook' | 'apple', string> = {
google: 'M8 2.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11Zm0 3a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z',
facebook:
'M4 2.5h8A1.5 1.5 0 0 1 13.5 4v8a1.5 1.5 0 0 1-1.5 1.5H4A1.5 1.5 0 0 1 2.5 12V4A1.5 1.5 0 0 1 4 2.5Z',
apple: 'M8 2.25 14 13.25H2L8 2.25Z',
};
function PlaceholderMark({ provider }: { provider: keyof typeof MARKS }) {
return (
<svg aria-hidden="true" viewBox="0 0 16 16" fill="currentColor" fillRule="evenodd">
<path d={MARKS[provider]} />
</svg>
);
}
const PROVIDERS: (keyof typeof MARKS & SocialProvider)[] = ['google', 'facebook', 'apple'];
export default function SocialButtonDemo({ replay = 0 }: { replay?: number }) {
const [pressed, setPressed] = React.useState(false);
React.useEffect(() => {
if (replay === 0) return;
setPressed(true);
const timer = window.setTimeout(() => setPressed(false), 240);
return () => window.clearTimeout(timer);
}, [replay]);
return (
<div className="flex w-full max-w-sm flex-col gap-8">
<div className="flex flex-col gap-6 rounded-xl border border-secondary bg-primary p-6 shadow-xs">
<div className="flex flex-col gap-1">
<p className="text-lg font-semibold text-primary">Sign in to your account</p>
<p className="text-sm text-tertiary">Use an account you already have, or your email.</p>
</div>
<SocialButtonGroup>
{PROVIDERS.map((provider, index) => (
<SocialButton
key={provider}
provider={provider}
logo={<PlaceholderMark provider={provider} />}
data-pressed={index === 0 && pressed ? '' : undefined}
/>
))}
</SocialButtonGroup>
<div className="flex items-center gap-2 text-sm text-tertiary">
<span className="flex-1 border-t border-secondary" />
or
<span className="flex-1 border-t border-secondary" />
</div>
<div className="flex flex-col gap-4">
<Input label="Email" type="email" placeholder="you@example.com" autoComplete="email" />
<Button>Continue with email</Button>
</div>
</div>
<div className="flex flex-col gap-3">
<p className="text-sm font-medium text-secondary">Logo only</p>
<SocialButtonGroup layout="row">
{PROVIDERS.map((provider) => (
<SocialButton
key={provider}
provider={provider}
iconOnly
logo={<PlaceholderMark provider={provider} />}
/>
))}
</SocialButtonGroup>
</div>
<div className="flex flex-col gap-3">
<p className="text-sm font-medium text-secondary">Color and brand themes</p>
<div className="flex flex-wrap gap-3">
<SocialButton
provider="facebook"
theme="color"
iconOnly
logo={<PlaceholderMark provider="facebook" />}
/>
<SocialButton
provider="facebook"
theme="brand"
iconOnly
logo={<PlaceholderMark provider="facebook" />}
/>
<SocialButton provider="apple" theme="brand" iconOnly logo={<PlaceholderMark provider="apple" />} />
<SocialButton
provider="google"
theme="brand"
iconOnly
logo={<PlaceholderMark provider="google" />}
/>
</div>
</div>
</div>
);
}
API
Parts: root (button or link), logo slot (provider logo, 16 or 20px), label ("Sign in with Google"), group (SocialButtonGroup: layout stack, full-width buttons 12px apart, or layout row, logo buttons 12px apart; label default "Sign in options").
| Prop | Type | Default | Description |
|---|---|---|---|
| provider | "google" | "facebook" | "apple" | "twitter" | "figma" | "dribbble" | undefined | Required. The account provider; sets the default label and the brand colors. |
| theme | "gray" | "color" | "brand" | "gray" | gray: white button with a one-color logo. color: white button with the full-color logo. brand: the provider's own fill with a white label. |
| size | "md" | "lg" | "md" | 40 or 44px tall; Text sm or Text md. |
| iconOnly | boolean | false | Shows only the logo, as a square button; the label becomes the aria-label (Figma Supporting text False). |
| label | string | "Sign in with <provider name>" | Button text, for example "Sign up with Google" on a sign-up page. |
| href | string | undefined | Renders a link (for example to an OAuth start URL). Without href the root is a button. |
| disabled | boolean | false | Turns the button off (50% opacity). |
| Event | Description |
|---|---|
| click | Fires on activation when there is no href. |
| Slot | Description |
|---|---|
| logo | Required. The provider logo the app supplies (an SVG or img, aria-hidden). Lunia ships no brand logos. |
Motion
Every row names a token from the Motion page. The last column says what happens when the user asks for reduced motion.
| ID | Trigger | Element | Property | From → to | Timing | Reduced motion |
|---|---|---|---|---|---|---|
| SCB1 | hover (gray and color themes) | root, label | background-color, color | bg-primary, text-secondary → bg-primary_hover, text-secondary_hover | duration-sm (200)ease-out | Swaps at once |
| SCB2 | press (pointer down or Space held) | root | scale | 1 → scale-press | duration-sm (200)ease-out | Stays still |
| SCB3 | release | root | scale | scale-press → 1 | duration-sm (200)ease-out | Stays still |
| SCB4 | keyboard focus | root | box-shadow | shadow-xs-skeuomorphic → focus ring (2px bg-primary gap, 4px focus-ring) | duration-2xs (120)ease-out | Shows at once |
Accessibility
- Native button, or a link when href is set; the logo is aria-hidden and the label carries the name.
- iconOnly buttons use label as aria-label ("Sign in with Apple"), so each logo button in a row has a different name.
- Use theme gray by default: some brand fills do not reach 4.5:1 contrast with their white label (WCAG 1.4.3). Apple, X and Figma (black, 21:1) pass; Facebook (4.2:1) and Dribbble (3.6:1) do not, so use their brand theme only where the provider requires it.
- Both sizes are 40px or taller, so the target meets the 24 x 24px minimum (WCAG 2.5.8).
- SocialButtonGroup is a div with role group and an aria-label (default "Sign in options").
- Reduced motion: colors swap at once and the button does not scale.