# Contact form with details

> Contact form with a confirmation state, next to email, phone, address and opening hours.

A shadcn/ui block from the [@balick](https://ui.balick.me) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/balick/contact-02).

- Page, with a live preview: https://shadcn.exchange/r/balick/contact-02
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/balick/contact-02-light-72697e9f27521d0d.png
- Registry item: https://ui.balick.me/r/contact-02.json
- Categories: contact
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.balick.me/r/contact-02.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: `lucide-react`
- Registry items: `button`, `input`, `label`, `textarea`, `https://ui.balick.me/r/section.json`

## Files

### `registry/balick/blocks/contact-02/contact-02.tsx`

```tsx
"use client"

import * as React from "react"
import { Check, Clock, Mail, MapPin, Phone } from "lucide-react"

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
import { Section, SectionHeader } from "@/registry/balick/ui/section"

const header = {
  eyebrow: "Contact",
  title: "Talk to our team.",
  description: "Questions about plans, a demo or a custom setup? We reply within one business day.",
}

const details = [
  { label: "Email", value: "hello@acme.com", href: "mailto:hello@acme.com", icon: Mail },
  { label: "Phone", value: "+1 (555) 010-2030", href: "tel:+15550102030", icon: Phone },
  { label: "Office", value: "221 Market Street, San Francisco, CA", href: "#", icon: MapPin },
  { label: "Hours", value: "Monday to Friday, 9:00 to 18:00 PT", icon: Clock },
]

/** Where the form is sent. Leave empty to only show the confirmation. */
const endpoint = ""

export function Contact02({ id = "contact" }: { id?: string }) {
  const [status, setStatus] = React.useState<"idle" | "sending" | "sent" | "error">("idle")

  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setStatus("sending")
    try {
      if (endpoint) {
        const response = await fetch(endpoint, {
          method: "POST",
          body: new FormData(event.currentTarget),
        })
        if (!response.ok) throw new Error(response.statusText)
      }
      setStatus("sent")
    } catch {
      setStatus("error")
    }
  }

  return (
    <Section id={id}>
      <div className="grid grid-cols-1 gap-12 lg:grid-cols-2 lg:gap-16">
        <div>
          <SectionHeader {...header} align="left" />
          <dl className="mt-12 grid grid-cols-1 gap-6">
            {details.map(({ label, value, href, icon: Icon }) => (
              <div key={label} className="flex gap-4">
                <span className="flex size-10 shrink-0 items-center justify-center rounded-lg border">
                  <Icon className="size-4" aria-hidden />
                </span>
                <div className="flex min-w-0 flex-col">
                  <dt className="text-sm font-medium">{label}</dt>
                  <dd className="text-sm text-muted-foreground">
                    {href ? (
                      <a href={href} className="transition-colors hover:text-foreground">
                        {value}
                      </a>
                    ) : (
                      value
                    )}
                  </dd>
                </div>
              </div>
            ))}
          </dl>
        </div>
        <div className="rounded-2xl border bg-card p-6 sm:p-8">
          {status === "sent" ? (
            <div role="status" className="flex h-full min-h-80 flex-col items-center justify-center text-center">
              <span className="flex size-12 items-center justify-center rounded-full bg-foreground text-background">
                <Check className="size-5" aria-hidden />
              </span>
              <h3 className="mt-6 font-medium">Message sent</h3>
              <p className="mt-2 max-w-xs text-sm leading-6 text-muted-foreground">
                Thanks for reaching out. We will get back to you within one business day.
              </p>
              <Button variant="outline" size="sm" className="mt-6" onClick={() => setStatus("idle")}>
                Send another message
              </Button>
            </div>
          ) : (
            <form onSubmit={onSubmit} className="grid grid-cols-1 gap-5 sm:grid-cols-2">
              <div className="grid gap-2">
                <Label htmlFor={`${id}-name`}>Name</Label>
                <Input id={`${id}-name`} name="name" autoComplete="name" required />
              </div>
              <div className="grid gap-2">
                <Label htmlFor={`${id}-email`}>Email</Label>
                <Input id={`${id}-email`} name="email" type="email" autoComplete="email" required />
              </div>
              <div className="grid gap-2 sm:col-span-2">
                <Label htmlFor={`${id}-company`}>
                  Company <span className="font-normal text-muted-foreground">(optional)</span>
                </Label>
                <Input id={`${id}-company`} name="company" autoComplete="organization" />
              </div>
              <div className="grid gap-2 sm:col-span-2">
                <Label htmlFor={`${id}-message`}>Message</Label>
                <Textarea
                  id={`${id}-message`}
                  name="message"
                  className="min-h-40"
                  required
                  placeholder="Tell us about your project"
                />
              </div>
              <div className="flex flex-col gap-3 sm:col-span-2 sm:flex-row sm:items-center sm:justify-between">
                <p aria-live="polite" className="text-sm text-muted-foreground">
                  {status === "error"
                    ? "Something went wrong. Please try again or email us."
                    : "We never share your details."}
                </p>
                <Button type="submit" disabled={status === "sending"}>
                  {status === "sending" ? "Sending…" : "Send message"}
                </Button>
              </div>
            </form>
          )}
        </div>
      </div>
    </Section>
  )
}
```
