Simple Contact Form
A centred name, email and message form with inline validation and no chrome.
3 frameworksBeginner
A contact form beside a stylised inline-SVG map panel that stacks on mobile.
<!--
The map is a stylised inline SVG, not an embedded tile: no third-party script,
no API key, no image to go stale, and it recolours for dark mode with the rest
of the page. It carries role="img" + aria-label so it is announced as a map
rather than as a wall of anonymous <path> elements.
-->
<section class="mx-auto grid w-full max-w-5xl items-stretch gap-6 px-4 py-12 md:grid-cols-2 md:gap-10 sm:py-16">
<div>
<h2 class="text-2xl font-bold tracking-tight text-gray-900 sm:text-3xl dark:text-gray-100">Visit or message us</h2>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">We're at 24 Harbour Street. Send a note and we'll line up a time.</p>
<form class="mt-6 space-y-4">
<div>
<label for="cm-name" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Name</label>
<input id="cm-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 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:focus-visible:ring-blue-400" />
</div>
<div>
<label for="cm-email" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Email</label>
<input id="cm-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 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:focus-visible:ring-blue-400" />
</div>
<div>
<label for="cm-message" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Message</label>
<textarea id="cm-message" name="message" rows="4" required 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 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:focus-visible:ring-blue-400"></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>
</div>
<div class="relative min-h-[240px] overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800">
<svg viewBox="0 0 400 320" class="absolute inset-0 h-full w-full" role="img" aria-label="Map to 24 Harbour Street">
<rect width="400" height="320" class="fill-gray-100 dark:fill-gray-900" />
<path d="M0 210 L400 150" fill="none" stroke-width="26" class="stroke-blue-200 dark:stroke-blue-950" />
<path d="M120 -10 L180 330" fill="none" stroke-width="16" class="stroke-gray-200 dark:stroke-gray-800" />
<path d="M-10 90 L410 40" fill="none" stroke-width="10" class="stroke-gray-200 dark:stroke-gray-800" />
<path d="M300 -10 L340 330" fill="none" stroke-width="10" class="stroke-gray-200 dark:stroke-gray-800" />
<circle cx="196" cy="150" r="46" class="fill-blue-500/10" />
<path d="M196 98c-20 0-36 16-36 36 0 27 36 58 36 58s36-31 36-58c0-20-16-36-36-36z" class="fill-blue-600 dark:fill-blue-500" />
<circle cx="196" cy="134" r="13" fill="#fff" />
</svg>
<p class="absolute bottom-3 left-3 rounded-md bg-white/90 px-2.5 py-1 text-xs font-medium text-gray-700 backdrop-blur dark:bg-gray-900/90 dark:text-gray-200">24 Harbour Street</p>
</div>
</section>| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'Visit or message us' | Heading text for the card. |
description | string | - | Description |
address | string | '24 Harbour Street' | Address |
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 map is inline SVG, not an embedded tile: no API key, no third-party script, no image to rot, and it recolours for dark mode with `fill-*` utilities. It carries `role="img"` and an `aria-label` so it is announced as a map. The two columns collapse to one below `md`.