Simple Contact Form
A centred name, email and message form with inline validation and no chrome.
3 frameworksBeginner
A department/topic radio-card picker above the contact form.
<!--
The topic picker is a real radio group inside a <fieldset>/<legend>, so it is
announced as one labelled group and arrow-keys move between options. The card
look is pure CSS: the native radio is sr-only and has-[:checked] / has-[:focus-visible]
on the <label> paint the selected and focused states — no JS, still keyboard-driven.
-->
<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">How can we help?</h2>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Pick a topic so your message reaches the right team.</p>
<form class="mt-6 space-y-5">
<fieldset>
<legend class="text-sm font-medium text-gray-700 dark:text-gray-300">Choose a topic</legend>
<div class="mt-2 grid gap-3 sm:grid-cols-3">
<label class="flex cursor-pointer flex-col rounded-lg border border-gray-300 p-3 text-sm has-[:checked]:border-blue-600 has-[:checked]:ring-2 has-[:checked]:ring-blue-600 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-blue-600 dark:border-gray-700">
<input type="radio" name="topic" value="sales" class="sr-only" checked />
<span class="font-medium text-gray-900 dark:text-gray-100">Sales</span>
<span class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Plans and pricing</span>
</label>
<label class="flex cursor-pointer flex-col rounded-lg border border-gray-300 p-3 text-sm has-[:checked]:border-blue-600 has-[:checked]:ring-2 has-[:checked]:ring-blue-600 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-blue-600 dark:border-gray-700">
<input type="radio" name="topic" value="support" class="sr-only" />
<span class="font-medium text-gray-900 dark:text-gray-100">Support</span>
<span class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Help with an issue</span>
</label>
<label class="flex cursor-pointer flex-col rounded-lg border border-gray-300 p-3 text-sm has-[:checked]:border-blue-600 has-[:checked]:ring-2 has-[:checked]:ring-blue-600 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-blue-600 dark:border-gray-700">
<input type="radio" name="topic" value="billing" class="sr-only" />
<span class="font-medium text-gray-900 dark:text-gray-100">Billing</span>
<span class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">Invoices and payments</span>
</label>
</div>
</fieldset>
<div>
<label for="cs-email" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Email</label>
<input id="cs-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="cs-message" class="block text-sm font-medium text-gray-700 dark:text-gray-300">Message</label>
<textarea id="cs-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">Submit request</button>
</form>
</section>| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'How can we help?' | Heading text for the card. |
description | string | - | Description |
topics | SupportTopic[] | - | Topics |
submitLabel | string | 'Submit request' | Submit label |
onSubmit | (values: SupportFormValues) => void | - | Called with the form's values when it is submitted. |
className | string | - | Additional classes merged onto the root element. |
The picker is a real radio group inside a `<fieldset>`/`<legend>`, so it is announced as one labelled group and arrow keys move between options. The card styling is pure CSS: the native radio is `sr-only` and `has-[:checked]` / `has-[:focus-visible]` on the label paint the selected and focused states. Pass your own `topics`.