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:
- Unexpected costs (shipping, taxes) - 48%
- Account creation required - 24%
- Too long/complicated - 18%
- Didn't trust the site - 17%
- 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>
);
}
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>
);
}
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:
- 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>
);
}
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;
});
}
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>
);
}
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>
);
}
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];
}
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
});
}
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"
/>
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>
);
}
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'
});
}
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)