Single-Page Checkout
Contact, shipping and payment in one scrolling form with a single place-order button.
3 frameworksIntermediate
A four-step wizard - contact, shipping, payment, review - with a progress bar and step tracker.
<!--
Static markup showing step 2 of 4. In the React tabs the current step is state;
here the progressbar and aria-current are hardcoded to illustrate the pattern.
The <ol> is the visible tracker; role="progressbar" on the fill gives assistive
tech a single "2 of 4" reading instead of four ambiguous list items.
-->
<div class="mx-auto w-full max-w-md rounded-2xl border border-gray-200 bg-white p-5 sm:p-6 dark:border-gray-800 dark:bg-gray-950">
<div role="progressbar" aria-valuenow="2" aria-valuemin="1" aria-valuemax="4" aria-label="Checkout step 2 of 4" class="h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-800">
<div class="h-full rounded-full bg-blue-600 transition-[width] duration-500 ease-out motion-reduce:transition-none dark:bg-blue-500" style="width:50%"></div>
</div>
<ol class="mt-3 flex flex-wrap gap-x-4 gap-y-1 text-xs font-medium">
<li class="text-gray-400 dark:text-gray-500">Contact</li>
<li class="text-blue-600 dark:text-blue-400" aria-current="step">Shipping</li>
<li class="text-gray-400 dark:text-gray-500">Payment</li>
<li class="text-gray-400 dark:text-gray-500">Review</li>
</ol>
<div class="mt-5 space-y-4">
<h2 class="text-sm font-semibold text-gray-900 dark:text-gray-100">Shipping</h2>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300" for="cm-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="cm-street" name="street-address" type="text" autocomplete="street-address" placeholder="123 Market St" />
</div>
</div>
<div class="mt-6 flex items-center justify-between gap-3">
<button class="inline-flex items-center justify-center rounded-lg border border-gray-300 px-4 py-2.5 text-sm font-semibold text-gray-700 transition-colors hover:bg-gray-100 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:border-gray-700 dark:text-gray-300 dark:hover:bg-gray-800 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950" type="button">Back</button>
<button class="inline-flex 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-950" type="button">Continue</button>
</div>
</div>| Prop | Type | Default | Description |
|---|---|---|---|
steps | string[] | ['Contact','Shipping','Payment','Review'] | The ordered steps of the process. |
onComplete | () => void | - | On complete |
className | string | - | Additional classes merged onto the root element. |
The current step is state; the fill is a `role="progressbar"` with live `aria-valuenow`/`max` so assistive tech reads "2 of 4" instead of four ambiguous list items, and the active step carries `aria-current="step"`. Pass your own `steps` labels and an `onComplete` for the final step.