> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Require terms before checkout

> A Cart SDK use case that adds a terms and conditions checkbox to the Aftersell Cart and blocks checkout until the shopper accepts.

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.

<Warning>
  **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.
</Warning>

## How it works

Three pieces:

1. A **Custom code block** below the checkout button renders the checkbox.
2. While the box is clear, the block adds a class to the cart's checkout and express-payment buttons through the [shadow root](/aftersell/cart/sdk-overview#shadowroot).
3. **Custom CSS** makes that class disable clicks.

## Step 1: Add the checkbox block

Add a [Custom code block](/aftersell/cart/custom-code-blocks) below your Checkout button block, switch it to **React component** mode, and paste:

```jsx theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
function CustomCode(props) {
  const [accepted, setAccepted] = useState(false);

  useEffect(() => {
    const root = window.aftersell?.cart?.shadowRoot;
    if (!root) return;

    const targets = root.querySelectorAll(
      '.cart-external-checkout-button, .cart-external-express-buttons'
    );
    targets.forEach((el) => el.classList.toggle('checkout-gated', !accepted));
  }, [accepted, props.cart]);

  return (
    <div style={{ width: '100%', fontSize: '14px', textAlign: 'center' }}>
      <label style={{ display: 'flex', gap: '6px', justifyContent: 'center', alignItems: 'center' }}>
        <input
          type="checkbox"
          checked={accepted}
          onChange={(e) => setAccepted(e.target.checked)}
        />
        <span>
          I accept the{' '}
          <a href="https://yourstore.com/terms" target="_blank" rel="noopener noreferrer">
            Terms &amp; Conditions
          </a>
        </span>
      </label>
      {!accepted && (
        <div style={{ color: '#c00', fontWeight: 600, marginTop: '6px' }}>
          Please accept the terms above to continue.
        </div>
      )}
    </div>
  );
}
```

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**:

```css theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
.checkout-gated {
  pointer-events: none !important;
  opacity: 0.5 !important;
}
```

`pointer-events: none` is what actually blocks the click; the opacity just shows the shopper why.

## Step 3: Test it

In [preview](/aftersell/cart/previewing-carts), 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:

```html theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
<input type="hidden" name="properties[_terms_version]" value="2026-01">
```

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](/aftersell/cart/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

* **[Custom code blocks](/aftersell/cart/custom-code-blocks)**: React mode and its props.
* **[Custom CSS](/aftersell/cart/custom-css)**: the public class convention.
* **[Events](/aftersell/cart/sdk-events#checkout)**: what the `checkout` event can and can't do.
