Clerk: Conditional Field
A password field that animates into a sign-in form only when it's needed. The container animates its measured height with Motion's resize() util while the field fades in.
Source code
"use client"
import { useCallback, useEffect, useId, useState } from "react"
import { AnimatePresence, motion, MotionConfig, resize } from "motion/react"
/* A password field that animates in only when it's needed. In the Clerk
* sign-in-or-up flow it appears when an entered email has no account yet,
* prompting the user to create a password. The field's container animates
* its measured height with Motion's resize() util, so the reveal stays
* smooth regardless of any validation message rendered below it. */
function ConditionalField({
open,
label,
error,
...props
}: React.ComponentProps<"input"> & {
open: boolean
label: string
error?: string | null
}) {
const [height, setHeight] = useState(0)
const internalId = useId()
const id = props.id || internalId
const measureRef = useCallback((el: HTMLDivElement | null) => {
if (!el) return
return resize(el, (_, { height }) => setHeight(height))
}, [])
return (
<motion.div animate={{ height: open ? height : 0 }} style={{ willChange: "height" }}>
<div ref={measureRef} style={{ position: "relative" }}>
<AnimatePresence mode="popLayout">
{open && (
<motion.div
key="password-field"
className="auth__field"
initial={{ opacity: 0, y: -8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 0 }}
style={{ marginTop: "0.75rem" }}
>
<label htmlFor={id}>{label}</label>
<input autoFocus {...props} id={id} />
{error && <p className="auth__field-error">{error}</p>}
</motion.div>
)}
</AnimatePresence>
</div>
</motion.div>
)
}
export default function ClerkConditionalField() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const [showPassword, setShowPassword] = useState(false)
const [done, setDone] = useState(false)
useEffect(() => {
if (!showPassword) return
const handleKeyUp = (e: KeyboardEvent) => {
if (e.key === "Escape") {
e.preventDefault()
setShowPassword(false)
}
}
window.addEventListener("keyup", handleKeyUp)
return () => window.removeEventListener("keyup", handleKeyUp)
}, [showPassword])
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
/* First submit with an email but no password reveals the password
* field. In a real flow this branch is driven by Clerk returning
* `form_identifier_not_found` for an unknown email. */
if (email && !password) {
setShowPassword(true)
} else {
setDone(true)
}
}
return (
<MotionConfig transition={{ type: "spring", bounce: 0.3, visualDuration: 0.4 }}>
<div className="clerk-stage">
<div className="auth__root">
<div className="auth__card-container">
<div className="auth__card">
<h3 className="auth__card-title">Welcome to Motion</h3>
<p className="auth__card-description">Enter your details to continue</p>
<form onSubmit={handleSubmit}>
<div className="auth__field">
<label htmlFor="email">Email</label>
<input
required
placeholder="Enter your email address"
type="email"
name="email"
id="email"
onChange={(e) => setEmail(e.target.value)}
value={email}
/>
</div>
<ConditionalField
label="Password"
name="password"
id="password"
open={showPassword}
type="password"
placeholder="Create a password"
onBlur={({ target }) => {
const hasPassword = target.value.length > 0
setShowPassword(hasPassword)
}}
onChange={(e) => setPassword(e.target.value)}
value={password}
required
/>
<button className="btn btn--primary" style={{ marginTop: "1.5rem" }} type="submit">
{done ? "Welcome aboard" : "Submit"}
</button>
</form>
</div>
</div>
</div>
<style>{`
.clerk-stage {
--surface: #f7f7f8;
color-scheme: light;
position: relative;
align-self: stretch;
flex: 1 1 auto;
width: 100%;
min-height: 100%;
overflow: auto;
background: #ededed;
color: #212126;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.clerk-stage, .clerk-stage * { box-sizing: border-box; }
.auth__root {
display: grid;
justify-content: center;
align-items: flex-start;
min-height: 100%;
padding: clamp(2rem, 14vh, 6rem) 1rem 2rem;
}
.auth__card-container {
position: relative;
grid-column: 1;
grid-row: 1;
}
.auth__card {
position: absolute;
left: 50%;
width: 25rem;
max-width: calc(100vw - 2rem);
transform: translateX(-50%);
padding: 2.5rem 2rem;
background-color: var(--surface);
border-radius: 2rem;
transform-origin: top center;
box-shadow:
inset 0 0 0 1px rgb(255 255 255),
inset 0 2px 4px 0 rgb(255 255 255),
inset 0 4px 10px 0 hsl(0 0 0 / 0.05),
0 4px 14px -10px hsl(0 0 0 / 0.4),
0 8px 28px -10px hsl(0 0 0 / 0.1),
0 0 0 1px rgb(0 0 0 / 0.06);
}
.auth__card button { width: 100%; }
.auth__card form { margin-top: 2rem; }
.auth__card-title {
text-align: center;
font-size: 1.125rem;
line-height: 1.5rem;
font-weight: 600;
letter-spacing: -0.025em;
}
.auth__card-description {
text-align: center;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 400;
opacity: 0.72;
}
.auth__field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.auth__field-error {
color: #ff3300;
font-size: 0.75rem;
line-height: 1rem;
}
.auth__field label {
font-size: 0.8125rem;
line-height: 1rem;
font-weight: 500;
}
.auth__field input {
--border-color: rgb(0 0 0 / 0.1);
--ring: 0 0 0 0px rgb(0 0 0 / 0);
width: 100%;
border-radius: 0.375rem;
background-color: var(--surface);
padding: 0.375rem 0.75rem;
font-size: 0.8125rem;
line-height: 1.125rem;
color: #212126;
border: none;
outline: none;
box-shadow: 0 1px 3px -1px rgb(0 0 0 / 0.08), 0 0 0 1px var(--border-color), var(--ring);
}
.auth__field input::placeholder { color: rgb(0 0 0 / 0.4); }
.auth__field input:hover { --border-color: rgb(0 0 0 / 0.2); }
.auth__field input:focus-visible {
--border-color: rgb(0 0 0 / 0.2);
--ring: 0 0 0 3px rgb(0 0 0 / 0.06);
}
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
position: relative;
height: 2rem;
border: 0;
padding: 0 0.75rem;
background-color: transparent;
color: #212126;
border-radius: 0.5rem;
overflow: hidden;
font-size: 0.8125rem;
font-weight: 500;
cursor: pointer;
}
.btn::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
}
.btn--primary {
background-color: #212126;
color: #fff;
box-shadow: inset 0 12px 0 -11px rgb(255 255 255 / 0.12), 0 2px 3px rgb(0 0 0 / 0.2), 0 0 0 1px #212126;
}
.btn--primary::after { background: linear-gradient(to bottom, rgb(255 255 255 / 0.15), rgb(255 255 255 / 0) 60%); }
.btn--primary:hover::after { background: linear-gradient(to bottom, rgb(255 255 255 / 0.12), rgb(255 255 255 / 0.12)); }
.btn--primary:active::after { background: linear-gradient(to bottom, rgb(255 255 255 / 0.2), rgb(255 255 255 / 0.2)); }
`}</style>
</div>
</MotionConfig>
)
}Related examples
Latest in React
Motion+
Unlock all 400+ examples
- Source code for every Plus example.
- Provide examples direct to your agent via Motion's MCP.
- Lifetime access to new examples and APIs.







