MKT UI
Sections

Project inquiry form

Qualifying enquiry form: services, budget, timeline, and context.

Open preview (opens in a new tab)

The demo sends nothing

Submissions on this page are simulated. To see the failure and retry path, put fail anywhere in the email address.

Installation

npx shadcn@latest add http://localhost:3000/r/project-inquiry-form.json

MKT UI is not published yet, so this points at your local development server. Set NEXT_PUBLIC_REGISTRY_URL to the deployed origin to show the public command.

Also installs: button, checkbox, input, select, textarea, field, submission-status, use-form-submission.

Usage

"use client";

import { ProjectInquiryForm } from "@/components/project-inquiry-form";

<ProjectInquiryForm
  services={[
    { value: "strategy", label: "Strategy", note: "Positioning and messaging" },
    { value: "build", label: "Build", note: "Web and product" },
  ]}
  budgets={[
    { value: "10-25k", label: "$10k – $25k" },
    { value: "25-50k", label: "$25k – $50k" },
  ]}
  timelines={[
    { value: "asap", label: "As soon as possible" },
    { value: "exploring", label: "Just exploring" },
  ]}
  onSubmit={async (values) => {
    const response = await fetch("/api/inquiry", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(values),
    });
    if (!response.ok) throw new Error("We couldn't send your inquiry. Please try again.");
  }}
/>

Props

PropTypeDefaultDescription
services (required)InquiryOption[]—What the visitor can ask for. Rendered as a checkbox group.
budgets (required)InquiryOption[]—Budget bands.
timelines (required)InquiryOption[]—When they need it done.
onSubmit (required)(values) => Promise<unknown> | unknown—Deliver the enquiry. Throw to show the failure state.
defaultValuesPartial<ProjectInquiryValues>—Pre-fill, e.g. from a campaign URL.
submitLabelstring"Send project inquiry"Submit button text.
successTitleReactNode"Inquiry received"Heading on the confirmation.
successMessageReactNode—Say what happens next and when.
disabledbooleanfalseTurn the form off. Pair with `unavailableMessage`.
footnoteReactNode—Small print under the submit button.

Fields

FieldTypeRequired
Your nametextYes
Work emailemailYes
CompanytextNo
What do you need help with?checkbox groupAt least one
BudgetselectYes
TimelineselectYes
About the projecttextareaYes, min 20 characters
ConsentcheckboxYes

Behaviour

Budget and timeline are bands, not free text. A deliberate trade: a visitor who will not type a number will still pick a range, and a range is enough to route the enquiry to the right person.

The service checkboxes are a real fieldset with a legend. Without it a screen reader announces four unrelated checkboxes and no question. The "choose at least one" error attaches to the group, not to any individual box, because that is where the failure actually is.

All the submission-state behaviour from quote-request-form applies here too: validation on blur then live, duplicate submits blocked by a ref, and input preserved on failure.

Limitations

  • Single step. The multistep qualification variant is not implemented.
  • No file upload, so no brief or brand-guideline attachments.
  • No spam protection — no honeypot, no CAPTCHA.
  • Field set is fixed. Adding fields means editing your installed copy, which is deliberate: a configurable field schema would be a form builder.

On this page