# Detailed application form

> Multi-step detailed application form

A shadcn/ui block from the [@formscn](https://www.formscn.space) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/formscn/detailed-application-form).

- Page, with a live preview: https://shadcn.exchange/r/formscn/detailed-application-form
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/formscn/detailed-application-form-light-bc7ebf9c14d40fbf.png
- Registry item: https://www.formscn.space/r/detailed-application-form.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://www.formscn.space/r/detailed-application-form.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `@hookform/resolvers`, `libphonenumber-js`, `react-hook-form`, `react-phone-number-input`, `sonner`, `zod`
- Registry items: `https://formscn.space/r/button.json`, `https://formscn.space/r/card.json`, `https://formscn.space/r/input.json`, `https://formscn.space/r/textarea.json`, `https://formscn.space/r/label.json`, `https://formscn.space/r/select.json`

## Files

### `src/registry/default/templates/detailed-application-form.tsx`

```tsx
"use client";

import { useState } from "react";
import { useForm, Controller } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { Progress } from "@/components/ui/progress";
import { Textarea } from "@/components/ui/textarea";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import PhoneInput from "react-phone-number-input";
import "react-phone-number-input/style.css";

const formSchema = z.object({
  fullName: z.string().min(2, "Name required"),
  email: z.string().email("Invalid email address"),
  phone: z.string().optional().or(z.literal("")),
  currentRole: z.string().min(2, "Role required"),
  experienceYears: z.string().min(1, "Required"),
  skills: z.string().min(10, "List some skills"),
  portfolioUrl: z.string().optional().or(z.literal("")),
  availability: z.enum(["immediate", "two-weeks", "one-month"]),
  comments: z.string().optional().or(z.literal("")),
});

type FormValues = z.infer<typeof formSchema>;

export function DetailedApplicationForm() {
  const [currentStep, setCurrentStep] = useState(0);
  const steps = [
  {
    "id": "contact",
    "title": "Contact Info",
    "description": "How can we reach you?",
    "fields": [
      "fullName",
      "email",
      "phone"
    ]
  },
  {
    "id": "experience",
    "title": "Experience",
    "description": "Your professional background",
    "fields": [
      "currentRole",
      "experienceYears",
      "skills"
    ]
  },
  {
    "id": "additional",
    "title": "Additional Info",
    "description": "Almost there",
    "fields": [
      "portfolioUrl",
      "availability",
      "comments"
    ]
  }
];

  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      fullName: "",
      email: "",
      phone: "",
      currentRole: "",
      experienceYears: "",
      skills: "",
      portfolioUrl: "",
      availability: "immediate",
      comments: "",
    },
    mode: "onChange",
  });

  const onSubmit = async (data: FormValues) => {
    console.log("Form submitted:", data);
    toast.success("Form submitted successfully!");
  };

  const nextStep = async () => {
    const fields = steps[currentStep].fields;
    const output = await form.trigger(fields as any);
    if (output) {
      setCurrentStep((prev) => Math.min(prev + 1, steps.length - 1));
    }
  };

  const prevStep = () => {
    setCurrentStep((prev) => Math.max(prev - 1, 0));
  };

  return (
    <Card className="w-full max-w-md mx-auto">
      <CardHeader>
        <CardTitle>Detailed Application</CardTitle>
        <CardDescription>Comprehensive multi-step application form</CardDescription>
        
        {/* Stepper */}
        <div className="space-y-2 pt-2">
          <div className="flex items-center justify-between text-sm">
            <span className="font-medium text-muted-foreground">
              Step {currentStep + 1} of {steps.length}
            </span>
            <span className="font-medium">
              {steps[currentStep].title}
            </span>
          </div>
          <Progress value={((currentStep + 1) / steps.length) * 100} className="h-2" />
        </div>
        <div className="text-center text-sm font-medium text-muted-foreground pt-1">
          {steps[currentStep].title}
        </div>
      </CardHeader>
      <CardContent>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
          
        {currentStep === 0 && (
          <div className="space-y-4 animate-in fade-in-50 slide-in-from-right-5">
                      <div className="space-y-2">
            <Label htmlFor="fullName">Full Name</Label>
            <Input
              id="fullName"
              type="text"
              placeholder=""
              {...form.register("fullName")}
            />
            {form.formState.errors.fullName && (
            <p className="text-sm text-destructive">{form.formState.errors.fullName.message}</p>
          )}
          </div>

          <div className="space-y-2">
            <Label htmlFor="email">Email Address</Label>
            <Input
              id="email"
              type="email"
              placeholder=""
              {...form.register("email")}
            />
            {form.formState.errors.email && (
            <p className="text-sm text-destructive">{form.formState.errors.email.message}</p>
          )}
          </div>

          <div className="space-y-2">
            <Label htmlFor="phone">Phone Number</Label>
            <Controller
              control={form.control}
              name="phone"
              render={({ field }) => (
                <PhoneInput
                  international
                  countryCallingCodeEditable={false}
                  defaultCountry="US"
                  value={field.value}
                  onChange={field.onChange}
                  placeholder="+1 (555) 000-0000"
                  className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
                />
              )}
            />
            {form.formState.errors.phone && (
            <p className="text-sm text-destructive">{form.formState.errors.phone.message}</p>
          )}
          </div>
          </div>
        )}

        {currentStep === 1 && (
          <div className="space-y-4 animate-in fade-in-50 slide-in-from-right-5">
                      <div className="space-y-2">
            <Label htmlFor="currentRole">Current Role</Label>
            <Input
              id="currentRole"
              type="text"
              placeholder=""
              {...form.register("currentRole")}
            />
            {form.formState.errors.currentRole && (
            <p className="text-sm text-destructive">{form.formState.errors.currentRole.message}</p>
          )}
          </div>

          <div className="space-y-2">
            <Label htmlFor="experienceYears">Years of Experience</Label>
            <Controller
              control={form.control}
              name="experienceYears"
              render={({ field }) => (
                <Select onValueChange={field.onChange} defaultValue={field.value}>
                  <SelectTrigger id="experienceYears">
                    <SelectValue placeholder="Select an option" />
                  </SelectTrigger>
                  <SelectContent>
                    <SelectItem value="0-2">0-2 years</SelectItem>
                    <SelectItem value="3-5">3-5 years</SelectItem>
                    <SelectItem value="5+">5+ years</SelectItem>
                  </SelectContent>
                </Select>
              )}
            />
            {form.formState.errors.experienceYears && (
            <p className="text-sm text-destructive">{form.formState.errors.experienceYears.message}</p>
          )}
          </div>

          <div className="space-y-2">
            <Label htmlFor="skills">Key Skills</Label>
            <Textarea
              id="skills"
              placeholder="React, Node.js, TypeScript..."
              {...form.register("skills")}
            />
            {form.formState.errors.skills && (
            <p className="text-sm text-destructive">{form.formState.errors.skills.message}</p>
          )}
          </div>
          </div>
        )}

        {currentStep === 2 && (
          <div className="space-y-4 animate-in fade-in-50 slide-in-from-right-5">
                      <div className="space-y-2">
            <Label htmlFor="portfolioUrl">Portfolio URL</Label>
            <Input
              id="portfolioUrl"
              type="url"
              placeholder=""
              {...form.register("portfolioUrl")}
            />
            {form.formState.errors.portfolioUrl && (
            <p className="text-sm text-destructive">{form.formState.errors.portfolioUrl.message}</p>
          )}
          </div>

          <div className="space-y-2">
            <Label>When can you start?</Label>
            <Controller
              control={form.control}
              name="availability"
              render={({ field }) => (
                <RadioGroup
                  onValueChange={field.onChange}
                  defaultValue={field.value}
                  className="flex flex-col space-y-1"
                >
                  <div className="flex items-center space-x-2">
                    <RadioGroupItem value="immediate" id="availability-immediate" />
                    <Label htmlFor="availability-immediate">Immediately</Label>
                  </div>
                  <div className="flex items-center space-x-2">
                    <RadioGroupItem value="two-weeks" id="availability-two-weeks" />
                    <Label htmlFor="availability-two-weeks">Two Weeks</Label>
                  </div>
                  <div className="flex items-center space-x-2">
                    <RadioGroupItem value="one-month" id="availability-one-month" />
                    <Label htmlFor="availability-one-month">One Month</Label>
                  </div>
                </RadioGroup>
              )}
            />
            {form.formState.errors.availability && (
            <p className="text-sm text-destructive">{form.formState.errors.availability.message}</p>
          )}
          </div>

          <div className="space-y-2">
            <Label htmlFor="comments">Additional Comments</Label>
            <Textarea
              id="comments"
              placeholder=""
              {...form.register("comments")}
            />
            {form.formState.errors.comments && (
            <p className="text-sm text-destructive">{form.formState.errors.comments.message}</p>
          )}
          </div>
          </div>
        )}

          <div className="flex justify-between pt-4">
            <Button 
              type="button" 
              variant="outline" 
              onClick={prevStep} 
              disabled={currentStep === 0}
              className={cn(currentStep === 0 && "invisible")}
            >
              Back
            </Button>
            
            {currentStep < steps.length - 1 ? (
              <Button type="button" onClick={nextStep}>
                Next
              </Button>
            ) : (
              <Button type="submit">Submit</Button>
            )}
          </div>
        </form>
      </CardContent>
    </Card>
  );
}
```
