Blog · · 12 min
Guest checkout: the field list that converts
Six inputs plus a payment method. Every other field on a guest checkout is either derivable from one you already have, or removable because nobody downstream reads it. Sort the whole list into keep, derive and remove, put the account offer after the payment, and write the autocomplete tokens, which is the single line of work that removes the most typing.
By uxgen
Six inputs and a payment method: email, full name, street line, postal code, city, country. That ships a parcel. Everything else on a guest checkout belongs in one of two other columns, and naming which one is the whole exercise.
Keep means a human or a system downstream reads it and something breaks without it. Derive means you already hold enough to produce it, so asking is charging the buyer for your own arithmetic. Remove means nobody reads it, or nobody reads it yet.
That third case is the one worth saying out loud. A password is not a bad field, it is a well-timed field asked at the worst possible moment. Move it past the payment and it costs nothing.
The list, sorted
| Field | Verdict | Token | Why |
|---|---|---|---|
| keep | email | receipt, tracking, dispute evidence | |
| Full name | keep | name | printed on the carrier label |
| Street line | keep | address-line1 | the carrier |
| Apartment, floor, building | keep, folded | address-line2 | the carrier, when it survives the trip |
| Postal code | keep | postal-code | routing, and it feeds the next two |
| City | derive | address-level2 | look it up from the postal code, leave it editable |
| State or province | derive | address-level1 | same lookup, in the countries that have one |
| Country | derive | country | one shipping zone means one value, not a select |
| Phone | keep only if the carrier needs it | tel | delivery notification, and say so on the label |
| Company | remove, behind a disclosure | organization | invoicing, for the minority who need it |
| VAT number | remove, behind the same disclosure | none | tax treatment, never a blocker |
| Password | remove | none | nobody, at this moment |
| Confirm email | remove | none | nobody. It catches typos you can catch better |
| Billing address | derive by default | none | same as shipping until the buyer says otherwise |
| How did you hear about us | remove | none | a report nobody opens |
A checkout that starts from this table is short for the reason that matters: not because short is fashionable, but because each removal is defended by a sentence naming who was going to read the value.
Should the name be one field or two?
One, labelled Full name, with autocomplete="name", unless something concrete downstream demands the split.
Concrete means: your carrier API takes firstName and lastName as separate required strings, or your payment processor runs a check that compares them separately. If that is true, split into two and use given-name and family-name, because those tokens fill correctly and a single name field will not.
What is never justified is three fields. A title select, a middle initial, and a suffix are all copied from a paper form that had a different purpose. A carrier prints one line.
The single field also survives contact with names that do not decompose the way a Western form assumes they do. A buyer with one name, or four, or a family name written first, types the thing that appears on their post box and the label prints it.

What can you work out instead of asking?
City and region from the postal code. In most countries this is a deterministic lookup rather than a guess. It removes two inputs from the visible form and, more usefully, removes two chances to mistype a destination. Treat it as a convenience with a failure mode: if the lookup is slow, fails, or returns nothing, the fields have to be there, empty and typeable, immediately.
Country from your shipping zone. If you ship to one country, it is not a question. Render it as text next to the address, not as a select with 195 entries where the first is Afghanistan and the second is Åland Islands.
Billing address from the shipping address. Default them equal, with one checkbox to separate them. The second set of fields is created only when the box is ticked, so it does not exist in the tab order for the majority who never touch it.
// Postal code fills city and region, and is never allowed to hold the form up.
function usePlaceFromPostal(postal: string, country: string) {
const [place, setPlace] = useState<{ city: string; region: string } | null>(null)
useEffect(() => {
if (postal.trim().length < 4) return
const ctrl = new AbortController()
const id = setTimeout(async () => {
try {
const r = await fetch(`/api/place?c=${country}&p=${encodeURIComponent(postal)}`, {
signal: ctrl.signal,
})
if (r.ok) setPlace(await r.json())
} catch {
// A failed lookup is a non-event. The two fields stay editable and empty.
}
}, 250)
return () => { clearTimeout(id); ctrl.abort() }
}, [postal, country])
return place
}
Two rules attach to that hook. The derived value goes into the input as a real value the buyer can overwrite, never as a disabled field or a placeholder. And nothing waits on it: the pay button stays enabled while the lookup is in flight, because a buyer who typed their own city is already finished.
Where does the second address line go?
Behind a disclosure, and then all the way to the label.
Rendered as a permanently visible input, it reads as a required unknown to the majority of buyers who live in a house. Rendered as a placeholder inside the street field, it is lost the moment they start typing. The shape that works is a small button, Add apartment, suite, floor, which reveals a real labelled input and moves focus into it.
The part that is not a design question: once collected, it has to reach the carrier. A flat or building number captured in a form and dropped between the order record and the shipping label produces parcels that scan as delivered and never arrive, and from inside the shop the tracking looks perfect. Collect it or do not, but never collect it and drop it.
When is a phone number a legitimate field?
When a carrier reads it, and then you say which one does.
Phone with an asterisk is an unexplained demand for personal data at the moment the buyer is deciding whether to trust you. Phone, so the courier can text you a delivery window is a service. Same input, different transaction.
If no carrier in your setup uses it, remove it. If one does and the other does not, ask for it only on the shipping methods that need it, after the method has been chosen. And keep it optional unless a shipment is genuinely rejected without one, because an optional field that most people fill costs you nothing and a required field that some people refuse costs you the order.
// The fields that survive, with the tokens that make the browser type for them.
<form noValidate onSubmit={onSubmit}>
<Field label="Email" name="email"
type="email" autoComplete="email" inputMode="email" required />
<Field label="Full name" name="name" autoComplete="name" required />
<Field label="Street address" name="line1" autoComplete="address-line1" required />
{showLine2
? <Field label="Apartment, suite, floor" name="line2" autoComplete="address-line2" autoFocus />
: <button type="button" className="lien" onClick={() => setShowLine2(true)}>
Add apartment, suite, floor
</button>}
<div className="paire">
<Field label="Postal code" name="postal"
autoComplete="postal-code" inputMode="numeric" required />
<Field label="City" name="city" autoComplete="address-level2" required
value={city} onChange={setCity} />
</div>
{/* One shipping zone: a sentence, not a select. */}
<p className="note">Shipping to the United States. <a href="/shipping">Other countries</a></p>
{business
? <>
<Field label="Company" name="company" autoComplete="organization" />
<Field label="VAT number (optional)" name="vat" />
</>
: <button type="button" className="lien" onClick={() => setBusiness(true)}>
I am buying for a company
</button>}
<PaymentElement />
<PayButton>Pay $48.00</PayButton>
</form>
autoComplete is the line that does the most and gets written the least. WCAG 2.1 added success criterion 1.3.5, Identify Input Purpose, at level AA, which asks that inputs collecting information about the user carry a programmatic purpose. In HTML that purpose is the autocomplete token. Getting them right is free, it is a conformance item, and it is the difference between a buyer tapping once and a buyer typing their street.
The token that is worth adding beyond this form: one-time-code on the field where an SMS confirmation code is entered, so the phone offers the code from the notification instead of sending the buyer out to their messages app during a 3D Secure step.
And never write autocomplete="off" on an address or payment field. Browsers increasingly ignore it there, so the only reliable effect is that the ones which still honour it make your form worse.
Validation that does not argue with the buyer
Three rules, and they are implementable in about fifteen lines.
- Nothing is marked invalid until the field has been left. A postal code flagged red on its third character is the form contradicting someone in the middle of a sentence.
- Once a field has errored, and only then, re-check on every keystroke, so the message disappears the instant it is fixed rather than on the next blur.
- A failed check never clears a value. Wiping a card number on a rejection is the fastest route from a typo to an abandoned order.
type FieldState = { value: string; touched: boolean; error: string | null }
function check(state: FieldState, rule: (v: string) => string | null): FieldState {
// Before the first blur, never show an error, whatever the rule says.
if (!state.touched) return { ...state, error: null }
return { ...state, error: rule(state.value) }
}
function onBlur(state: FieldState, rule: (v: string) => string | null) {
return check({ ...state, touched: true }, rule)
}
function onChange(state: FieldState, value: string, rule: (v: string) => string | null) {
// Re-check while typing only if this field is already showing an error.
return state.error ? check({ ...state, value }, rule) : { ...state, value, error: null }
}
Put the message next to the input, reference it with aria-describedby, and on submit move focus to the first failing field rather than scrolling the page to a summary at the top.
When do you ask for the account?
After the money has moved, on the confirmation page, with the email already known and one password field.
At that point the buyer has a reason to want an account (their order is in it) and creating one cannot cost you the sale, because the sale is finished. Before the payment it is a wall you built yourself and then asked people to climb.
The same screen carries the order number, the amount and the delivery estimate, in that order, and it is a component rather than a receipt: the thank-you page is where that argument is made properly.
The parts of a checkout that are not about fields at all (the regulated button label, when the delivery cost has to be visible, one page against several) are in checkout page design, field by field. And the fastest way to skip this form entirely, for the buyers who can, is a wallet row at the top of the page, which has its own placement rule: where do express checkout buttons go.
Disclosure. We build uxgen, an MCP server that hands a coding agent commerce components as HTML the merchant owns, with these tokens already in them. It is one way to get this list into a build; typing it yourself from the table above is another, and it works.
FAQ
How many fields should a guest checkout have?
Six inputs and a payment method: email, full name, street line, postal code, city and country, with an optional folded line for an apartment or floor. City, region and country can usually be derived rather than asked, and every field beyond the six has to name the person or system downstream who reads it.
Should full name be one field or two?
One field with autocomplete="name", unless a carrier or processor downstream genuinely requires the parts separately, in which case use two with given-name and family-name. Never three: a title, a middle initial and a suffix come from a paper form with a different purpose, and a carrier prints a single line.
Can I ask for a phone number at checkout?
Only if a carrier reads it, and then say so in the label rather than marking it required with an asterisk. Phone, so the courier can text you a delivery window is a service; a bare required Phone field is an unexplained demand for personal data at the exact moment the buyer is deciding whether to trust the shop.
When should I ask a guest to create an account?
On the confirmation page, after payment. The email is already known, so the offer is one password field, and at that point an account is something the buyer may want because their order is inside it. Asked before payment, it is a wall the shop built and then asked people to climb.