Contact With Map
A contact form beside a stylised inline-SVG map panel that stacks on mobile.
3 frameworksIntermediate
A centred name, email and message form with inline validation and no chrome.
<!--
Native validation is doing the accessibility work here: required + type="email"
+ minlength give the browser real, announced error messages for free. The
React/TypeScript tabs swap that for inline errors wired with aria-invalid and
role="alert"; this static markup keeps the browser's version.
-->
<section class="mx-auto w-full max-w-lg px-4 py-12 sm:py-16">
<h2 class="text-2xl font-bold tracking-tight text-gray-900 sm:text-3xl dark:text-gray-100">
Get in touch
</h2>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
Tell us what you need and we'll reply within one business day.
</p>
<form class="mt-6 space-y-4">
<div>
<label for="cf-name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Name</label>
<input id="cf-name" name="name" type="text" autocomplete="name" required class="mt-1.5 block w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2.5 text-sm text-gray-900 placeholder:text-gray-500 focus-visible:border-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-400 dark:focus-visible:ring-blue-400" placeholder="Jane Doe" />
</div>
<div>
<label for="cf-email" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Email</label>
<input id="cf-email" name="email" type="email" autocomplete="email" required class="mt-1.5 block w-full rounded-lg border border-gray-300 bg-white px-3.5 py-2.5 text-sm text-gray-900 placeholder:text-gray-500 focus-visible:border-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-400 dark:focus-visible:ring-blue-400" placeholder="you@company.com" />
</div>
<div>
<label for="cf-message" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Message</label>
<textarea id="cf-message" name="message" rows="4" required minlength="10" class="mt-1.5 block w-full resize-y rounded-lg border border-gray-300 bg-white px-3.5 py-2.5 text-sm text-gray-900 placeholder:text-gray-500 focus-visible:border-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-400 dark:focus-visible:ring-blue-400" placeholder="How can we help?"></textarea>
</div>
<button type="submit" class="inline-flex w-full items-center justify-center rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white transition-colors hover:bg-blue-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white motion-reduce:transition-none dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-900">
Send message
</button>
</form>
</section>| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'Get in touch' | Heading text for the card. |
description | string | - | Description |
submitLabel | string | 'Send message' | Submit label |
onSubmit | (values: ContactFormValues) => void | - | Called with the form's values when it is submitted. |
className | string | - | Additional classes merged onto the root element. |
The React and TypeScript tabs run their own validation (`noValidate` hands it over) so errors render inline as `role="alert"` text wired by `aria-invalid` + `aria-describedby`; the static HTML tab keeps the browser's native validation. Every field has a real `<label>` and the message is a `<textarea>`. Wire `onSubmit` to your backend; by default it is a no-op.