Easily create form using our components based on Tailwind CSS and React.
Forms are essential user interface design element, providing users with the means to enter non-standardized responses.
See below our beautiful form examples that you can use in your Tailwind CSS and React project. The examples below are using the react-hook-form, @hookform/resolvers and zod make sure to install them before using the example.
We're using react-hook-form
, @hookform/resolvers
and zod
libraries to create a functional form with validation. Make sure to install them before using the example.
npm install react-hook-form @hookform/resolvers zod
npm install react-hook-form @hookform/resolvers zod
"use client";
import * as React from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Input,
Button,
Checkbox,
Typography,
InputFieldProps,
} from "@material-tailwind/react";
import { Lock, Mail, ProfileCircle } from "iconoir-react";
const formSchema = z.object({
name: z.string().min(1, { message: "Name is required." }),
email: z.string().email({ message: "Invalid email." }),
password: z
.string()
.min(6, { message: "Password must be at least 6 characters." }),
});
type FormInputs = z.infer<typeof formSchema>;
type TextFieldProps = InputFieldProps & {
label: string;
error?: string;
icon: React.ElementType;
};
const TextField = React.forwardRef<typeof Input.Field, TextFieldProps>(
({ label, error, icon: Icon, ...props }, ref) => {
const id = React.useId();
return (
<Typography
as="label"
htmlFor={id}
color="default"
className="mb-6 block space-y-1.5"
>
<span className="text-sm font-semibold">{label}</span>
<Input
ref={ref}
{...props}
id={id}
isError={Boolean(error)}
color={error ? "error" : "primary"}
>
<Input.Icon>
<Icon className="h-full w-full" />
</Input.Icon>
</Input>
{error && (
<Typography type="small" color="error">
{error}
</Typography>
)}
</Typography>
);
},
);
export function FormDemo() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormInputs>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
email: "",
password: "",
},
});
function onSubmit(data: FormInputs) {
console.log(data);
}
const nameError = errors.name?.message;
const emailError = errors.email?.message;
const passwordError = errors.password?.message;
return (
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-md">
<TextField
label="Name"
error={nameError}
icon={ProfileCircle}
placeholder="Alex Smith"
{...register("name")}
/>
<TextField
type="email"
label="Email Address"
error={emailError}
icon={Mail}
placeholder="[email protected]"
{...register("email")}
/>
<TextField
type="password"
label="Password"
error={passwordError}
icon={Lock}
placeholder="******"
{...register("password")}
/>
<div className="my-6 flex items-center gap-2">
<Checkbox>
<Checkbox.Indicator />
</Checkbox>
<Typography
htmlFor="remember"
className="flex items-center gap-1 text-foreground"
>
I'm agree with the
<Typography as="a" href="#" color="primary">
Terms and Conditions
</Typography>
</Typography>
</div>
<Button type="submit" className="w-full">
Sign Up
</Button>
<Typography
type="small"
className="my-4 flex items-center justify-center gap-1 text-foreground"
>
Already have an account?
<Typography
as="a"
href="#"
type="small"
color="primary"
className="font-bold"
>
Sign In
</Typography>
</Typography>
</form>
);
}
"use client";
import * as React from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Input,
Button,
Checkbox,
Typography,
InputFieldProps,
} from "@material-tailwind/react";
import { Lock, Mail, ProfileCircle } from "iconoir-react";
const formSchema = z.object({
name: z.string().min(1, { message: "Name is required." }),
email: z.string().email({ message: "Invalid email." }),
password: z
.string()
.min(6, { message: "Password must be at least 6 characters." }),
});
type FormInputs = z.infer<typeof formSchema>;
type TextFieldProps = InputFieldProps & {
label: string;
error?: string;
icon: React.ElementType;
};
const TextField = React.forwardRef<typeof Input.Field, TextFieldProps>(
({ label, error, icon: Icon, ...props }, ref) => {
const id = React.useId();
return (
<Typography
as="label"
htmlFor={id}
color="default"
className="mb-6 block space-y-1.5"
>
<span className="text-sm font-semibold">{label}</span>
<Input
ref={ref}
{...props}
id={id}
isError={Boolean(error)}
color={error ? "error" : "primary"}
>
<Input.Icon>
<Icon className="h-full w-full" />
</Input.Icon>
</Input>
{error && (
<Typography type="small" color="error">
{error}
</Typography>
)}
</Typography>
);
},
);
export function FormDemo() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormInputs>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
email: "",
password: "",
},
});
function onSubmit(data: FormInputs) {
console.log(data);
}
const nameError = errors.name?.message;
const emailError = errors.email?.message;
const passwordError = errors.password?.message;
return (
<form onSubmit={handleSubmit(onSubmit)} className="w-full max-w-md">
<TextField
label="Name"
error={nameError}
icon={ProfileCircle}
placeholder="Alex Smith"
{...register("name")}
/>
<TextField
type="email"
label="Email Address"
error={emailError}
icon={Mail}
placeholder="[email protected]"
{...register("email")}
/>
<TextField
type="password"
label="Password"
error={passwordError}
icon={Lock}
placeholder="******"
{...register("password")}
/>
<div className="my-6 flex items-center gap-2">
<Checkbox>
<Checkbox.Indicator />
</Checkbox>
<Typography
htmlFor="remember"
className="flex items-center gap-1 text-foreground"
>
I'm agree with the
<Typography as="a" href="#" color="primary">
Terms and Conditions
</Typography>
</Typography>
</div>
<Button type="submit" className="w-full">
Sign Up
</Button>
<Typography
type="small"
className="my-4 flex items-center justify-center gap-1 text-foreground"
>
Already have an account?
<Typography
as="a"
href="#"
type="small"
color="primary"
className="font-bold"
>
Sign In
</Typography>
</Typography>
</form>
);
}
"use client";
import * as React from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Card,
Tabs,
Input,
Button,
Checkbox,
Typography,
InputFieldProps,
} from "@material-tailwind/react";
import {
Mail,
Calendar,
CreditCard,
LockSquare,
ProfileCircle,
Globe,
Post,
} from "iconoir-react";
const cardFormSchema = z.object({
email: z.string().email({ message: "Invalid email." }),
cardNumber: z.string().min(15, { message: "Invalid card number." }),
expiry: z.string().min(4, { message: "Invalid expiry date." }),
cvc: z.string().min(3, { message: "Invalid CVC." }),
holderName: z.string().min(1, { message: "Holder name is required." }),
});
const paypalFormSchema = z.object({
email: z.string().email({ message: "Invalid email." }),
country: z.string().min(1, { message: "Country is required." }),
postalCode: z.string().min(1, { message: "Postal code is required." }),
});
type CardFormInputs = z.infer<typeof cardFormSchema>;
type PaypalFormInputs = z.infer<typeof paypalFormSchema>;
type TextFieldProps = InputFieldProps & {
label: string;
error?: string;
icon: React.ElementType;
};
const TextField = React.forwardRef<typeof Input.Field, TextFieldProps>(
({ label, error, icon: Icon, ...props }, ref) => {
const id = React.useId();
return (
<Typography
as="label"
htmlFor={id}
color="default"
className="mb-6 block w-full space-y-1.5"
>
<span className="text-sm font-semibold">{label}</span>
<Input
ref={ref}
{...props}
id={id}
isError={Boolean(error)}
color={error ? "error" : "primary"}
>
<Input.Icon>
<Icon className="h-full w-full" />
</Input.Icon>
</Input>
{error && (
<Typography type="small" color="error">
{error}
</Typography>
)}
</Typography>
);
},
);
function formatExpires(value: string) {
return value
.replace(/[^0-9]/g, "")
.replace(/^([2-9])$/g, "0$1")
.replace(/^(1{1})([3-9]{1})$/g, "0$1/$2")
.replace(/^0{1,}/g, "0")
.replace(/^([0-1]{1}[0-9]{1})([0-9]{1,2}).*/g, "$1/$2");
}
export function CheckoutForm() {
const cardForm = useForm<CardFormInputs>({
resolver: zodResolver(cardFormSchema),
defaultValues: {
email: "",
cardNumber: "",
expiry: "",
cvc: "",
holderName: "",
},
});
const paypalForm = useForm<PaypalFormInputs>({
resolver: zodResolver(paypalFormSchema),
defaultValues: {
email: "",
country: "",
postalCode: "",
},
});
function cardFormOnSubmit(data: CardFormInputs) {
console.log(data);
}
function paypalFormOnSubmit(data: PaypalFormInputs) {
console.log(data);
}
const emailError = cardForm.formState.errors.email?.message;
const cardNumberError = cardForm.formState.errors.cardNumber?.message;
const expiryError = cardForm.formState.errors.expiry?.message;
const cvcError = cardForm.formState.errors.cvc?.message;
const holderNameError = cardForm.formState.errors.holderName?.message;
const emailErrorPaypal = paypalForm.formState.errors.email?.message;
const countryError = paypalForm.formState.errors.country?.message;
const postalCodeError = paypalForm.formState.errors.postalCode?.message;
return (
<Card className="w-full max-w-md">
<Card.Header>
<Card
color="primary"
className="grid h-40 place-items-center rounded-md shadow-none"
>
<Typography type="h5" className="text-white">
Material Tailwind PRO
</Typography>
</Card>
</Card.Header>
<Card.Body>
<Tabs defaultValue="card">
<Tabs.List className="w-full">
<Tabs.Trigger type="button" className="w-full" value="card">
Card
</Tabs.Trigger>
<Tabs.Trigger type="button" className="w-full" value="paypal">
PayPal
</Tabs.Trigger>
<Tabs.TriggerIndicator />
</Tabs.List>
<Tabs.Panel
as="form"
onSubmit={cardForm.handleSubmit(cardFormOnSubmit)}
value="card"
>
<TextField
type="email"
label="Email Address"
error={emailError}
icon={Mail}
placeholder="[email protected]"
{...cardForm.register("email")}
/>
<TextField
label="Card Number"
error={cardNumberError}
icon={CreditCard}
placeholder="XXXX XXXX XXXX XXXX"
{...cardForm.register("cardNumber")}
value={cardForm.watch("cardNumber")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (digits.length > 16) {
return;
}
if (!digits || /^[\d\s]+$/.test(digits)) {
const formattedDigits = digits.replace(
/(\d{4})(?=\d)/g,
"$1 ",
);
cardForm.setValue("cardNumber", formattedDigits);
}
cardForm.trigger("cardNumber");
}}
/>
<div className="flex w-full gap-4">
<TextField
label="Expiry Date"
error={expiryError}
icon={Calendar}
placeholder="mm/yy"
{...cardForm.register("expiry")}
value={cardForm.watch("expiry")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (digits.length > 4 || digits.length < 4) {
cardForm.setError("expiry", {
type: "manual",
message: "Invalid expiry date.",
});
}
cardForm.setValue("expiry", formatExpires(digits));
cardForm.trigger("expiry");
}}
/>
<TextField
label="CVC"
error={cvcError}
icon={LockSquare}
placeholder="000"
maxLength={3}
{...cardForm.register("cvc")}
value={cardForm.watch("cvc")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (digits.length > 3) {
return;
}
cardForm.setValue("cvc", digits);
cardForm.trigger("cvc");
}}
/>
</div>
<TextField
label="Holder Name"
error={holderNameError}
icon={ProfileCircle}
placeholder="Alex Smith"
{...cardForm.register("holderName")}
/>
<div className="my-6 flex items-center gap-2">
<Checkbox>
<Checkbox.Indicator />
</Checkbox>
<Typography
htmlFor="remember"
className="flex items-center gap-1 text-foreground"
>
I'm agree with the
<Typography as="a" href="#" color="primary">
Terms and Conditions
</Typography>
</Typography>
</div>
<Button type="submit" className="w-full">
Pay Now
</Button>
</Tabs.Panel>
<Tabs.Panel
as="form"
onSubmit={paypalForm.handleSubmit(paypalFormOnSubmit)}
value="paypal"
>
<TextField
type="email"
label="Email Address"
error={emailErrorPaypal}
icon={Mail}
placeholder="[email protected]"
{...paypalForm.register("email")}
/>
<TextField
label="Country"
error={countryError}
icon={Globe}
placeholder="United Arab Emirates"
{...paypalForm.register("country")}
/>
<TextField
label="Postal Code"
error={postalCodeError}
icon={Post}
placeholder="123456"
{...paypalForm.register("postalCode")}
value={paypalForm.watch("postalCode")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (newValue !== digits) {
return;
}
paypalForm.setValue("postalCode", digits);
paypalForm.trigger("postalCode");
}}
/>
<Button type="submit" className="w-full">
Pay With PayPal
</Button>
</Tabs.Panel>
</Tabs>
<Typography
type="small"
className="my-4 flex items-center justify-center gap-1 text-foreground"
>
Payments are secure and encrypted
</Typography>
</Card.Body>
</Card>
);
}
"use client";
import * as React from "react";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import {
Card,
Tabs,
Input,
Button,
Checkbox,
Typography,
InputFieldProps,
} from "@material-tailwind/react";
import {
Mail,
Calendar,
CreditCard,
LockSquare,
ProfileCircle,
Globe,
Post,
} from "iconoir-react";
const cardFormSchema = z.object({
email: z.string().email({ message: "Invalid email." }),
cardNumber: z.string().min(15, { message: "Invalid card number." }),
expiry: z.string().min(4, { message: "Invalid expiry date." }),
cvc: z.string().min(3, { message: "Invalid CVC." }),
holderName: z.string().min(1, { message: "Holder name is required." }),
});
const paypalFormSchema = z.object({
email: z.string().email({ message: "Invalid email." }),
country: z.string().min(1, { message: "Country is required." }),
postalCode: z.string().min(1, { message: "Postal code is required." }),
});
type CardFormInputs = z.infer<typeof cardFormSchema>;
type PaypalFormInputs = z.infer<typeof paypalFormSchema>;
type TextFieldProps = InputFieldProps & {
label: string;
error?: string;
icon: React.ElementType;
};
const TextField = React.forwardRef<typeof Input.Field, TextFieldProps>(
({ label, error, icon: Icon, ...props }, ref) => {
const id = React.useId();
return (
<Typography
as="label"
htmlFor={id}
color="default"
className="mb-6 block w-full space-y-1.5"
>
<span className="text-sm font-semibold">{label}</span>
<Input
ref={ref}
{...props}
id={id}
isError={Boolean(error)}
color={error ? "error" : "primary"}
>
<Input.Icon>
<Icon className="h-full w-full" />
</Input.Icon>
</Input>
{error && (
<Typography type="small" color="error">
{error}
</Typography>
)}
</Typography>
);
},
);
function formatExpires(value: string) {
return value
.replace(/[^0-9]/g, "")
.replace(/^([2-9])$/g, "0$1")
.replace(/^(1{1})([3-9]{1})$/g, "0$1/$2")
.replace(/^0{1,}/g, "0")
.replace(/^([0-1]{1}[0-9]{1})([0-9]{1,2}).*/g, "$1/$2");
}
export function CheckoutForm() {
const cardForm = useForm<CardFormInputs>({
resolver: zodResolver(cardFormSchema),
defaultValues: {
email: "",
cardNumber: "",
expiry: "",
cvc: "",
holderName: "",
},
});
const paypalForm = useForm<PaypalFormInputs>({
resolver: zodResolver(paypalFormSchema),
defaultValues: {
email: "",
country: "",
postalCode: "",
},
});
function cardFormOnSubmit(data: CardFormInputs) {
console.log(data);
}
function paypalFormOnSubmit(data: PaypalFormInputs) {
console.log(data);
}
const emailError = cardForm.formState.errors.email?.message;
const cardNumberError = cardForm.formState.errors.cardNumber?.message;
const expiryError = cardForm.formState.errors.expiry?.message;
const cvcError = cardForm.formState.errors.cvc?.message;
const holderNameError = cardForm.formState.errors.holderName?.message;
const emailErrorPaypal = paypalForm.formState.errors.email?.message;
const countryError = paypalForm.formState.errors.country?.message;
const postalCodeError = paypalForm.formState.errors.postalCode?.message;
return (
<Card className="w-full max-w-md">
<Card.Header>
<Card
color="primary"
className="grid h-40 place-items-center rounded-md shadow-none"
>
<Typography type="h5" className="text-white">
Material Tailwind PRO
</Typography>
</Card>
</Card.Header>
<Card.Body>
<Tabs defaultValue="card">
<Tabs.List className="w-full">
<Tabs.Trigger type="button" className="w-full" value="card">
Card
</Tabs.Trigger>
<Tabs.Trigger type="button" className="w-full" value="paypal">
PayPal
</Tabs.Trigger>
<Tabs.TriggerIndicator />
</Tabs.List>
<Tabs.Panel
as="form"
onSubmit={cardForm.handleSubmit(cardFormOnSubmit)}
value="card"
>
<TextField
type="email"
label="Email Address"
error={emailError}
icon={Mail}
placeholder="[email protected]"
{...cardForm.register("email")}
/>
<TextField
label="Card Number"
error={cardNumberError}
icon={CreditCard}
placeholder="XXXX XXXX XXXX XXXX"
{...cardForm.register("cardNumber")}
value={cardForm.watch("cardNumber")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (digits.length > 16) {
return;
}
if (!digits || /^[\d\s]+$/.test(digits)) {
const formattedDigits = digits.replace(
/(\d{4})(?=\d)/g,
"$1 ",
);
cardForm.setValue("cardNumber", formattedDigits);
}
cardForm.trigger("cardNumber");
}}
/>
<div className="flex w-full gap-4">
<TextField
label="Expiry Date"
error={expiryError}
icon={Calendar}
placeholder="mm/yy"
{...cardForm.register("expiry")}
value={cardForm.watch("expiry")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (digits.length > 4 || digits.length < 4) {
cardForm.setError("expiry", {
type: "manual",
message: "Invalid expiry date.",
});
}
cardForm.setValue("expiry", formatExpires(digits));
cardForm.trigger("expiry");
}}
/>
<TextField
label="CVC"
error={cvcError}
icon={LockSquare}
placeholder="000"
maxLength={3}
{...cardForm.register("cvc")}
value={cardForm.watch("cvc")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (digits.length > 3) {
return;
}
cardForm.setValue("cvc", digits);
cardForm.trigger("cvc");
}}
/>
</div>
<TextField
label="Holder Name"
error={holderNameError}
icon={ProfileCircle}
placeholder="Alex Smith"
{...cardForm.register("holderName")}
/>
<div className="my-6 flex items-center gap-2">
<Checkbox>
<Checkbox.Indicator />
</Checkbox>
<Typography
htmlFor="remember"
className="flex items-center gap-1 text-foreground"
>
I'm agree with the
<Typography as="a" href="#" color="primary">
Terms and Conditions
</Typography>
</Typography>
</div>
<Button type="submit" className="w-full">
Pay Now
</Button>
</Tabs.Panel>
<Tabs.Panel
as="form"
onSubmit={paypalForm.handleSubmit(paypalFormOnSubmit)}
value="paypal"
>
<TextField
type="email"
label="Email Address"
error={emailErrorPaypal}
icon={Mail}
placeholder="[email protected]"
{...paypalForm.register("email")}
/>
<TextField
label="Country"
error={countryError}
icon={Globe}
placeholder="United Arab Emirates"
{...paypalForm.register("country")}
/>
<TextField
label="Postal Code"
error={postalCodeError}
icon={Post}
placeholder="123456"
{...paypalForm.register("postalCode")}
value={paypalForm.watch("postalCode")}
onChange={(event) => {
const newValue = event.target.value;
const digits = newValue.replace(/\D/g, "");
if (newValue !== digits) {
return;
}
paypalForm.setValue("postalCode", digits);
paypalForm.trigger("postalCode");
}}
/>
<Button type="submit" className="w-full">
Pay With PayPal
</Button>
</Tabs.Panel>
</Tabs>
<Typography
type="small"
className="my-4 flex items-center justify-center gap-1 text-foreground"
>
Payments are secure and encrypted
</Typography>
</Card.Body>
</Card>
);
}