DEV Community

Joshua Matthews
Joshua Matthews

Posted on

Stop Losing Sales: Checkout Optimisations That Actually Convert

Average cart abandonment rate: 70%. That's 7 out of 10 people who added items to cart and left without buying.

Most of this is fixable. Here's how.

The Drop-Off Points

Users abandon carts at predictable stages:

  1. Unexpected costs (shipping, taxes) - 48%
  2. Account creation required - 24%
  3. Too long/complicated - 18%
  4. Didn't trust the site - 17%
  5. Website errors - 13%

Address these, and you've addressed most abandonment.

Guest Checkout is Non-Negotiable

Forcing account creation kills conversions. Period.

// Good: Guest checkout as default
function CheckoutOptions() {
  return (
    <div className="checkout-options">
      <div className="guest-option primary">
        <h3>Continue as Guest</h3>
        <p>No account needed. We'll save your details for faster checkout next time.</p>
        <button onClick={startGuestCheckout}>Continue</button>
      </div>

      <div className="account-option secondary">
        <p>Have an account? <a href="/login">Sign in</a> for faster checkout</p>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Offer account creation AFTER purchase: "Want to track your order easily? Create an account with one click."

Show Total Cost Early

Surprise fees at checkout are the #1 conversion killer.

// Cart component with real-time totals
function CartSummary({ items, postcode }) {
  const [shipping, setShipping] = useState(null);

  // Calculate shipping as soon as we have a postcode
  useEffect(() => {
    if (postcode) {
      calculateShipping(postcode).then(setShipping);
    }
  }, [postcode]);

  const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const tax = subtotal * 0.20; // 20% VAT

  return (
    <div className="cart-summary">
      <div className="line">
        <span>Subtotal</span>
        <span>£{subtotal.toFixed(2)}</span>
      </div>
      <div className="line">
        <span>Shipping</span>
        <span>{shipping ? ${shipping.toFixed(2)}` : 'Enter postcode'}</span>
      </div>
      <div className="line">
        <span>VAT (included)</span>
        <span>£{tax.toFixed(2)}</span>
      </div>
      <div className="line total">
        <span>Total</span>
        <span>£{(subtotal + (shipping || 0)).toFixed(2)}</span>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Better yet: include shipping in product prices. "Free shipping" converts better than "£50 + £5 shipping".

Reduce Form Fields

Every field you remove increases conversions.

Essential fields:

  • Email
  • Shipping address
  • Payment info

Usually unnecessary:

  • Phone number (unless needed for delivery)
  • Company name
  • Separate billing address (make it a checkbox)
  • Middle name
// Optimised address form
function AddressForm({ onSubmit }) {
  const [sameAsBilling, setSameAsBilling] = useState(true);

  return (
    <form onSubmit={onSubmit}>
      <input name="email" type="email" placeholder="Email" required />

      <input name="name" placeholder="Full name" required />
      <input name="address1" placeholder="Address" required />
      <input name="address2" placeholder="Apartment, suite, etc. (optional)" />
      <div className="row">
        <input name="city" placeholder="City" required />
        <input name="postcode" placeholder="Postcode" required />
      </div>

      <label className="checkbox">
        <input 
          type="checkbox" 
          checked={sameAsBilling}
          onChange={(e) => setSameAsBilling(e.target.checked)}
        />
        Billing address same as shipping
      </label>

      {!sameAsBilling && <BillingAddressFields />}
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Address Autocomplete Saves Time

Use the Google Places API or similar:

// Address autocomplete setup
function initAddressAutocomplete(inputElement) {
  const autocomplete = new google.maps.places.Autocomplete(inputElement, {
    componentRestrictions: { country: 'gb' },
    fields: ['address_components', 'formatted_address'],
    types: ['address']
  });

  autocomplete.addListener('place_changed', () => {
    const place = autocomplete.getPlace();
    const address = parseAddressComponents(place.address_components);

    // Auto-fill other fields
    document.querySelector('[name="city"]').value = address.city;
    document.querySelector('[name="postcode"]').value = address.postcode;
  });
}
Enter fullscreen mode Exit fullscreen mode

Users love not typing their full address.

Progress Indicator

Show users where they are:

function CheckoutProgress({ currentStep }) {
  const steps = ['Cart', 'Shipping', 'Payment', 'Confirmation'];

  return (
    <div className="checkout-progress">
      {steps.map((step, index) => (
        <div 
          key={step}
          className={`step ${index <= currentStep ? 'completed' : ''}`}
        >
          <span className="step-number">{index + 1}</span>
          <span className="step-name">{step}</span>
        </div>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Knowing there are only 3 steps reduces anxiety.

Trust Signals Matter

Place these near your payment form:

function TrustSignals() {
  return (
    <div className="trust-signals">
      <div className="signal">
        <LockIcon />
        <span>256-bit SSL Encryption</span>
      </div>
      <div className="signal">
        <ShieldIcon />
        <span>Secure Payment</span>
      </div>
      <div className="payment-icons">
        <img src="/visa.svg" alt="Visa" />
        <img src="/mastercard.svg" alt="Mastercard" />
        <img src="/amex.svg" alt="American Express" />
        <img src="/paypal.svg" alt="PayPal" />
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Save Cart State

Users leave and come back. Don't make them start over:

// Persist cart to localStorage
function useCart() {
  const [cart, setCart] = useState(() => {
    const saved = localStorage.getItem('cart');
    return saved ? JSON.parse(saved) : [];
  });

  useEffect(() => {
    localStorage.setItem('cart', JSON.stringify(cart));
  }, [cart]);

  return [cart, setCart];
}
Enter fullscreen mode Exit fullscreen mode

For logged-in users, sync to your database.

Abandoned Cart Recovery

Capture email early, then follow up:

// Capture email at first checkout step
async function saveCheckoutProgress(email, cart) {
  await fetch('/api/checkout/save-progress', {
    method: 'POST',
    body: JSON.stringify({ email, cart, step: 'shipping' })
  });
}

// Server-side: Schedule recovery email
// Trigger 1 hour after abandonment
async function scheduleAbandonmentEmail(checkoutId) {
  await queue.add('send-abandonment-email', { checkoutId }, {
    delay: 60 * 60 * 1000 // 1 hour
  });
}
Enter fullscreen mode Exit fullscreen mode

A well-timed "You left something behind" email recovers 5-10% of abandoned carts.

Mobile-First Checkout

Over 60% of e-commerce traffic is mobile. Test your checkout on phones:

  • Large touch targets (minimum 44px)
  • Proper input types (type="tel", type="email", inputmode="numeric")
  • No horizontal scrolling
  • Sticky "Continue" button
<input
  type="text"
  inputMode="numeric"
  pattern="[0-9]*"
  placeholder="Card number"
  autoComplete="cc-number"
/>
Enter fullscreen mode Exit fullscreen mode

Express Checkout Options

Apple Pay, Google Pay, and PayPal Express can cut checkout time to seconds:

function ExpressCheckout({ cart }) {
  return (
    <div className="express-checkout">
      <p>Express checkout</p>
      <div className="express-buttons">
        <ApplePayButton cart={cart} />
        <GooglePayButton cart={cart} />
        <PayPalButton cart={cart} />
      </div>
      <div className="divider">or continue below</div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Users with saved payment details convert at much higher rates.

The Conversion Checklist

  • [ ] Guest checkout available
  • [ ] Total cost visible from cart
  • [ ] Progress indicator shown
  • [ ] Minimal form fields
  • [ ] Address autocomplete
  • [ ] Trust signals near payment
  • [ ] Mobile-optimised
  • [ ] Express checkout options
  • [ ] Cart saved across sessions
  • [ ] Abandonment recovery in place

Measure Everything

Track your funnel:

// Analytics events at each step
function trackCheckoutStep(step, cart) {
  analytics.track('Checkout Step Viewed', {
    step,
    cart_value: cart.total,
    item_count: cart.items.length
  });
}

// Track drop-offs
function trackCheckoutAbandoned(step, cart) {
  analytics.track('Checkout Abandoned', {
    step,
    cart_value: cart.total,
    reason: 'page_unload'
  });
}
Enter fullscreen mode Exit fullscreen mode

You can't improve what you don't measure.


E-commerce that converts is what we build at LogicLeap. Every checkout we create is tested, optimised, and built to turn browsers into buyers.

Top comments (0)