Two-Column Checkout
A checkout form beside a sticky order summary that stacks below the form on mobile.
3 frameworksIntermediate
Contact, shipping and payment in one scrolling form with a single place-order button.
<!--
One form, three fieldsets. Each fieldset owns a <legend> so a screen reader
announces "Contact", "Shipping", "Payment" as it enters the group - the visual
headings and the accessible structure are the same element, not two that can
drift. Card fields are plain inputs with cc-* autocomplete; nothing here
processes a payment.
-->
<form class="mx-auto w-full max-w-lg space-y-6 rounded-2xl border border-gray-200 bg-white p-5 sm:p-6 dark:border-gray-800 dark:bg-gray-950" action="#" method="post" novalidate>
<fieldset class="space-y-4 border-0 p-0">
<legend class="text-sm font-semibold text-gray-900 dark:text-gray-100">Contact</legend>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-email">Email</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-email" name="email" type="email" autocomplete="email" inputmode="email" placeholder="you@example.com" required />
</div>
</fieldset>
<fieldset class="space-y-4 border-0 p-0">
<legend class="text-sm font-semibold text-gray-900 dark:text-gray-100">Shipping address</legend>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-name">Full name</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-name" name="name" type="text" autocomplete="name" placeholder="Ada Lovelace" required />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-street">Address</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-street" name="street-address" type="text" autocomplete="street-address" placeholder="123 Market St" required />
</div>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-city">City</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-city" name="city" type="text" autocomplete="address-level2" placeholder="San Francisco" required />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-zip">Postal code</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-zip" name="postal-code" type="text" autocomplete="postal-code" inputmode="numeric" placeholder="94103" required />
</div>
</div>
</fieldset>
<fieldset class="space-y-4 border-0 p-0">
<legend class="text-sm font-semibold text-gray-900 dark:text-gray-100">Payment</legend>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-card">Card number</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-card" name="cc-number" type="text" autocomplete="cc-number" inputmode="numeric" placeholder="1234 1234 1234 1234" required />
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-exp">Expiry</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-exp" name="cc-exp" type="text" autocomplete="cc-exp" inputmode="numeric" placeholder="MM / YY" required />
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cs-csc">CVC</label>
<input class="mt-1 block w-full rounded-lg border border-gray-300 bg-white px-3 py-2.5 text-sm text-gray-900 placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder:text-gray-500 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" id="cs-csc" name="cc-csc" type="text" autocomplete="cc-csc" inputmode="numeric" placeholder="123" required />
</div>
</div>
</fieldset>
<button class="inline-flex w-full items-center justify-center rounded-lg bg-blue-600 px-5 py-3 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-950" type="submit">Place order</button>
</form>| Prop | Type | Default | Description |
|---|---|---|---|
submitLabel | string | 'Place order' | Submit label |
onSubmit | (e: FormEvent) => void | - | Called with the form's values when it is submitted. |
className | string | - | Additional classes merged onto the root element. |
Each section is a `<fieldset>` with a `<legend>`, so the visual heading and the group a screen reader announces are the same element. Card fields carry `cc-*` autocomplete and `inputMode="numeric"` but process nothing - `onSubmit` defaults to a `preventDefault()` no-op you replace with your own handler.