Add a checkbox under the checkout button and stop the shopper proceeding until they tick it. Useful for terms acceptance, age gates, and made-to-order acknowledgements.
The checkout event can’t cancel checkout. It fires as a notification just before the browser navigates, so returning false or calling preventDefault does nothing. The only way to gate checkout is to stop the button responding to clicks before it’s pressed, which is what this page shows.Treat this as a UI deterrent, not a legal guarantee. A determined shopper can still reach checkout directly.
How it works
Three pieces:
- A Custom code block below the checkout button renders the checkbox.
- While the box is clear, the block adds a class to the cart’s checkout and express-payment buttons through the shadow root.
- Custom CSS makes that class disable clicks.
Step 1: Add the checkbox block
Add a Custom code block below your Checkout button block, switch it to React component mode, and paste:
Click Compile, then turn on “Use custom template”. The block renders nothing until you do.
The props.cart dependency matters: it re-applies the class after the cart re-renders, which would otherwise wipe it.
Step 2: Add the CSS
In Cart settings → Custom CSS:
pointer-events: none is what actually blocks the click; the opacity just shows the shopper why.
Step 3: Test it
In preview, confirm that:
- The checkout button is dimmed and doesn’t respond to clicks on load.
- Ticking the box enables it; clearing it disables the button again.
- Express payment buttons are gated too.
- Adding or removing an item doesn’t re-enable the button while the box is clear.
That last one is the common failure. If the button re-enables itself after a cart change, the effect isn’t re-running. Check that props.cart is in the dependency array.
Tracking who accepted
The checkbox is browser-only state; it never reaches the order. To record acceptance, stamp it onto every line as a property at add-to-cart time:
Add that to your product form in the theme. Shopify reads properties[...] straight off the form, so the value reaches the order whoever performs the add: Aftersell, the theme, or another app.
A property prefixed with _ stays out of the buyer-facing line but still reaches Shopify, so it appears on the order. This records which terms version was live, not that the shopper ticked the box.
Things to get right
- Only target
cart-external-* classes. The cart-internal-* twins are the cart’s own plumbing, not a handle for your code. See Custom CSS.
- Check
shadowRoot exists before using it. It’s undefined until the cart boots.
- The block renders nothing until the cart loads, so the checkout button is never briefly live before the gate applies.
- A React block that throws renders nothing and the rest of the cart carries on, which here means an unguarded checkout button. Test in preview before publishing.
Where to go next