There's a rite of passage in e-commerce engineering: the moment someone says, "How hard can checkout be?"
Six months later, you're debugging a race condition in cart state, fighting with payment processor webhooks, discovering that your "simple" promo code logic doesn't handle stacking, and realizing that your checkout looks broken on half of Android devices.
We know because we've built checkout infrastructure for hundreds of commerce businesses. And we've seen the same pattern: teams underestimate checkout by 10x, then spend months—and significant budget—building something that converts worse than a drop-in solution would have.
This is the case for hosted checkout. Not as a shortcut, but as the correct engineering decision.
The True Cost of Building Checkout
Let's be honest about what checkout actually requires:
The Feature List Nobody Scopes Correctly
Cart management (add, remove, update quantities, inventory validation)
Guest checkout and authenticated flows
Address entry with validation and autocomplete
Multiple shipping methods with real-time rate calculation
Promo codes, coupons, and stackable discounts
Loyalty points redemption
Multiple payment methods (cards, wallets, BNPL, UPI)
Payment failure handling and retry flows
Order confirmation and receipt generation
Mobile responsiveness (not just "it works"—actually good)
Accessibility compliance
Error handling that doesn't lose cart state
Loading states that don't feel broken
Token management and session handling
Now multiply that by edge cases. What happens when inventory changes mid-checkout? When a promo code expires between cart and payment? When the user switches devices? When they have items from a previous session?
The Real Numbers
Based on our work with commerce teams across the industry:
The math is stark. A senior engineer costs $150,000–$200,000/year fully loaded. Three months of focused checkout work—assuming no other priorities, which never happens—is $37,500–$50,000 in direct cost. Add a designer, QA, and the opportunity cost of what else that team could build, and you're easily at $75,000–$150,000.
And that's just to launch. Checkout is never done. Payment methods evolve. Regulations change. Conversion optimization is continuous. The "build once" fantasy doesn't exist.
Mobile-First Isn't Optional: 90%+ of Purchases Happen on Phones
Here's the number that should drive every checkout decision: over 90% of e-commerce purchases now happen on mobile devices.
Not "mobile is important." Not "we should consider mobile." Ninety percent. Your checkout is a mobile experience that occasionally runs on desktop.
Yet most custom-built checkouts are designed desktop-first, then awkwardly adapted for mobile. The result: tiny tap targets, forms that fight the keyboard, drawers that don't respect safe areas, and conversion rates that crater on the devices where your customers actually shop.
What Mobile-Native Checkout Actually Requires
System Font Rendering
When checkout text renders in San Francisco on iOS and Roboto on Android, it feels like part of the operating system. When it renders in your brand font that wasn't optimized for small screens, it feels foreign. We use system font stacks by default because native feel beats brand consistency at the checkout layer.
Gesture-Based Interactions
Mobile users expect to swipe. Swipe to delete a cart item. Swipe down to dismiss. Pull to refresh. These aren't nice-to-haves—they're the grammar of mobile interaction. A checkout that only responds to taps feels dated.
Keyboard-Aware Layouts
The mobile keyboard covers 40–50% of the screen. When it appears, your checkout needs to reflow intelligently: scroll the active input into view, adjust drawer heights, maintain context. Get this wrong and users are typing blind.
Thumb-Zone Optimization
Primary actions need to be reachable by thumb. On modern phone sizes, that means bottom-positioned CTAs, not buttons at the top of the screen that require hand repositioning.
The Apple Pay Standard
Apple Pay set the bar for mobile checkout: one interaction, biometric confirmation, done. Users now compare every checkout experience to that standard—even when they're not using Apple Pay.
Our hosted checkout is designed to feel that native. Not "almost as good as Apple Pay." Indistinguishable from a first-party experience.
The CSS-Only Drawer: Why We Built Our Own
Mobile drawers—those bottom sheets that slide up for cart and checkout—are deceptively complex. Most UI libraries get them wrong in subtle ways that destroy mobile experience.
The Problem with JavaScript-Based Drawers
Popular libraries like Vaul and Radix Dialog primitives use JavaScript to handle drawer behavior: measuring viewport height, calculating positions, managing scroll locking, repositioning on keyboard appearance.
This approach has fundamental issues:
Keyboard handling is fragile. JavaScript can't reliably detect mobile keyboard state. Libraries guess based on viewport resize, which fires inconsistently across browsers and can false-positive on toolbar hide/show.
Performance suffers. JS-based position calculations run on the main thread. During the exact moment you need smooth 60fps animation—when the user is dragging the drawer—you're competing with JavaScript execution.
Input repositioning breaks. When a mobile keyboard appears, the browser needs to scroll the focused input into view. JS drawer libraries often fight this behavior, causing inputs to be hidden behind the keyboard or to jump erratically.
Our Solution: CSS-Native Drawers
We wrote css-drawer, an MIT-licensed package that handles drawer behavior with modern CSS:
CSS
dvhunits for true dynamic viewport height (respects browser chrome and keyboard)CSS scroll snap for detent positioning (half-open, full-open states)
CSS
overscroll-behaviorfor scroll containment without JS scroll lockingNative HTML
<dialog>for proper focus management and accessibilityZero JavaScript for layout—the browser handles positioning, so input repositioning works natively
The result: drawers that feel native because they use native browser behavior instead of fighting it.
npm install css-drawerIt's MIT licensed. Use it even if you don't use Commerce Engine.
Zero Impact on Core Web Vitals
"Drop-in checkout" sounds like it might hurt performance. An iframe, third-party code, network requests—all the things that typically tank Core Web Vitals.
Here's why our checkout has zero measurable impact:
LCP (Largest Contentful Paint)
The checkout iframe is hidden until the user opens it. Hidden content doesn't participate in LCP calculation. Your product images, your headlines, your hero content—those determine LCP, and we don't touch them.
INP (Interaction to Next Paint)
Initialization is non-blocking. When you call initCheckout(), it returns immediately. The iframe loads asynchronously in the background. Your click handlers, scroll interactions, and form inputs remain responsive.
CLS (Cumulative Layout Shift)
The checkout opens as an overlay with fixed positioning. It doesn't push your content around. When it closes, your page layout is exactly as it was. Zero layout shift contribution.
The Technical Details
Iframe loads with
loading="lazy"semantics—it doesn't block page loadAll checkout assets are served from a global CDN with edge caching
First paint in the iframe (p50): under 200ms
JavaScript bundle: under 50KB gzipped
Zero main-thread blocking during initialization
You can verify this yourself. Run Lighthouse before and after adding our checkout. The scores will be identical.
Integration: Minutes, Not Months
Any Framework
Commerce Engine Checkout works with React, Vue, Svelte, Solid, Angular, and vanilla JavaScript. Not through separate packages with different APIs—through one universal SDK with framework-native bindings.
React / Next.js:
import { useCheckout, initCheckout } from "@commercengine/checkout/react";
import { useEffect } from "react";
// Initialize once at app root
function App({ children }) {
useEffect(() => {
initCheckout({
storeId: "your_store_id",
apiKey: "your_api_key",
});
}, []);
return <>{children}</>;
}
// Use anywhere—no providers, no context
function CartButton() {
const { cartCount, openCart } = useCheckout();
return <button onClick={openCart}>Cart ({cartCount})</button>;
}Vue / Nuxt:
<script setup>
import { useCheckout, initCheckout } from "@commercengine/checkout/vue";
import { onMounted } from "vue";
onMounted(() => {
initCheckout({ storeId: "your_store_id", apiKey: "your_api_key" });
});
const { cartCount, openCart } = useCheckout();
</script>
<template>
<button @click="openCart">Cart ({{ cartCount }})</button>
</template>Svelte / SvelteKit:
<script>
import { checkout, initCheckout } from "@commercengine/checkout/svelte";
import { onMount } from "svelte";
onMount(() => {
initCheckout({ storeId: "your_store_id", apiKey: "your_api_key" });
});
</script>
<button on:click={() => $checkout.openCart()}>
Cart ({$checkout.cartCount})
</button>Vanilla JavaScript:
<script src="https://cdn.commercengine.com/checkout/v1.js" async></script>
<script>
CommerceEngine.onLoad = () => {
CommerceEngine.init({
storeId: "your_store_id",
apiKey: "your_api_key",
});
};
</script>
<button onclick="CommerceEngine.openCart()">Cart</button>Any CMS or Website Builder
The vanilla JavaScript snippet works anywhere you can add a script tag. That includes:
Webflow:
Add to Project Settings → Custom Code → Footer Code. Then use Webflow's button click interactions to call CommerceEngine.openCart().
Framer:
Add as a Code Override or in the Custom Code section. Works with Framer's component system.
WordPress:
Add to your theme's footer.php or use a plugin like Insert Headers and Footers. Works with any theme, any page builder.
Shopify (Hydrogen or Liquid):
Drop into your theme's layout file. Yes, you can use Commerce Engine checkout on Shopify—useful for merchants who want checkout features Shopify doesn't offer.
Static Sites (Hugo, Jekyll, Eleventy):
Add the script to your base template. Three lines of code, enterprise checkout.
The pattern is always the same: load the script, initialize with your credentials, call methods when users interact. No build step required. No framework dependencies. Works today, on any site.
Customization: Your Brand, Our Infrastructure
CSS Variables for Brand Styling
The checkout UI is customizable through CSS variables. Override colors, typography, spacing, and border radius to match your brand:
:root {
--ce-background: oklch(1 0 0);
--ce-foreground: oklch(0.145 0 0);
--ce-card: oklch(1 0 0);
--ce-card-foreground: oklch(0.145 0 0);
--ce-popover: oklch(1 0 0);
--ce-popover-foreground: oklch(0.145 0 0);
--ce-primary: oklch(0.205 0 0);
--ce-primary-foreground: oklch(0.985 0 0);
....
}Pass custom styles during initialization:
initCheckout({
storeId: "your_store_id",
apiKey: "your_api_key",
theme: "light", // or "dark" or "system"
styles: {
"--ce-primary": "#your-brand-color",
},
});Dark Mode Out of the Box
Dark mode isn't an afterthought—it's a first-class feature. Set theme: "dark" or theme: "system" to respect user preference. Colors, shadows, and contrast ratios are all optimized for both modes.
Our Design Philosophy
We believe the default checkout design should work for any brand. It's intentionally neutral: clean typography, clear hierarchy, generous whitespace, and no decorative elements that might clash with your brand identity.
Most merchants use it as-is. The design is timeless enough to not feel dated in two years, and generic enough to not feel mismatched with your site. But when you need customization, the escape hatches are there.
Enterprise: Full White-Label
For enterprise customers, we offer fully white-labeled checkout:
Custom domain (checkout.yourbrand.com)
Complete CSS control beyond variables
Custom email templates
Dedicated infrastructure
SLA guarantees
Contact us for enterprise pricing.
Advanced: Cart Recovery via URL Parameters
Abandoned cart recovery typically requires email flows that link back to your site, reconstruct cart state, and hope nothing breaks. We simplified this.
Commerce Engine Checkout supports URL parameter-based cart initialization:
https://yoursite.com/product?
product_id=prod_xxx&
variant_id=var_xxx&
qty=2When a user hits this URL:
Checkout initializes with the specified product
Cart is populated automatically
Checkout drawer opens immediately
This enables powerful recovery flows:
Abandoned Cart Emails: "You left something behind" → links directly to checkout with their cart restored.
SMS Recovery: "Complete your order" → one tap opens checkout on mobile.
Ad Retargeting: Dynamic product ads link directly to checkout for that product.
Influencer/Affiliate Links: "Get mine here" → opens checkout for the exact variant shown.
Quick Buy Configuration
For "Buy Now" buttons that skip the cart entirely:
initCheckout({
storeId: "your_store_id",
apiKey: "your_api_key",
quickBuy: {
productId: "prod_xxx",
variantId: "var_xxx",
quantity: 1,
},
sessionMode: "force-new", // Clear existing cart
});sessionMode: "force-new" clears any existing cart and starts fresh with just this product. sessionMode: "continue-existing" adds to the current cart. Choose based on your UX goals.
What You Get
Let's summarize what hosted checkout actually delivers:
All maintained, updated, and optimized continuously—by us, not your team.
The Decision Framework
Build checkout yourself if:
Checkout is your core product differentiation
You have regulatory requirements that demand full code ownership
You have 6+ months and $100K+ to invest in checkout alone
You have dedicated engineers for ongoing maintenance
Use hosted checkout if:
You want to ship in days, not months
Your engineering time is better spent on product differentiation
You want mobile conversion rates that match native apps
You want checkout to improve continuously without your involvement
For 95% of commerce businesses, hosted checkout is the correct choice. The 5% who should build custom are the ones for whom checkout is the product.
Get Started
Step 1: Sign up at commercengine.io and get your store credentials.
Step 2: Add the SDK to your project:
npm install @commercengine/checkoutOr use the CDN for no-build-step integration:
<script src="https://cdn.commercengine.com/checkout/v1.js" async></script>Step 3: Initialize and open:
initCheckout({
storeId: "your_store_id",
apiKey: "your_api_key",
});
// That's it. Call openCart() when ready.Your checkout is now live. Mobile-optimized, accessible, performant, and maintained by a team that thinks about checkout every day.
Questions? Check out our documentation or talk to our team.

