- Accordion
- Avatar
- Badge
- Breadcrumb
- Button
- Calendar
- Checkbox
- Combobox
- Container
- CurrencyInput
- DistributionSlider
- Drawer
- Dropdown
- Grid
- Heading
- Image
- Input
- InputGroup
- Label
- Logo
- MapPin
- Modal
- NativeSelect
- NumberInput
- OtpInput
- PhoneInput
- Popover
- Progress
- PropertyCalendar
- RadioGroup
- RadioGroupCards
- ResponsiveModal
- ScrollArea
- SearchBar
- SearchBarFallback
- SearchInput
- Select
- Separator
- Spinner
- Switch
- Tabs
- Text
- Textarea
- Toast
- Toggle
- ToggleGroup
- Tooltip
LoginModal
Login and signup modal with email, OTP verification, and social authentication options.
pnpm add @wandercom/design-system-web
Loading example...
import { LoginModal } from '@wandercom/design-system-web/blocks/login-modal';
import { Button } from '@wandercom/design-system-web/ui/button';
export function Example() {
return (
<LoginModal
trigger={<Button>Log in</Button>}
onEmailSubmit={(email) => sendOtp(email)}
onCodeSubmit={(code) => verifyCode(code)}
onResendCode={() => resendOtp()}
onGoogleLogin={() => signInWithGoogle()}
onAppleLogin={() => signInWithApple()}
onFacebookLogin={() => signInWithFacebook()}
onPhoneSubmit={(phone) => sendOtp(phone)}
/>
);
}Submitting an email transitions directly to a 6-digit OTP verification step. Clicking the phone button first prompts for a phone number, then transitions to OTP verification. The verification description adapts based on the method used.
Callbacks that return a Promise are handled automatically — the modal shows a loading state during the async operation, advances to the next step on resolve, and displays the error message on reject.
<LoginModal
trigger={<Button>Log in</Button>}
onEmailSubmit={async (email) => {
const { digitsKey } = await sendMagicEmail(email);
store.setDigitsKey(digitsKey);
}}
onCodeSubmit={async (code) => {
const session = await checkMagicDigits(code, digitsKey);
setUserSession(session);
}}
/>For forced authentication flows, set closable={false} to prevent the user from dismissing the modal.
<LoginModal
open={mustAuthenticate}
closable={false}
onEmailSubmit={(email) => sendOtp(email)}
onCodeSubmit={(code) => verifyCode(code)}
/><LoginModal
trigger={<Button>Join now</Button>}
title="Welcome to Wander"
description="Join thousands of travelers worldwide."
/>open?:
boolean
Controlled open state of the modal.
onOpenChange?:
(open: boolean) => void
Callback when the open state changes.
defaultOpen?:
boolean
Uncontrolled default open state.
trigger?:
ReactNode
Optional trigger element that opens the modal when clicked.
title?:
string
Title text displayed in the modal header. Defaults to "Log in or sign up".
description?:
string
Description text displayed below the title.
onEmailSubmit?:
(email: string) => void | Promise<void>
Callback when the email form is submitted. Returns a Promise for async loading and error handling. Transitions to OTP verification on resolve.
onCodeSubmit?:
(code: string) => void | Promise<void>
Callback when the OTP verification code is submitted. Returns a Promise for async loading and error handling. Closes modal on resolve.
onResendCode?:
() => void
Callback when the resend code link is clicked.
onGoogleLogin?:
() => void
Callback when the Google login button is clicked.
onAppleLogin?:
() => void
Callback when the Apple login button is clicked.
onFacebookLogin?:
() => void
Callback when the Facebook login button is clicked.
onPhoneSubmit?:
(phone: string) => void | Promise<void>
Callback when the phone number is submitted. Returns a Promise for async loading and error handling. Transitions to OTP verification on resolve.
companyName?:
string
Company name displayed in the terms footer. Falls back to "our" if not provided. Defaults to "Wander".
termsUrl?:
string
URL for the Terms of Service link. Defaults to "/terms".
privacyUrl?:
string
URL for the Privacy Policy link. Defaults to "/privacy".
className?:
string
Additional CSS classes to apply to the modal content.
error?:
string
Error message to display. Takes precedence over internally managed errors from rejected async callbacks.
closable?:
boolean
Whether the modal can be dismissed via close button, overlay click, or escape key. Set to false for forced authentication flows. Defaults to true.
onBack?:
() => void
Callback when the user navigates back from the verification step.