Blog · · 10 min

Where do express checkout buttons go?

At the top of the checkout page, above the email field and above everything the buyer would otherwise type. The only advantage a wallet button has is that it removes the form, so one placed under the card form removes nothing. Show at most three, name the separator after what sits below it, and render nothing at all when no wallet is available.

By

Top of the checkout page. Above the email field, above the address block, above the card form. That is the answer, and it falls out of a single property: the only thing an express checkout button does better than your form is that it removes the form. A control whose entire value is skipping the typing, placed after the typing, skips nothing. It has become a second way to pay, offered to someone who already paid attention to the first one.

Two placements to refuse outright. Under the card fields, where the buyer arrives having already filled them in. And beside or below the pay button, where two controls of equal weight ask the same question and the page stops saying what to do next.

Why does the position change what the button is worth?

Count the keystrokes. A short domestic guest checkout, seven inputs, no company, no phone:

What the buyer typesCharacters, roughly
Email address24
Full name14
Street line22
Postal code and city14
Card number16
Expiry and security code7
Total97

A wallet button at the top of the page removes all 97. The same button under the card form is reached with 74 already typed, so it removes 23 and asks the buyer to abandon their own work to collect them. That is not a smaller win, it is a different offer, and the second one is easy to decline.

The count is an estimate of typing, not a claim about conversion. It is here to make the geometry visible: the value of the control decays as you push it down the page, and it reaches zero at the bottom.

How many buttons, and in what order?

Three visible, at most. Stripe's Express Checkout Element paints only the wallets the current browser can actually pay with, so on a given device the realistic maximum is already smaller than your configured list. Order them so that the one the device is most likely to honour comes first.

The reason to cap it is not choice paralysis. It is that every extra button pushes the ordinary card path further down, and the card path is the one that works for everybody. Six wallet buttons stacked on a phone means the email field starts below the fold, which is the exact opposite of the thing you installed them for.

![Three heavy brass tokens laid in a row across the top edge of a long blank ruled paper form on dark slate, the form running down and out of the bottom of the frame, lit hard from the left so each token throws a black shadow onto the paper.](/blog/jetons-au-dessus.webp "The tokens sit across the top of the form, not beside the signature line at the bottom. The rest of this article is that arrangement, written in TypeScript.")

What should the separator say?

Or pay with card. Not OR alone, not a bare hairline, not an em dash between two rules.

The separator is the only thing on the screen telling a buyer who does not want a wallet that the normal path still exists below. Naming the destination is what turns it from decoration into an instruction. Keep the words in the DOM as real text, draw the two rules with CSS pseudo-elements, and never push the label into a background image.

If the form below accepts more than a card, say so: Or pay another way. The rule is that the sentence describes what follows it.

Do they belong on the product page too?

In one case, and it is narrow: a single item, with no option left to choose, on a store where nothing in the cart is doing work.

That last condition is the one that gets skipped. A wallet button on a product page is a jump straight over the cart, and everything you built into the cart goes with it.

On the product page you haveA wallet button there means
A quantity tier selectorone unit is bought at the single-unit price
A free shipping threshold barthe remainder is never shown, the order lands under the threshold
An order bump above the totalsthe bump is never rendered
One product, one price, no optionsnothing is lost, put the button there

So the test is not whether the express button is technically available on that page. It is whether skipping the cart costs you anything. On a single-SKU store it costs nothing. On a store with any basket mechanic at all, it removes that mechanic from the order that was most ready to buy.

If a variant is still unselected, the button must be disabled or absent. A wallet sheet opening over an unresolved choice buys whatever the default was, and the buyer discovers which one on the confirmation.

What breaks when the wallet hands you the shipping address?

This is where an otherwise correct integration produces unshippable orders.

If you do not ask for the address, you do not get one. The sheet is configured in the click handler. Omit shippingAddressRequired and the payment succeeds with no destination attached, which surfaces later as a paid order nobody can pick.

Before authorisation the address is redacted. Apple Pay hands back a partial address, enough to price a shipment (country, postal code, locality, administrative area) and releases the street line only once the buyer authorises. Any rate calculation that needs the house number fails at exactly the moment you cannot see it. Price on the partial address, or flat-rate that step.

The address is the wallet's, not the one they would have written. It is whatever was saved in the account, possibly years ago. Render it back on the confirmation screen with a way to correct it, and treat the correction as ordinary rather than exceptional.

Carry the second line through. A wallet can return an apartment or building number. Losing it between the payment object and the shipping label produces parcels that scan as delivered and never arrive, and the failure is invisible from inside the shop. The same warning applies to your typed form, and it is written out in checkout page design, field by field.

// Stripe.js, Express Checkout Element. It paints only the wallets this
// browser can actually pay with, so the button count is not entirely yours.
const elements = stripe.elements({ mode: 'payment', amount: 4800, currency: 'usd' })

const express = elements.create('expressCheckout', {
  buttonHeight: 48,
  paymentMethodOrder: ['apple_pay', 'google_pay', 'link'],
})
express.mount('#express')

express.on('click', (event) => {
  // Resolve inside the handler, before any await. Fetching the line items
  // first and resolving afterwards is the bug where the sheet never opens.
  event.resolve({
    emailRequired: true,
    shippingAddressRequired: true,
    phoneNumberRequired: false,
    lineItems: [{ name: 'Ceremonial matcha, 3 tins', amount: 4800 }],
    shippingRates: [{ id: 'std', displayName: 'Standard, 3 to 5 days', amount: 0 }],
    allowedShippingCountries: ['US', 'CA'],
  })
})

express.on('shippingaddresschange', async (event) => {
  // event.address is the redacted one: enough to price a shipment,
  // not enough to print a label.
  const rates = await priceShipment(event.address)
  if (!rates.length) return event.reject()
  event.resolve({ shippingRates: rates })
})

express.on('confirm', async () => {
  const { error: submitError } = await elements.submit()
  if (submitError) return show(submitError.message)

  // The amount is recomputed server-side here. Never the one above.
  const clientSecret = await createIntentOnServer()
  const { error } = await stripe.confirmPayment({
    elements,
    clientSecret,
    confirmParams: { return_url: `${location.origin}/order/complete` },
  })
  if (error) show(error.message)
})

The amount handed to elements() exists so the sheet can display a total. It is not what gets charged. The charge is whatever the intent your server created says it is, and that is not a distinction you can add later, so it is worth reading the part nobody writes about wiring Stripe before this goes anywhere near live keys.

What do you render when there is no wallet at all?

Something, or nothing, but never a hole.

Desktop Firefox with no Google Pay configured, a Chrome profile with no saved card, a locked-down work laptop: the element renders no buttons. If the surrounding heading and the Or pay with card separator are static markup, the buyer meets a caption with nothing above it.

The ready event is the signal. When nothing is available, availablePaymentMethods comes back undefined.

type Wallets = 'pending' | 'none' | 'some'

export function ExpressBlock({ express }: { express: StripeExpressCheckoutElement }) {
  const [wallets, setWallets] = useState<Wallets>('pending')

  useEffect(() => {
    express.on('ready', ({ availablePaymentMethods }) => {
      setWallets(availablePaymentMethods ? 'some' : 'none')
    })
  }, [express])

  return (
    <>
      {/* collapsed while pending, so a browser with no wallet never shows a gap */}
      <div id="express" hidden={wallets !== 'some'} />
      {wallets === 'some' && <Separator>Or pay with card</Separator>}
      <GuestFields />
      <PayButton />
    </>
  )
}

Do not reserve the height with a skeleton. A 48-pixel band that stays empty forever, sitting directly above the first field, costs every buyer without a wallet more than the brief appearance of the buttons costs the ones who have one. Collapse during pending, accept that the row arrives a beat late, and keep the form underneath in its final position from the first paint.

The fields underneath still have to be the short list, with the right autocomplete tokens, or the buyer who declined the wallet pays for the whole form anyway. That list is in guest checkout, the field list that converts.

Disclosure. We build uxgen, an MCP server that hands a coding agent the checkout and cart components described here, as HTML the merchant keeps. Nothing here is behind it, and the commerce kit is MIT.

FAQ

Where should express checkout buttons be placed on a checkout page?

At the very top, above the email field and above every other input. The value of a wallet button is that it removes the form, so it has to appear before the buyer starts filling the form in. Placed under the card fields it only removes the last few inputs, and placed next to the pay button it competes with the action you want.

How many express checkout buttons should I show?

Three at most, and in practice the browser decides: the Express Checkout Element renders only the wallets the current device can pay with. Cap the list anyway, because each extra button pushes the ordinary card path further down the page, and on a phone a tall stack of wallets moves the email field below the fold.

Should express checkout buttons appear on the product page?

Only when the product page has a single item, no unresolved option, and no cart mechanic. A wallet button jumps straight over the cart, so a quantity tier selector, a free shipping threshold bar or an order bump will never be shown to the buyer who used it. On a single-SKU store it costs nothing and belongs there.

What happens if the buyer has no digital wallet?

The element renders no buttons and the ready event reports no available payment methods, so your surrounding markup has to disappear with it. Hide the container and the separator together rather than reserving space for them, otherwise a browser without a wallet shows an empty band and an orphaned caption above the first field.

uxgen is a service of UXGen AI, LLC — 131 Continental Dr, Suite 305, Newark, DE 19713, United States.

© 2026 UXGen AI, LLC. All rights reserved.