All skills
clerk avatar

/clerk-custom-ui

@1b2b3a1 official
by clerkclerk/skills83 stars
5

Custom authentication flows and component appearance - hooks (useSignIn, useSignUp), themes, colors, fonts, CSS. Use for custom sign-in/sign-up flows, appearance styling, visual customization, branding.

Use this Skill: https://skilld.dev/gh/clerk/skills/clerk-custom-ui

This session only. Nothing lands on disk.

core-2custom-sign-up.md

≈1.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Custom Sign-Up Flow (Core 2)

This document covers the older SDK (@clerk/nextjs v5–v6, @clerk/clerk-react v5–v6, @clerk/clerk-expo v1–v2). For the current SDK, see core-3/custom-sign-up.md.

Build a custom sign-up experience using the useSignUp() hook.

Hook API

import { useSignUp } from '@clerk/nextjs' // or @clerk/clerk-react, @clerk/clerk-expo

const { signUp, isLoaded, setActive } = useSignUp()
Property Type Description
signUp SignUp Sign-up object with methods
isLoaded boolean Whether the hook has loaded
setActive (params) => Promise Sets the active session

Sign-Up Flow

1. Create Sign-Up

const result = await signUp.create({
  emailAddress: 'user@example.com',
  password: 'securePassword123',
  firstName: 'Jane',  // optional
  lastName: 'Doe',    // optional
})

2. Prepare Verification

Send a verification code to the user's email or phone:

await signUp.prepareVerification({
  strategy: 'email_code', // or 'phone_code', 'email_link'
})

3. Attempt Verification

Verify the code the user received:

const result = await signUp.attemptVerification({
  strategy: 'email_code',
  code: '123456',
})

4. Finalize

Set the active session after successful sign-up:

await setActive({ session: signUp.createdSessionId })

SSO (OAuth)

await signUp.authenticateWithRedirect({
  strategy: 'oauth_google',
  redirectUrl: '/sso-callback',
  redirectUrlComplete: '/',
})

Error Handling

Use try/catch with isClerkAPIResponseError():

import { isClerkAPIResponseError } from '@clerk/nextjs/errors'

try {
  await signUp.create({ emailAddress, password })
} catch (err) {
  if (isClerkAPIResponseError(err)) {
    err.errors.forEach((e) => {
      console.log(e.code)        // e.g. 'form_password_pwned'
      console.log(e.message)     // Human-readable message
      console.log(e.longMessage) // Detailed message
    })
  }
}

Complete Example: Email/Password with Email Verification

'use client'
import { useState } from 'react'
import { useSignUp } from '@clerk/nextjs'
import { isClerkAPIResponseError } from '@clerk/nextjs/errors'
import { useRouter } from 'next/navigation'

export default function SignUpPage() {
  const { signUp, isLoaded, setActive } = useSignUp()
  const router = useRouter()

  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [code, setCode] = useState('')
  const [step, setStep] = useState<'register' | 'verify'>('register')
  const [error, setError] = useState('')

  if (!isLoaded) return <div>Loading...</div>

  async function handleRegister(e: React.FormEvent) {
    e.preventDefault()
    setError('')

    try {
      await signUp.create({ emailAddress: email, password })
      await signUp.prepareVerification({ strategy: 'email_code' })
      setStep('verify')
    } catch (err) {
      if (isClerkAPIResponseError(err)) {
        setError(err.errors[0]?.message || 'Sign up failed')
      }
    }
  }

  async function handleVerify(e: React.FormEvent) {
    e.preventDefault()
    setError('')

    try {
      const result = await signUp.attemptVerification({
        strategy: 'email_code',
        code,
      })

      if (result.status === 'complete') {
        await setActive({ session: result.createdSessionId })
        router.push('/')
      }
    } catch (err) {
      if (isClerkAPIResponseError(err)) {
        setError(err.errors[0]?.message || 'Verification failed')
      }
    }
  }

  if (step === 'verify') {
    return (
      <form onSubmit={handleVerify}>
        <p>Check your email for a verification code.</p>
        <input
          type="text"
          value={code}
          onChange={(e) => setCode(e.target.value)}
          placeholder="Verification code"
        />
        {error && <p>{error}</p>}
        <button type="submit">Verify Email</button>
      </form>
    )
  }

  return (
    <form onSubmit={handleRegister}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
      />
      {error && <p>{error}</p>}
      <button type="submit">Sign Up</button>
    </form>
  )
}

Docs

Source: SKILL.md on GitHub

1 warning2d5 checks · Risk SAFE
  • Gen Agent Trust Hub2d

    No security issues were detected. The skill provides legitimate documentation and code examples for implementing custom authentication flows and styling with the Clerk platform.

  • Socket2d

    No alerts

  • Snyk2d

    Risk: MEDIUM · 1 issue

  • Runlayer6mo

    7 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 1b2b3a1. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 3 weeks ago
What it can do
Network
metadata
{
  "author": "clerk",
  "version": "2.3.0"
}
All 1 allowed tools
WebFetch
  • React
  • clerk
  • authentication
  • custom-ui
  • hooks
  • theming
  • appearance
  • branding

README badge

README badge for clerk/skills/clerk-custom-ui

Builds custom sign-in and sign-up flows using Clerk's useSignIn and useSignUp hooks, and customizes Clerk component appearance via the appearance prop with themes, colors, fonts, and CSS. Supports both Core 2 and current SDK versions with different API shapes for each.

Generated from the current SKILL.md.

Does this skill work with both Core 2 and the current SDK (v7+)?
Yes. Custom flows have separate references for Core 2 and current SDK because their hook APIs differ. Appearance customization works the same across both, but Core 2 uses `layout` instead of `options` and imports themes from `@clerk/themes` instead of `@clerk/ui/themes`.
Can I use the shadcn theme with Clerk components?
Yes. If your project has `components.json` (shadcn/ui installed), import the shadcn theme from `@clerk/ui/themes` and apply it as your first customization step to ensure visual consistency with your design system.
How do I build a completely custom sign-in form?
Use the `useSignIn` hook with a custom component. The skill includes separate references for Core 2 and current SDK since their APIs differ significantly.
What's the difference between `variables` and `options` in the appearance prop?
`variables` controls colors, typography, and spacing (e.g. `colorPrimary`, `borderRadius`). `options` controls component structure like logo URL and social button layout (note: Core 2 calls this `layout`).
Does this skill require ClerkProvider to be set up first?
Yes. The skill assumes `ClerkProvider` wraps your app. Refer to the `clerk-setup` skill for initial installation if needed.

Generated from the current SKILL.md. These answers refresh after source changes.