Classic WooCommerce Shop Loop
Use this when building or reviewing shop archives, product category/tag/attribute archives, product grids, related product grids, and product cards in a classic WooCommerce theme.
When to Use This Skill
- Editing
woocommerce/archive-product.php.
- Editing
woocommerce/content-product.php.
- Changing product card markup, image size, title, price, rating, sale badge, or add-to-cart output.
- Adjusting result count, ordering, pagination, no-products state, rows, or columns.
- Reviewing custom product loops in templates.
Archive Skeleton
Keep Woo's archive loop contract.
do_action( 'woocommerce_before_main_content' );
do_action( 'woocommerce_shop_loop_header' );
if ( woocommerce_product_loop() ) {
do_action( 'woocommerce_before_shop_loop' );
woocommerce_product_loop_start();
if ( wc_get_loop_prop( 'total' ) ) {
while ( have_posts() ) {
the_post();
do_action( 'woocommerce_shop_loop' );
wc_get_template_part( 'content', 'product' );
}
}
woocommerce_product_loop_end();
do_action( 'woocommerce_after_shop_loop' );
} else {
do_action( 'woocommerce_no_products_found' );
}
do_action( 'woocommerce_after_main_content' );
Rules:
- Use
woocommerce_product_loop() instead of only checking have_posts().
- Use
woocommerce_product_loop_start() and woocommerce_product_loop_end() so loop wrappers and subcategories work.
- Use
wc_get_loop_prop( 'total' ) before rendering products.
- Preserve
woocommerce_before_shop_loop, woocommerce_after_shop_loop, and woocommerce_no_products_found.
- Do not build shop archives with a raw
WP_Query unless this is a deliberate secondary product section.
Product Card Contract
content-product.php must preserve the product object guard and visibility check.
global $product;
if ( ! is_a( $product, WC_Product::class ) || ! $product->is_visible() ) {
return;
}
?>
<li <?php wc_product_class( '', $product ); ?>>
<?php
do_action( 'woocommerce_before_shop_loop_item' );
do_action( 'woocommerce_before_shop_loop_item_title' );
do_action( 'woocommerce_shop_loop_item_title' );
do_action( 'woocommerce_after_shop_loop_item_title' );
do_action( 'woocommerce_after_shop_loop_item' );
?>
</li>
Rules:
- Keep
wc_product_class( '', $product ).
- Preserve default hook positions unless intentionally replacing output.
- Do not echo product title, price, image, or add-to-cart from raw postmeta.
- Use Woo template functions or
WC_Product methods.
- Product cards should remain valid list items when inside
woocommerce_product_loop_start().
Hook Map for Cards
Default product card hooks:
woocommerce_before_shop_loop_item: product link open.
woocommerce_before_shop_loop_item_title: sale flash and thumbnail.
woocommerce_shop_loop_item_title: product title.
woocommerce_after_shop_loop_item_title: rating and price.
woocommerce_after_shop_loop_item: product link close and add-to-cart.
Example: move rating below add-to-cart.
add_action( 'after_setup_theme', 'mytheme_product_card_hooks' );
function mytheme_product_card_hooks() {
remove_action( 'woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_rating', 5 );
add_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_rating', 15 );
}
Rules:
- Use exact priorities from
wc-template-hooks.php.
- Prefer hook changes over copying
content-product.php.
- Test simple, variable, external, grouped, out-of-stock, sale, and hidden products.
Add-to-Cart Accessibility
Woo 10.x loop add-to-cart provides:
aria-label from $product->add_to_cart_description().
- optional
aria-describedby screen-reader text.
role="button" for AJAX add-to-cart links when configured.
data-success_message for simple products.
Rules:
- Do not remove
aria-label, aria-describedby, or screen-reader text from loop/add-to-cart.php.
- If filtering
woocommerce_loop_add_to_cart_link, preserve product-specific accessible names.
- Do not replace add-to-cart with a generic "Buy" link for all products.
- Variable/external/grouped products often need a view/select-options flow, not AJAX add-to-cart.
Images and Grid
Rules:
- Product card images should use
woocommerce_thumbnail.
- Control catalog image width through Woo theme support or Woo image settings.
- Do not use full-size images in product grids.
- Preserve responsive image attributes generated by Woo/WP.
- Set CSS grid/flex rules so
columns-N classes do not create overflow.
Result Count, Ordering, Pagination
Default hooks:
woocommerce_before_shop_loop: notices at 10, result count at 20, ordering at 30.
woocommerce_after_shop_loop: pagination at 10.
Rules:
- Preserve result count and ordering unless the design has a replacement.
- Preserve
woocommerce_output_all_notices on archives.
- Use Woo pagination; do not manually build
paged links.
- The pagination template has an accessible
aria-label.
Custom Product Sections
For secondary product grids, use Woo product queries carefully.
Rules:
- Prefer Woo shortcodes/blocks only when the theme is intentionally using them.
- For PHP sections, use
wc_get_products() for product objects or WP_Query only when WordPress loop behavior is required.
- If using
WP_Query, call wp_reset_postdata() after the loop.
- Render each product with
wc_get_template_part( 'content', 'product' ) when you want standard card behavior.
- Set loop props such as columns only for the local section and restore expectations afterward.
Review Checklist
- Archive templates keep Woo wrapper hooks.
- Product loops use Woo loop helpers.
- Product cards preserve product guard, visibility, and
wc_product_class().
- Hook changes are preferred over template copies.
- Add-to-cart accessibility attributes are preserved.
- Product images use Woo image sizes.
- Result count, ordering, notices, pagination, and no-products states work.
- Product categories/tags/attributes use Woo taxonomy archive flow.
- No raw price/stock/meta output bypasses
WC_Product methods.
Common Mistakes
- Replacing
woocommerce_product_loop_start() with a custom <div> and breaking subcategories/plugins.
- Removing
woocommerce_output_all_notices from archives.
- Rendering hidden products because the
is_visible() guard was deleted.
- Losing AJAX add-to-cart classes/data attributes.
- Hardcoding "Add to cart" without product-specific ARIA.
- Using
WP_Query and forgetting wp_reset_postdata().
References
1---2name: classic-woocommerce-shop-loop3description: Build or audit WooCommerce shop/archive loops and product cards in a classic PHP theme. Covers `archive-product.php`, `content-product.php`, product taxonomy templates, `woocommerce_product_loop()`, `woocommerce_product_loop_start/end()`, `wc_get_loop_prop()`, `wc_get_template_part( 'content', 'product' )`, `wc_product_class()`, loop hooks, product cards, sale flash, thumbnails, ratings, price, add-to-cart ARIA, result count, ordering, pagination, no-products state, grid columns, and avoiding raw `WP_Query`/postmeta product loops.4---56# Classic WooCommerce Shop Loop78Use this when building or reviewing shop archives, product category/tag/attribute archives, product grids, related product grids, and product cards in a classic WooCommerce theme.910## When to Use This Skill1112- Editing `woocommerce/archive-product.php`.13- Editing `woocommerce/content-product.php`.14- Changing product card markup, image size, title, price, rating, sale badge, or add-to-cart output.15- Adjusting result count, ordering, pagination, no-products state, rows, or columns.16- Reviewing custom product loops in templates.1718## Archive Skeleton1920Keep Woo's archive loop contract.2122```php23do_action( 'woocommerce_before_main_content' );24do_action( 'woocommerce_shop_loop_header' );2526if ( woocommerce_product_loop() ) {27 do_action( 'woocommerce_before_shop_loop' );2829 woocommerce_product_loop_start();3031 if ( wc_get_loop_prop( 'total' ) ) {32 while ( have_posts() ) {33 the_post();34 do_action( 'woocommerce_shop_loop' );35 wc_get_template_part( 'content', 'product' );36 }37 }3839 woocommerce_product_loop_end();4041 do_action( 'woocommerce_after_shop_loop' );42} else {43 do_action( 'woocommerce_no_products_found' );44}4546do_action( 'woocommerce_after_main_content' );47```4849Rules:5051- Use `woocommerce_product_loop()` instead of only checking `have_posts()`.52- Use `woocommerce_product_loop_start()` and `woocommerce_product_loop_end()` so loop wrappers and subcategories work.53- Use `wc_get_loop_prop( 'total' )` before rendering products.54- Preserve `woocommerce_before_shop_loop`, `woocommerce_after_shop_loop`, and `woocommerce_no_products_found`.55- Do not build shop archives with a raw `WP_Query` unless this is a deliberate secondary product section.5657## Product Card Contract5859`content-product.php` must preserve the product object guard and visibility check.6061```php62global $product;6364if ( ! is_a( $product, WC_Product::class ) || ! $product->is_visible() ) {65 return;66}67?>68<li <?php wc_product_class( '', $product ); ?>>69 <?php70 do_action( 'woocommerce_before_shop_loop_item' );71 do_action( 'woocommerce_before_shop_loop_item_title' );72 do_action( 'woocommerce_shop_loop_item_title' );73 do_action( 'woocommerce_after_shop_loop_item_title' );74 do_action( 'woocommerce_after_shop_loop_item' );75 ?>76</li>77```7879Rules:8081- Keep `wc_product_class( '', $product )`.82- Preserve default hook positions unless intentionally replacing output.83- Do not echo product title, price, image, or add-to-cart from raw postmeta.84- Use Woo template functions or `WC_Product` methods.85- Product cards should remain valid list items when inside `woocommerce_product_loop_start()`.8687## Hook Map for Cards8889Default product card hooks:9091- `woocommerce_before_shop_loop_item`: product link open.92- `woocommerce_before_shop_loop_item_title`: sale flash and thumbnail.93- `woocommerce_shop_loop_item_title`: product title.94- `woocommerce_after_shop_loop_item_title`: rating and price.95- `woocommerce_after_shop_loop_item`: product link close and add-to-cart.9697Example: move rating below add-to-cart.9899```php100add_action( 'after_setup_theme', 'mytheme_product_card_hooks' );101102function mytheme_product_card_hooks() {103 remove_action( 'woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_rating', 5 );104 add_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_rating', 15 );105}106```107108Rules:109110- Use exact priorities from `wc-template-hooks.php`.111- Prefer hook changes over copying `content-product.php`.112- Test simple, variable, external, grouped, out-of-stock, sale, and hidden products.113114## Add-to-Cart Accessibility115116Woo 10.x loop add-to-cart provides:117118- `aria-label` from `$product->add_to_cart_description()`.119- optional `aria-describedby` screen-reader text.120- `role="button"` for AJAX add-to-cart links when configured.121- `data-success_message` for simple products.122123Rules:124125- Do not remove `aria-label`, `aria-describedby`, or screen-reader text from `loop/add-to-cart.php`.126- If filtering `woocommerce_loop_add_to_cart_link`, preserve product-specific accessible names.127- Do not replace add-to-cart with a generic "Buy" link for all products.128- Variable/external/grouped products often need a view/select-options flow, not AJAX add-to-cart.129130## Images and Grid131132Rules:133134- Product card images should use `woocommerce_thumbnail`.135- Control catalog image width through Woo theme support or Woo image settings.136- Do not use full-size images in product grids.137- Preserve responsive image attributes generated by Woo/WP.138- Set CSS grid/flex rules so `columns-N` classes do not create overflow.139140## Result Count, Ordering, Pagination141142Default hooks:143144- `woocommerce_before_shop_loop`: notices at 10, result count at 20, ordering at 30.145- `woocommerce_after_shop_loop`: pagination at 10.146147Rules:148149- Preserve result count and ordering unless the design has a replacement.150- Preserve `woocommerce_output_all_notices` on archives.151- Use Woo pagination; do not manually build `paged` links.152- The pagination template has an accessible `aria-label`.153154## Custom Product Sections155156For secondary product grids, use Woo product queries carefully.157158Rules:159160- Prefer Woo shortcodes/blocks only when the theme is intentionally using them.161- For PHP sections, use `wc_get_products()` for product objects or `WP_Query` only when WordPress loop behavior is required.162- If using `WP_Query`, call `wp_reset_postdata()` after the loop.163- Render each product with `wc_get_template_part( 'content', 'product' )` when you want standard card behavior.164- Set loop props such as columns only for the local section and restore expectations afterward.165166## Review Checklist167168- Archive templates keep Woo wrapper hooks.169- Product loops use Woo loop helpers.170- Product cards preserve product guard, visibility, and `wc_product_class()`.171- Hook changes are preferred over template copies.172- Add-to-cart accessibility attributes are preserved.173- Product images use Woo image sizes.174- Result count, ordering, notices, pagination, and no-products states work.175- Product categories/tags/attributes use Woo taxonomy archive flow.176- No raw price/stock/meta output bypasses `WC_Product` methods.177178## Common Mistakes179180- Replacing `woocommerce_product_loop_start()` with a custom `<div>` and breaking subcategories/plugins.181- Removing `woocommerce_output_all_notices` from archives.182- Rendering hidden products because the `is_visible()` guard was deleted.183- Losing AJAX add-to-cart classes/data attributes.184- Hardcoding "Add to cart" without product-specific ARIA.185- Using `WP_Query` and forgetting `wp_reset_postdata()`.186187## References188189- Official documentation: <https://developer.woocommerce.com/docs/theming/theme-development/template-structure/>190- Official documentation: <https://developer.woocommerce.com/docs/theming/theme-development/conditional-tags/>191- Official documentation: <https://developer.woocommerce.com/docs/theming/theme-development/image-sizes/>192- Official documentation: <https://developer.woocommerce.com/docs/theming/theme-development/theme-design-ux-guidelines/>193- Verified source paths:194 - `wp-content/plugins/woocommerce/templates/archive-product.php`195 - `wp-content/plugins/woocommerce/templates/content-product.php`196 - `wp-content/plugins/woocommerce/templates/loop/add-to-cart.php`197 - `wp-content/plugins/woocommerce/templates/loop/loop-start.php`198 - `wp-content/plugins/woocommerce/templates/loop/pagination.php`199 - `wp-content/plugins/woocommerce/includes/wc-template-hooks.php`200 - `wp-content/plugins/woocommerce/includes/wc-template-functions.php`201 - `wp-content/plugins/woocommerce/includes/wc-product-functions.php`