Blocks
Forms
Sign-in, registration, settings, checkout-style forms.
Sign in
Centered sign-in card with email/password fields, remember-me row, social login and a sign-up footer.
Welcome back
Sign in to your account to continueNew here?
import {
Button,
Card,
Checkbox,
Divider,
Form,
FormField,
Heading,
Input,
Password,
Text,
} from "@zephora/react";
export function SignInBlock() {
return (
<div style={{ display: "flex", justifyContent: "center", padding: 24 }}>
<Card padding="lg" style={{ width: "100%", maxWidth: 380 }}>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 8,
marginBottom: 24,
textAlign: "center",
}}
>
<div
aria-hidden="true"
style={{
width: 44,
height: 44,
borderRadius: "50%",
background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#fff",
fontWeight: 700,
fontSize: 18,
}}
>
Z
</div>
<Heading level={2} size="xl">
Welcome back
</Heading>
<Text size="sm" muted>
Sign in to your account to continue
</Text>
</div>
<Form gap={16} onSubmit={(event) => event.preventDefault()}>
<FormField name="email" label="Email" required>
<Input type="email" placeholder="you@example.com" autoComplete="email" fullWidth />
</FormField>
<FormField name="password" label="Password" required>
<Password placeholder="Enter your password" autoComplete="current-password" fullWidth />
</FormField>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
flexWrap: "wrap",
gap: 8,
}}
>
<Checkbox size="sm">Remember me</Checkbox>
<Button type="button" variant="link" size="sm">
Forgot password?
</Button>
</div>
<Button type="submit" fullWidth>
Sign in
</Button>
</Form>
<Divider label="or" spacing={4} />
<Button
type="button"
variant="outline"
fullWidth
startIcon={
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" />
</svg>
}
>
Continue with GitHub
</Button>
<Text as="p" size="sm" muted align="center" style={{ marginTop: 16 }}>
New here?{" "}
<Button type="button" variant="link" size="sm">
Create an account
</Button>
</Text>
</Card>
</div>
);
}Sign up
Two-column registration card with a gradient brand panel, testimonial quote and a full account form.
import {
Button,
Card,
Checkbox,
Form,
FormField,
Heading,
Input,
Password,
Text,
} from "@zephora/react";
export function SignUpBlock() {
return (
<Card padding="none" style={{ overflow: "hidden", maxWidth: 860, margin: "0 auto" }}>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))" }}>
<div
style={{
background: "linear-gradient(160deg, #4f46e5 0%, #7c3aed 60%, #a855f7 100%)",
color: "#fff",
padding: 32,
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
gap: 32,
minHeight: 320,
}}
>
<div style={{ fontWeight: 700, fontSize: 18, letterSpacing: "0.02em" }}>Zephora</div>
<div>
<blockquote style={{ margin: 0, fontSize: 15, lineHeight: 1.6 }}>
“We shipped our new dashboard in a week. The blocks and components saved us an
entire design sprint.”
</blockquote>
<p style={{ marginTop: 16, marginBottom: 0, fontSize: 13, opacity: 0.85 }}>
Maya Lindqvist — CTO, Fjordline Labs
</p>
</div>
</div>
<div style={{ padding: 32 }}>
<Heading level={2} size="xl" style={{ marginBottom: 4 }}>
Create your account
</Heading>
<Text as="p" size="sm" muted style={{ marginBottom: 20 }}>
Start your 14-day free trial. No credit card required.
</Text>
<Form gap={14} onSubmit={(event) => event.preventDefault()}>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(140px, 1fr))",
gap: 12,
}}
>
<FormField name="firstName" label="First name" required>
<Input placeholder="Maya" autoComplete="given-name" fullWidth />
</FormField>
<FormField name="lastName" label="Last name" required>
<Input placeholder="Lindqvist" autoComplete="family-name" fullWidth />
</FormField>
</div>
<FormField name="email" label="Work email" required>
<Input type="email" placeholder="you@company.com" autoComplete="email" fullWidth />
</FormField>
<FormField
name="password"
label="Password"
help="Must be at least 8 characters."
required
>
<Password placeholder="Create a password" autoComplete="new-password" fullWidth />
</FormField>
<Checkbox size="sm" required>
I agree to the Terms of Service and Privacy Policy
</Checkbox>
<Button type="submit" fullWidth>
Create account
</Button>
<Text as="p" size="xs" muted>
By signing up you will receive occasional product emails. You can unsubscribe at
any time.
</Text>
</Form>
</div>
</div>
</Card>
);
}OTP verification
Centered verification card with a six-digit one-time-code input, success state and resend countdown.
Check your email
We sent a 6-digit code to alex@example.com. Enter it below to verify your address.
import * as React from "react";
import { Alert, Button, Card, Heading, InputOTP, Text } from "@zephora/react";
export function OtpVerificationBlock() {
const [verified, setVerified] = React.useState(false);
return (
<div style={{ display: "flex", justifyContent: "center", padding: 24 }}>
<Card padding="lg" style={{ width: "100%", maxWidth: 420, textAlign: "center" }}>
<Heading level={2} size="xl" style={{ marginBottom: 8 }}>
Check your email
</Heading>
<Text as="p" size="sm" muted style={{ marginBottom: 24 }}>
We sent a 6-digit code to alex@example.com. Enter it below to verify your address.
</Text>
<div style={{ display: "flex", justifyContent: "center", marginBottom: 20 }}>
<InputOTP length={6} onComplete={() => setVerified(true)} />
</div>
{verified ? (
<Alert status="success" title="Email verified">
Your address has been confirmed. Redirecting you now…
</Alert>
) : (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
gap: 4,
flexWrap: "wrap",
}}
>
<Text size="sm" muted>
{"Didn't get the code?"}
</Text>
<Button type="button" variant="link" size="sm" disabled>
Resend in 0:42
</Button>
</div>
)}
</Card>
</div>
);
}Profile settings
Account settings form with avatar upload row, username adornment, bio, website and timezone fields.
Profile
This information will be displayed publicly, so be careful what you share.
JPG, GIF or PNG. 1 MB max.
import {
Avatar,
Button,
Form,
FormField,
Heading,
Input,
Select,
Text,
Textarea,
} from "@zephora/react";
export function ProfileSettingsBlock() {
return (
<div style={{ maxWidth: 560, margin: "0 auto", padding: 24 }}>
<Heading level={2} size="xl" style={{ marginBottom: 4 }}>
Profile
</Heading>
<Text as="p" size="sm" muted style={{ marginBottom: 24 }}>
This information will be displayed publicly, so be careful what you share.
</Text>
<div
style={{
display: "flex",
alignItems: "center",
gap: 16,
flexWrap: "wrap",
marginBottom: 24,
}}
>
<Avatar name="Alex Johnson" size="xl" />
<div>
<Button type="button" variant="outline" size="sm">
Change photo
</Button>
<Text as="p" size="xs" muted style={{ marginTop: 6 }}>
JPG, GIF or PNG. 1 MB max.
</Text>
</div>
</div>
<Form gap={16} onSubmit={(event) => event.preventDefault()}>
<FormField name="fullName" label="Full name" required>
<Input defaultValue="Alex Johnson" autoComplete="name" fullWidth />
</FormField>
<FormField name="username" label="Username" required>
<Input startAdornment="@" defaultValue="alexj" autoComplete="username" fullWidth />
</FormField>
<FormField
name="bio"
label="Bio"
help="Brief description for your profile. Markdown is supported."
>
<Textarea rows={3} placeholder="I build design systems and…" />
</FormField>
<FormField name="website" label="Website">
<Input type="url" placeholder="https://example.com" fullWidth />
</FormField>
<FormField name="timezone" label="Timezone">
<Select
defaultValue="utc"
options={[
{ label: "(UTC-08:00) Pacific Time", value: "pt" },
{ label: "(UTC-05:00) Eastern Time", value: "et" },
{ label: "(UTC+00:00) UTC", value: "utc" },
{ label: "(UTC+01:00) Central European Time", value: "cet" },
{ label: "(UTC+03:00) Istanbul", value: "ist" },
{ label: "(UTC+09:00) Japan Standard Time", value: "jst" },
]}
/>
</FormField>
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8, flexWrap: "wrap" }}>
<Button type="button" variant="ghost">
Cancel
</Button>
<Button type="submit">Save changes</Button>
</div>
</Form>
</div>
);
}Checkout form
Contact and payment checkout form with card adornment, expiry/CVC row, country combobox and pay button.
import {
Button,
Checkbox,
Combobox,
Divider,
Form,
FormField,
Heading,
Input,
} from "@zephora/react";
export function CheckoutFormBlock() {
return (
<div style={{ maxWidth: 480, margin: "0 auto", padding: 24 }}>
<Form gap={16} onSubmit={(event) => event.preventDefault()}>
<Heading level={3} size="lg">
Contact
</Heading>
<FormField name="email" label="Email" required>
<Input type="email" placeholder="you@example.com" autoComplete="email" fullWidth />
</FormField>
<FormField name="phone" label="Phone" help="Used only for delivery updates.">
<Input type="tel" placeholder="+1 (555) 000-0000" autoComplete="tel" fullWidth />
</FormField>
<Divider spacing={2} />
<Heading level={3} size="lg">
Payment
</Heading>
<FormField name="cardNumber" label="Card number" required>
<Input
inputMode="numeric"
placeholder="1234 5678 9012 3456"
autoComplete="cc-number"
fullWidth
endAdornment={
<svg width="22" height="15" viewBox="0 0 22 15" aria-hidden="true">
<rect
x="0.5"
y="0.5"
width="21"
height="14"
rx="2"
fill="none"
stroke="currentColor"
/>
<rect x="2" y="3.5" width="18" height="2.5" fill="currentColor" />
</svg>
}
/>
</FormField>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(120px, 1fr))",
gap: 12,
}}
>
<FormField name="expiry" label="Expiry" required>
<Input placeholder="MM / YY" autoComplete="cc-exp" fullWidth />
</FormField>
<FormField name="cvc" label="CVC" required>
<Input inputMode="numeric" placeholder="123" autoComplete="cc-csc" fullWidth />
</FormField>
</div>
<FormField name="country" label="Country" required>
<Combobox
aria-label="Country"
placeholder="Search country…"
options={[
{ label: "United States", value: "us" },
{ label: "United Kingdom", value: "gb" },
{ label: "Germany", value: "de" },
{ label: "France", value: "fr" },
{ label: "Türkiye", value: "tr" },
{ label: "Japan", value: "jp" },
{ label: "Australia", value: "au" },
]}
/>
</FormField>
<Checkbox size="sm" defaultChecked>
Billing address is the same as shipping
</Checkbox>
<Button type="submit" fullWidth>
Pay $86.00
</Button>
</Form>
</div>
);
}Event form
Create-event form with date picker, time select, rich-text description, file attachments, visibility radios and notification switches.
Create event
import {
Button,
DatePicker,
Editor,
Fieldset,
FileUpload,
Form,
FormField,
Heading,
Input,
Radio,
RadioGroup,
Select,
Switch,
Text,
} from "@zephora/react";
export function EventFormBlock() {
return (
<div style={{ maxWidth: 640, margin: "0 auto", padding: 24 }}>
<Heading level={2} size="xl" style={{ marginBottom: 20 }}>
Create event
</Heading>
<Form gap={16} onSubmit={(event) => event.preventDefault()}>
<FormField name="title" label="Event title" required>
<Input placeholder="Product launch webinar" fullWidth />
</FormField>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
gap: 12,
}}
>
<FormField label="Date" required>
<DatePicker aria-label="Event date" />
</FormField>
<FormField label="Start time" required>
<Select
aria-label="Start time"
placeholder="Pick a time"
options={[
{ label: "09:00", value: "09:00" },
{ label: "10:00", value: "10:00" },
{ label: "11:00", value: "11:00" },
{ label: "13:00", value: "13:00" },
{ label: "15:00", value: "15:00" },
{ label: "17:00", value: "17:00" },
]}
/>
</FormField>
</div>
<FormField label="Description" help="Formatting is supported.">
<Editor minHeight={120} placeholder="Tell attendees what to expect…" />
</FormField>
<FormField label="Attachments" help="Slides, agenda or any supporting files.">
<FileUpload multiple />
</FormField>
<FormField label="Visibility">
<RadioGroup defaultValue="public" aria-label="Event visibility">
<Radio value="public">
<span style={{ display: "flex", flexDirection: "column" }}>
<Text size="sm" weight="medium">
Public
</Text>
<Text size="xs" muted>
Anyone can find and join this event.
</Text>
</span>
</Radio>
<Radio value="private">
<span style={{ display: "flex", flexDirection: "column" }}>
<Text size="sm" weight="medium">
Private
</Text>
<Text size="xs" muted>
Only invited people can see it.
</Text>
</span>
</Radio>
<Radio value="unlisted">
<span style={{ display: "flex", flexDirection: "column" }}>
<Text size="sm" weight="medium">
Unlisted
</Text>
<Text size="xs" muted>
Hidden from search, joinable via link.
</Text>
</span>
</Radio>
</RadioGroup>
</FormField>
<Fieldset legend="Notifications">
<div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
<Switch size="sm" defaultChecked>
Email reminder 24 hours before
</Switch>
<Switch size="sm">Notify attendees when details change</Switch>
</div>
</Fieldset>
<div style={{ display: "flex", justifyContent: "flex-end", gap: 8, flexWrap: "wrap" }}>
<Button type="button" variant="ghost">
Cancel
</Button>
<Button type="submit">Create event</Button>
</div>
</Form>
</div>
);
}Newsletter inline
Slim horizontal subscribe band with inline email field, consent checkbox and a success state.
Stay in the loop
Monthly product updates, no spam. Unsubscribe anytime.
import * as React from "react";
import { Alert, Button, Card, Checkbox, Form, Heading, Input, Text } from "@zephora/react";
export function NewsletterInlineBlock() {
const [subscribed, setSubscribed] = React.useState(false);
return (
<Card padding="lg" style={{ maxWidth: 860, margin: "0 auto" }}>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 24,
flexWrap: "wrap",
}}
>
<div style={{ flex: "1 1 260px", minWidth: 220 }}>
<Heading level={3} size="lg" style={{ marginBottom: 4 }}>
Stay in the loop
</Heading>
<Text as="p" size="sm" muted>
Monthly product updates, no spam. Unsubscribe anytime.
</Text>
</div>
{subscribed ? (
<div style={{ flex: "1 1 320px" }}>
<Alert status="success" title="You are subscribed!">
Check your inbox to confirm your email address.
</Alert>
</div>
) : (
<Form
gap={10}
style={{ flex: "1 1 320px" }}
onSubmit={(event) => {
event.preventDefault();
setSubscribed(true);
}}
>
<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
<div style={{ flex: "1 1 200px" }}>
<Input
type="email"
required
placeholder="you@example.com"
aria-label="Email address"
autoComplete="email"
fullWidth
/>
</div>
<Button type="submit">Subscribe</Button>
</div>
<Checkbox size="sm" required>
I agree to receive product update emails
</Checkbox>
</Form>
)}
</div>
</Card>
);
}