Plain Contact

Get in touch

Writeasmuchoraslittleasyoulike.

Every message is read by a practitioner, not a form robot, and answered within a working day.

Phone
+351 21 000 0000
Studio
Rua da Prata 12, Lisbon

We never share your details with anyone.

About this block

Plain ContactPRO

The classic contact section: a serif heading that settles in word by word, an intro and a ruled contact sheet on the left, and a plain form with name, email and message fields and a sliding-marker pill on the right, with no panel around it.

Booking32: Plain Contact

A plain contact section: a heading that settles in word by word over a ruled list of contact lines, beside a name, email and message form that ends on a pill and a one-line privacy note.

Upgrade to Pro

Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.

Upgrade Now

Installation

Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/booking32?email=YOUR_EMAIL&license_key=YOUR_KEY"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/booking32?email=YOUR_EMAIL&license_key=YOUR_KEY"

This installs the block to components/beste/block/booking32.tsx, the shadcn input, label, and textarea primitives the form is built on, and the button22 sliding-marker pill and the text1 word stagger it uses.

Quick start

The installed file exports booking32Demo alongside the block: the exact props behind the preview above. Spread it to get a working contact form in one line.

tsx
import { Booking32, booking32Demo } from "@/components/beste/block/booking32";

export default function Page() {
  return <Booking32 {...booking32Demo} />;
}

Then replace the demo with your own props. Written out, a trimmed setup looks like this:

tsx
import { Booking32 } from "@/components/beste/block/booking32";

export default function Page() {
  return (
    <Booking32
      eyebrow="Get in touch"
      heading="Write as much or as little as you like."
      description="Every message is read by a practitioner, not a form robot."
      contacts={[
        { label: "Email", value: "hello@beste.co", href: "mailto:hello@beste.co" },
        { label: "Phone", value: "+351 21 000 0000" },
      ]}
      form={{
        name: "Your name",
        email: "Email",
        message: "What would you like to say?",
        button: "Send the message",
        note: "We never share your details with anyone.",
      }}
    />
  );
}

Props

PropTypeDefaultDescription
eyebrowstringSmall-caps line above the heading
headingstringSection heading, rendered as an h2 through Text1
descriptionstringIntro paragraph under the heading
contactsContactLine[][]Ruled contact rows; a row with href becomes a link
formBooking32FormEvery string in the form; the whole form is skipped when unset
classNamestringExtra classes for the outer <section>
ts
type ContactLine = {
  label: string;
  value: string;
  href?: string;
};

type Booking32Form = {
  name: string;
  email: string;
  message: string;
  button: string;
  note: string;
};

Behavior notes

  • href on a contact line is what makes it clickable, so an email or a phone number can carry mailto: or tel: while an address stays plain text in the same list.
  • Those links are plain a elements rather than the router Link, which is correct for mailto: and tel: targets that should never be handled by client-side navigation.
  • Contact lines are a dl on a three-column grid with the value spanning two, so a long address wraps under itself instead of pushing its label out of line.
  • Field ids come from useId, so two of these on one page keep each Label pointing at its own input.
  • Name and email share a two-column row from sm, while the message keeps the full width, which is the shape a message form wants: short answers side by side, the long one on its own.
  • Dropping form leaves a usable contact section on its own, with just the heading and the ruled lines.
  • The form only calls preventDefault() on submit; no state is collected and nothing is sent until you wire it up.

Wiring the form up

This block ships the form markup only; state, validation, and submit are yours to add. Our guide wires the shadcn Field primitives to React Hook Form, TanStack Form, and Formisch on one field system.

More Booking blocks

View all Booking
PRO

booking30

Session Request

A booking section: a serif heading that settles in word by word over the intro, a ruled contact sheet and a practitioner avatar line, beside a calm request form with name and email fields, pill choices for session type and preferred time, a message box and a sliding-marker pill to send.

PRO

booking31

Visit Sheet

A visit-us section: a photograph drifting inside its frame on the left, and on the right a serif heading that settles in word by word, an intro, a ruled sheet with the serif address, directions and opening hours, a sliding-marker pill and a text link.

PRO

booking33

Session Picker

A booking chooser: a serif heading that settles in word by word and an intro, then a ruled list of session types on the left that swaps, with a soft blur, the muted detail card on the right showing a serif title, a description, a fact row and a sliding-marker pill.

PRO

booking24

Service And Slot Picker

An interactive appointment picker: a hairline panel pairing a selectable service list with a scrollable day strip and a time slot grid that marks unavailable times, beside a full-height provider photo with a gradient name card, closing on a live summary bar and confirm action.

PRO

booking26

Minimal Service Picker

A quiet booking picker whose left column lists each service as a single hairline row carrying only its length and price, swapping a sticky panel that floats a live week strip above the chosen service's detail and its book action.

FREE

booking29

Appointment Intake Match

Three intake questions that land on a real appointment type instead of a drop-down: the answers pick a route with its length, its facts, and the clinician you will sit with, and the image tile beside them swaps its floated availability card to match.