shadcn/ui
The following examples demonstrate how to compose Clerk Elements with shadcn/ui to build custom sign-in and sign-up flows.
Before you start
To use these examples, you must first:
- Complete the shadcn/ui Next.js installation guide
- Install the Button, Card, Input, and Label components within your project
Install shadcn /ui components npx shadcn-ui@latest add button card input label
- Add the
Icons
component from the shadcn/ui docs to anicons.tsx
file within yourcomponent/ui/
directory. - Add the following animations to your
tailwind.config.js
file:tailwind.config.js /** @type {import('tailwindcss').Config} */ module.exports = { theme: { extend: { keyframes: { "caret-blink": { "0%,70%,100%": { opacity: "1" }, "20%,50%": { opacity: "0" }, }, }, animation: { "caret-blink": "caret-blink 1.25s ease-out infinite", }, }, }, }
You must also configure the appropriate settings in Clerk:
- Navigate to the Clerk Dashboard.
- In the navigation sidebar, select User & Authentication > Social connections.
- Ensure that Google and GitHub are enabled.
Sign up
'use client';
import * as Clerk from '@clerk/elements/common';
import * as SignUp from '@clerk/elements/sign-up';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Icons } from '@/components/ui/icons';
import { cn } from '@/lib/utils';
export default function SignUpPage() {
return (
<div className="grid w-full grow items-center px-4 sm:justify-center">
<SignUp.Root>
<Clerk.Loading>
{(isGlobalLoading) => (
<>
<SignUp.Step name="start">
<Card className="w-full sm:w-96">
<CardHeader>
<CardTitle>Create your account</CardTitle>
<CardDescription>Welcome! Please fill in the details to get started.</CardDescription>
</CardHeader>
<CardContent className="grid gap-y-4">
<div className="grid grid-cols-2 gap-x-4">
<Clerk.Connection name="github" asChild>
<Button size="sm" variant="outline" type="button" disabled={isGlobalLoading}>
<Clerk.Loading scope="provider:github">
{(isLoading) =>
isLoading ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<>
<Icons.gitHub className="mr-2 size-4" />
GitHub
</>
)
}
</Clerk.Loading>
</Button>
</Clerk.Connection>
<Clerk.Connection name="google" asChild>
<Button size="sm" variant="outline" type="button" disabled={isGlobalLoading}>
<Clerk.Loading scope="provider:google">
{(isLoading) =>
isLoading ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<>
<Icons.google className="mr-2 size-4" />
Google
</>
)
}
</Clerk.Loading>
</Button>
</Clerk.Connection>
</div>
<p className="flex items-center gap-x-3 text-sm text-muted-foreground before:h-px before:flex-1 before:bg-border after:h-px after:flex-1 after:bg-border">
or
</p>
<Clerk.Field name="emailAddress" className="space-y-2">
<Clerk.Label asChild>
<Label>Email address</Label>
</Clerk.Label>
<Clerk.Input type="email" required asChild>
<Input />
</Clerk.Input>
<Clerk.FieldError className="block text-sm text-destructive" />
</Clerk.Field>
<Clerk.Field name="password" className="space-y-2">
<Clerk.Label asChild>
<Label>Password</Label>
</Clerk.Label>
<Clerk.Input type="password" required asChild>
<Input />
</Clerk.Input>
<Clerk.FieldError className="block text-sm text-destructive" />
</Clerk.Field>
</CardContent>
<CardFooter>
<div className="grid w-full gap-y-4">
<SignUp.Action submit asChild>
<Button disabled={isGlobalLoading}>
<Clerk.Loading>
{(isLoading) => {
return isLoading ? <Icons.spinner className="size-4 animate-spin" /> : 'Continue';
}}
</Clerk.Loading>
</Button>
</SignUp.Action>
<Button variant="link" size="sm" asChild>
<Link href="/sign-in">Already have an account? Sign in</Link>
</Button>
</div>
</CardFooter>
</Card>
</SignUp.Step>
<SignUp.Step name="continue">
<Card className="w-full sm:w-96">
<CardHeader>
<CardTitle>Continue registration</CardTitle>
</CardHeader>
<CardContent>
<Clerk.Field name="username" className="space-y-2">
<Clerk.Label>
<Label>Username</Label>
</Clerk.Label>
<Clerk.Input type="text" required asChild>
<Input />
</Clerk.Input>
<Clerk.FieldError className="block text-sm text-destructive" />
</Clerk.Field>
</CardContent>
<CardFooter>
<div className="grid w-full gap-y-4">
<SignUp.Action submit asChild>
<Button disabled={isGlobalLoading}>
<Clerk.Loading>
{(isLoading) => {
return isLoading ? <Icons.spinner className="size-4 animate-spin" /> : 'Continue';
}}
</Clerk.Loading>
</Button>
</SignUp.Action>
</div>
</CardFooter>
</Card>
</SignUp.Step>
<SignUp.Step name="verifications">
<SignUp.Strategy name="email_code">
<Card className="w-full sm:w-96">
<CardHeader>
<CardTitle>Verify your email</CardTitle>
<CardDescription>Use the verification link sent to your email address</CardDescription>
</CardHeader>
<CardContent className="grid gap-y-4">
<div className="grid items-center justify-center gap-y-2">
<Clerk.Field name="code" className="space-y-2">
<Clerk.Label className="sr-only">Email address</Clerk.Label>
<div className="flex justify-center text-center">
<Clerk.Input
type="otp"
className="flex justify-center has-[:disabled]:opacity-50"
autoSubmit
render={({ value, status }) => {
return (
<div
data-status={status}
className={cn(
'relative flex size-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
{
'z-10 ring-2 ring-ring ring-offset-background':
status === 'cursor' || status === 'selected',
},
)}
>
{value}
{status === 'cursor' && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
</div>
)}
</div>
);
}}
/>
</div>
<Clerk.FieldError className="block text-center text-sm text-destructive" />
</Clerk.Field>
<SignUp.Action
asChild
resend
className="text-muted-foreground"
fallback={({ resendableAfter }) => (
<Button variant="link" size="sm" disabled>
Didn't recieve a code? Resend (
<span className="tabular-nums">{resendableAfter}</span>)
</Button>
)}
>
<Button type="button" variant="link" size="sm">
Didn't recieve a code? Resend
</Button>
</SignUp.Action>
</div>
</CardContent>
<CardFooter>
<div className="grid w-full gap-y-4">
<SignUp.Action submit asChild>
<Button disabled={isGlobalLoading}>
<Clerk.Loading>
{(isLoading) => {
return isLoading ? <Icons.spinner className="size-4 animate-spin" /> : 'Continue';
}}
</Clerk.Loading>
</Button>
</SignUp.Action>
</div>
</CardFooter>
</Card>
</SignUp.Strategy>
</SignUp.Step>
</>
)}
</Clerk.Loading>
</SignUp.Root>
</div>
);
}
'use client';
import * as Clerk from '@clerk/elements/common';
import * as SignIn from '@clerk/elements/sign-in';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Icons } from '@/components/ui/icons';
import { cn } from '@/lib/utils';
export default function SignInPage() {
return (
<div className="grid w-full grow items-center px-4 sm:justify-center">
<SignIn.Root>
<Clerk.Loading>
{(isGlobalLoading) => (
<>
<SignIn.Step name="start">
<Card className="w-full sm:w-96">
<CardHeader>
<CardTitle>Sign in to Acme Co</CardTitle>
<CardDescription>Welcome back! Please sign in to continue</CardDescription>
</CardHeader>
<CardContent className="grid gap-y-4">
<div className="grid grid-cols-2 gap-x-4">
<Clerk.Connection name="github" asChild>
<Button size="sm" variant="outline" type="button" disabled={isGlobalLoading}>
<Clerk.Loading scope="provider:github">
{(isLoading) =>
isLoading ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<>
<Icons.gitHub className="mr-2 size-4" />
GitHub
</>
)
}
</Clerk.Loading>
</Button>
</Clerk.Connection>
<Clerk.Connection name="google" asChild>
<Button size="sm" variant="outline" type="button" disabled={isGlobalLoading}>
<Clerk.Loading scope="provider:google">
{(isLoading) =>
isLoading ? (
<Icons.spinner className="size-4 animate-spin" />
) : (
<>
<Icons.google className="mr-2 size-4" />
Google
</>
)
}
</Clerk.Loading>
</Button>
</Clerk.Connection>
</div>
<p className="flex items-center gap-x-3 text-sm text-muted-foreground before:h-px before:flex-1 before:bg-border after:h-px after:flex-1 after:bg-border">
or
</p>
<Clerk.Field name="identifier" className="space-y-2">
<Clerk.Label asChild>
<Label>Email address</Label>
</Clerk.Label>
<Clerk.Input type="email" required asChild>
<Input />
</Clerk.Input>
<Clerk.FieldError className="block text-sm text-destructive" />
</Clerk.Field>
</CardContent>
<CardFooter>
<div className="grid w-full gap-y-4">
<SignIn.Action submit asChild>
<Button disabled={isGlobalLoading}>
<Clerk.Loading>
{(isLoading) => {
return isLoading ? <Icons.spinner className="size-4 animate-spin" /> : 'Continue';
}}
</Clerk.Loading>
</Button>
</SignIn.Action>
<Button variant="link" size="sm" asChild>
<Link href="/sign-up">Don't have an account? Sign up</Link>
</Button>
</div>
</CardFooter>
</Card>
</SignIn.Step>
<SignIn.Step name="choose-strategy">
<Card className="w-full sm:w-96">
<CardHeader>
<CardTitle>Use another method</CardTitle>
<CardDescription>Facing issues? You can use any of these methods to sign in.</CardDescription>
</CardHeader>
<CardContent className="grid gap-y-4">
<SignIn.SupportedStrategy name="email_code" asChild>
<Button type="button" variant="link" disabled={isGlobalLoading}>
Email code
</Button>
</SignIn.SupportedStrategy>
<SignIn.SupportedStrategy name="password" asChild>
<Button type="button" variant="link" disabled={isGlobalLoading}>
Password
</Button>
</SignIn.SupportedStrategy>
</CardContent>
<CardFooter>
<div className="grid w-full gap-y-4">
<SignIn.Action navigate="previous" asChild>
<Button disabled={isGlobalLoading}>
<Clerk.Loading>
{(isLoading) => {
return isLoading ? <Icons.spinner className="size-4 animate-spin" /> : 'Go back';
}}
</Clerk.Loading>
</Button>
</SignIn.Action>
</div>
</CardFooter>
</Card>
</SignIn.Step>
<SignIn.Step name="verifications">
<SignIn.Strategy name="password">
<Card className="w-full sm:w-96">
<CardHeader>
<CardTitle>Check your email</CardTitle>
<CardDescription>Enter the verification code sent to your email</CardDescription>
<p className="text-sm text-muted-foreground">
Welcome back <SignIn.SafeIdentifier />
</p>
</CardHeader>
<CardContent className="grid gap-y-4">
<Clerk.Field name="password" className="space-y-2">
<Clerk.Label asChild>
<Label>Password</Label>
</Clerk.Label>
<Clerk.Input type="password" asChild>
<Input />
</Clerk.Input>
<Clerk.FieldError className="block text-sm text-destructive" />
</Clerk.Field>
</CardContent>
<CardFooter>
<div className="grid w-full gap-y-4">
<SignIn.Action submit asChild>
<Button disabled={isGlobalLoading}>
<Clerk.Loading>
{(isLoading) => {
return isLoading ? <Icons.spinner className="size-4 animate-spin" /> : 'Continue';
}}
</Clerk.Loading>
</Button>
</SignIn.Action>
<SignIn.Action navigate="choose-strategy" asChild>
<Button type="button" size="sm" variant="link">
Use another method
</Button>
</SignIn.Action>
</div>
</CardFooter>
</Card>
</SignIn.Strategy>
<SignIn.Strategy name="email_code">
<Card className="w-full sm:w-96">
<CardHeader>
<CardTitle>Check your email</CardTitle>
<CardDescription>Enter the verification code sent to your email</CardDescription>
<p className="text-sm text-muted-foreground">
Welcome back <SignIn.SafeIdentifier />
</p>
</CardHeader>
<CardContent className="grid gap-y-4">
<Clerk.Field name="code">
<Clerk.Label className="sr-only">
Email verification code
</Clerk.Label>
<div className="grid gap-y-2 items-center justify-center">
<div className="flex justify-center text-center">
<Clerk.Input
type="otp"
autoSubmit
className="flex justify-center has-[:disabled]:opacity-50"
render={({ value, status }) => {
return (
<div
data-status={status}
className="relative flex h-9 w-9 items-center justify-center border-y border-r border-input text-sm shadow-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md data-[status=selected]:ring-1 data-[status=selected]:ring-ring data-[status=cursor]:ring-1 data-[status=cursor]:ring-ring"
>
{value}
</div>
);
}}
/>
</div>
<Clerk.FieldError className="block text-sm text-destructive text-center" />
<SignIn.Action
asChild
resend
className="text-muted-foreground"
fallback={({ resendableAfter }) => (
<Button variant="link" size="sm" disabled>
Didn't recieve a code? Resend (
<span className="tabular-nums">
{resendableAfter}
</span>
)
</Button>
)}
>
<Button variant="link" size="sm">
Didn't recieve a code? Resend
</Button>
</SignIn.Action>
</div>
</Clerk.Field>
</CardContent>
<CardFooter>
<div className="grid w-full gap-y-4">
<SignIn.Action submit asChild>
<Button disabled={isGlobalLoading}>
<Clerk.Loading>
{(isLoading) => {
return isLoading ? <Icons.spinner className="size-4 animate-spin" /> : 'Continue';
}}
</Clerk.Loading>
</Button>
</SignIn.Action>
<SignIn.Action navigate="choose-strategy" asChild>
<Button size="sm" variant="link">
Use another method
</Button>
</SignIn.Action>
</div>
</CardFooter>
</Card>
</SignIn.Strategy>
</SignIn.Step>
</>
)}
</Clerk.Loading>
</SignIn.Root>
</div>
);
}
OTP Input
The following example demonstrates how to make a one-time password (OTP) input with Clerk Elements. This example will only work if placed within a Step
in a sign-up or sign-in authentication flow, as shown in the sign-in and sign-up examples.
<Clerk.Input
type="otp"
className="flex justify-center has-[:disabled]:opacity-50"
autoSubmit
render={({ value, status }) => {
return (
<div
data-status={status}
className={cn(
'relative flex size-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
{
'z-10 ring-2 ring-ring ring-offset-background':
status === 'cursor' || status === 'selected',
},
)}
>
{value}
{status === 'cursor' && (
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
<div className="animate-caret-blink h-4 w-px bg-foreground duration-1000" />
</div>
)}
</div>
);
}}
/>