Skip to content
    Style: Wireframe

    Blocks · Forms

    Contact form

    Name, email, topic and message with visible labels and native validation.

    40% of the 2,073 sites in The UI Index have a form (821 sites).

    Used in UI Pro: Form section

    Talk to us

    Questions about plans, a deploy that won't go through, or anything else.

    A few sentences is plenty. We reply within one working day.

    All fields except the topic are required.

    Both previews render the same markup; the HTML tab below is that markup without a framework.

    Install

    React
    npx shadcn@latest add https://www.themes.dev/ui/r/contact-form.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/contact-form.json

    Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the themes.dev UI theme. The HTML needs only the compiled CSS.

    contact-form.classes.ts
    /** A contact form: a heading and a line on the left, the form on the right from a wide container. Visible labels,
     *  native validation, the required fields marked in words, one primary button. */
    export const contactForm = {
      root: '@container bg-background px-6 py-20 text-foreground',
      inner: 'mx-auto grid max-w-6xl gap-10 @3xl:grid-cols-[1fr_1.4fr]',
      title: 'text-3xl font-semibold tracking-[-0.03em] text-balance',
      description: 'mt-3 max-w-md text-base text-pretty text-muted-foreground',
      form: 'grid gap-5 @lg:grid-cols-2',
      field: 'grid gap-2',
      wide: '@lg:col-span-2',
      hint: 'text-sm text-muted-foreground',
      footer: 'flex flex-wrap items-center gap-4 @lg:col-span-2',
    };
    
    export type ContactContent = {
      title: string;
      description?: string;
      action: string;
      topics: { value: string; label: string }[];
      button: string;
      /** Unique on the page; prefixes the field ids. */
      id: string;
    };
    contact-form.tsx
    import { Button } from '@/components/ui/button';
    import { Input } from '@/components/ui/input';
    import { Label } from '@/components/ui/label';
    import { Select } from '@/components/ui/select';
    import { Textarea } from '@/components/ui/textarea';
    import { cn } from '@/lib/utils';
    import { contactForm as c, type ContactContent } from './contact-form.classes';
    
    export type ContactFormProps = ContactContent & { className?: string };
    
    /** A contact form: name, email, topic and message with visible labels and native validation. */
    export function ContactForm({ title, description, action, topics, button, id, className }: ContactFormProps) {
      return (
        <section aria-labelledby={`${id}-title`} className={cn(c.root, className)}>
          <div className={c.inner}>
            <div>
              <h2 id={`${id}-title`} className={c.title}>{title}</h2>
              {description && <p className={c.description}>{description}</p>}
            </div>
            <form action={action} method="post" className={c.form}>
              <div className={c.field}>
                <Label htmlFor={`${id}-name`}>Name</Label>
                <Input id={`${id}-name`} name="name" autoComplete="name" required />
              </div>
              <div className={c.field}>
                <Label htmlFor={`${id}-email`}>Email</Label>
                <Input id={`${id}-email`} name="email" type="email" autoComplete="email" required />
              </div>
              <div className={cn(c.field, c.wide)}>
                <Label htmlFor={`${id}-topic`}>Topic</Label>
                <Select id={`${id}-topic`} name="topic" placeholder="Choose a topic" options={topics} />
              </div>
              <div className={cn(c.field, c.wide)}>
                <Label htmlFor={`${id}-message`}>Message</Label>
                <Textarea id={`${id}-message`} name="message" rows={5} required aria-describedby={`${id}-message-hint`} />
                <p id={`${id}-message-hint`} className={c.hint}>A few sentences is plenty. We reply within one working day.</p>
              </div>
              <div className={c.footer}>
                <Button type="submit">{button}</Button>
                <p className={c.hint}>All fields except the topic are required.</p>
              </div>
            </form>
          </div>
        </section>
      );
    }