Magento 2's default quantity field is a plain text input — customers can type "0", "999", or "abc", and only find out at validation time that it won't fly. A dropdown fixes that: one tap on mobile, only valid quantities offered, no typing at all. In this tutorial we'll replace the qty input with a dropdown on both the product page and the cart page. Fair warning before the code: both changes are template overrides you'll maintain through theme and Magento upgrades — if you'd rather configure than code, the Quantity Dropdown extension is the ready-made option, with admin-controlled ranges, increments, and per-product settings.
Step 1: Create the Custom Module
Both overrides will live in one small module — we'll call it Mageants_QuantityDropdown (replace the vendor/module names with your own). Two files to start:
Create app/code/Mageants/QuantityDropdown/registration.php:
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Mageants_QuantityDropdown',
__DIR__
);
|
Create app/code/Mageants/QuantityDropdown/etc/module.xml:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Mageants_QuantityDropdown">
<sequence>
<module name="Magento_Catalog"/>
<module name="Magento_Checkout"/>
</sequence>
</module>
</config>
|
Adding a Quantity Dropdown to the Product Page
The qty field on the product page is rendered by the product.info.addtocart block's template. The correct way to swap the input for a dropdown is to override that template — adding a separate qty block would leave you with two name="qty" fields fighting each other in the same form.
Step 2: Point the Block at Your Template
Create app/code/Mageants/QuantityDropdown/view/frontend/layout/catalog_product_view.xml:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="product.info.addtocart">
<arguments>
<argument name="template" xsi:type="string">Mageants_QuantityDropdown::product/view/addtocart.phtml</argument>
</arguments>
</referenceBlock>
</body>
</page>
|
Step 3: Create the addtocart.phtml Template
Create app/code/Mageants/QuantityDropdown/view/frontend/templates/product/view/addtocart.phtml. This reproduces the core add-to-cart box with a select in place of the input:
<?php
/** @var \Magento\Catalog\Block\Product\View $block */
$_product = $block->getProduct();
?>
<?php if ($_product->isSaleable()) : ?>
<div class="box-tocart">
<div class="fieldset">
<?php if ($block->shouldRenderQuantity()) : ?>
<div class="field qty">
<label class="label" for="qty"><span><?= $block->escapeHtml(__('Qty')) ?></span></label>
<div class="control">
<select id="qty" name="qty" class="input-text qty"
data-validate="<?= $block->escapeHtmlAttr(json_encode($block->getQuantityValidators())) ?>">
<?php for ($i = 1; $i <= 10; $i++) : ?>
<option value="<?= (int) $i ?>"><?= (int) $i ?></option>
<?php endfor; ?>
</select>
</div>
</div>
<?php endif; ?>
<div class="actions">
<button type="submit"
title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"
class="action primary tocart"
id="product-addtocart-button">
<span><?= $block->escapeHtml(__('Add to Cart')) ?></span>
</button>
<?= $block->getChildHtml('', true) ?>
</div>
</div>
</div>
<script type="text/x-magento-init">
{
"#product_addtocart_form": {
"Magento_Catalog/js/validate-product": {}
}
}
</script>
<?php endif; ?>
|
The dropdown offers quantities 1–10 here for simplicity. For a smarter range, cap the loop with the product's maximum sale quantity — $_product->getExtensionAttributes()->getStockItem() exposes getMaxSaleQty() — so the dropdown never offers more than a customer is allowed to buy.
Displaying the Quantity Dropdown on the Cart Page
The cart item row — image, name, price, qty, subtotal, actions — is all rendered by one template: Magento_Checkout::cart/item/default.phtml. We'll point the renderer at our copy of it and change only the qty field. Don't replace it with a template containing just the qty markup — that would wipe out the entire cart row.
Step 4: Override the Cart Item Renderer Template
Create app/code/Mageants/QuantityDropdown/view/frontend/layout/checkout_cart_item_renderers.xml:
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.cart.item.renderers.default">
<arguments>
<argument name="template" xsi:type="string">Mageants_QuantityDropdown::cart/item/default.phtml</argument>
</arguments>
</referenceBlock>
</body>
</page>
|
Step 5: Copy the Core Template and Swap the Qty Field
Copy the full core file vendor/magento/module-checkout/view/frontend/templates/cart/item/default.phtml into your module at app/code/Mageants/QuantityDropdown/view/frontend/templates/cart/item/default.phtml — the whole row markup must stay intact. Then find the qty input inside it:
<input id="cart-<?= $block->escapeHtmlAttr($_item->getId()) ?>-qty"
name="cart[<?= $block->escapeHtmlAttr($_item->getId()) ?>][qty]"
data-cart-item-id="<?= $block->escapeHtmlAttr($_item->getSku()) ?>"
value="<?= $block->escapeHtmlAttr($block->getQty()) ?>"
type="number"
size="4"
step="any"
title="<?= $block->escapeHtmlAttr(__('Qty')) ?>"
class="input-text qty"
data-validate="{required:true,'validate-greater-than-zero':true}"/>
|
and replace just that input with the dropdown — same id, name, and data-cart-item-id, with the item's current quantity preselected:
<select id="cart-<?= $block->escapeHtmlAttr($_item->getId()) ?>-qty"
name="cart[<?= $block->escapeHtmlAttr($_item->getId()) ?>][qty]"
data-cart-item-id="<?= $block->escapeHtmlAttr($_item->getSku()) ?>"
title="<?= $block->escapeHtmlAttr(__('Qty')) ?>"
class="qty">
<?php for ($i = 1; $i <= 10; $i++) : ?>
<option value="<?= (int) $i ?>" <?= ((int) $block->getQty() === $i) ? 'selected="selected"' : '' ?>>
<?= (int) $i ?>
</option>
<?php endfor; ?>
</select>
|
Enable the Module and Deploy
Run from the Magento root, then hard-refresh the storefront:
php bin/magento module:enable Mageants_QuantityDropdown php bin/magento setup:upgrade php bin/magento setup:static-content:deploy -f php bin/magento cache:flush |
One heads-up before you ship it: these templates target the default Luma-based frontend. If your store runs a Hyvä theme, the product and cart markup is different (Tailwind-based templates in the theme), so the same idea applies but the files you override won't be these ones.
Conclusion
With one small module and two template overrides, both the product page and the cart now offer a tap-friendly quantity dropdown instead of a free-typed text field — no invalid quantities, less friction on mobile. Remember the trade-off from the intro: overridden templates are yours to re-verify whenever the theme or Magento core updates them, which is exactly the maintenance the Quantity Dropdown extension exists to spare you — including admin-configurable ranges and increments per product. Either way, your customers get a cleaner way to buy more than one.