Loading...
Hot as hell, this is the top-bar widget Link

bs Loop

Plugin to display posts, pages, any custom post type, or taxonomy terms via shortcode. Choose from horizontal, grid, overlay, or custom layout templates.

Overview

bs Loop uses the theme’s loop templates in /template-parts/loop/, making every card design overridable from a child theme. It is the base plugin for bs Swiper and bs Isotope, which extend it with slider and filter functionality respectively. The shortcode supports any post type, taxonomy, or term, making it equally useful for blog archives, WooCommerce product grids, portfolio pages, or organizing child pages under a parent.

Installation

  • Download Plugin
  • In your admin panel, go to Plugins > and click the Add New button.
  • Click Upload Plugin and Choose File, then select the Plugin’s .zip file. Click Install Now.
  • Click Activate to use your new Plugin right away.

Shortcode

Basic

Shows all posts in the default horizontal card layout.

[bs-loop]

Select Post Type

Select any registered post type, filter by taxonomy or id, and use WooCommerce-specific options like featured, sale, or stock status.

AttributeDescriptionDefault
type=""Selects the post type
type="post"
type="page"
type="product"
type="my_custom_post_type"
post
id=""Single id, multiple id’s separated by comma
id="123"
id="123, 456"
N/A
tax=""Taxonomy
tax="category" (Post)
tax="post_tag" (Post)
tax="product_cat" (Product)
tax="product_tag" (Product)
tax="product_brand" (Product)
tax="my_custom_post_type_tax" (CPT)
N/A
terms=""terms-slug, multiple terms separated by comma,
requires the tax="" attribute
terms="cars"
terms="cars, boats"
N/A
exclude_terms=""Exclude posts from specific taxonomy terms
(slugs, comma-separated)
exclude_terms="cars"
exclude_terms="cars, boats"
N/A
terms_parent=""Id of a hierarchical parent term, multiple
terms separated by comma,
requires the tax="" attribute.
terms_parent="789"
terms_parent="789, 101112"

Vehicles (id=”789″)
– Cars
– Boats

Animals (id=”101112″)
– Cats
– Dogs
N/A
post_parent=""ID of a hierarchical post type (page) to show child elements,
multiple terms separated by comma
post_parent="123"
post_parent="123, 456"
N/A
featured=""true – pulls featured productsfalse
out_of_stock=""true – shows out of stock productsfalse
on_sale=""true – shows only on sale productsfalse
show_hidden=""true – shows products hidden from catalogfalse
related_to=""Shows posts that share the same taxonomy terms
(requires tax="" – e.g., tax="category" or tax="post_tag").
Automatically excludes the current post when used within a
single post/page.

Usage examples:
related_to="current" – Use current post as reference
(works in single post templates only)
related_to="123" – Use specific post ID as reference
related_to="current" tax="category" – Show posts from same categories
related_to="current" tax="post_tag" – Show posts from same tags
related_to="current" tax="product_cat" type="product" – Show related products

Note: When related_to is empty, the shortcode functions
as a normal query without
excluding any posts.
N/A

Select Taxonomy (Show Terms instead of Posts)

[bsfa fa-solid fa-circle-xmark] bs Isotope

If type="" matches a registered taxonomy (e.g. product_cat, category, post_tag) instead of a post type, bs-loop renders a grid of taxonomy terms instead of posts — useful for category overview pages.

[bs-loop layout="grid" type="product_cat"]
AttributeDescriptionDefault
type=""Taxonomy name instead of a post type
type="product_cat"
type="category"
type="post_tag"
type="my_custom_taxonomy"
N/A
id=""Single term id, multiple term id’s separated by comma
id="123"
id="123, 456"
N/A
terms_parent=""Show only child terms of given parent term id(s),
multiple separated by comma
terms_parent="789"
terms_parent="789, 101112"
N/A
posts=""Limit number of terms shown
posts="6"
-1
order=""ASC or DESCASC
orderby=""Any get_terms() orderby value
name, slug, count, term_id, etc.
name

tax="", terms="", post_parent="", featured="", on_sale="", out_of_stock="", and show_hidden="" only apply when type="" is a post type — this mode shows terms, not posts, so those attributes are ignored. id="" and terms_parent="" are alternative ways to select terms; use one or the other, not both.

Template selection mirrors how type="product" works for posts:

  • type="product_cat" or type="product_tag" always render via template-parts/loop/cards-wc-product-cat.php, regardless of layout="".
  • Any other taxonomy (category, post_tag, custom taxonomies) follows layout="" the same way posts do: grid → cards-grid.php, overlay → cards-overlay.php, default/horizontal → cards-horizontal.php, custom → custom.php.

Term cards use the term’s thumbnail_id meta for an image (the same meta key WooCommerce uses for product category images). Core WordPress doesn’t provide an admin UI to set this for category/post_tag— without a plugin or custom field to populate it, those term cards render without an image. For cards-horizontal.php/cards-grid.php that’s harmless (the card body still gives it normal height). Avoid layout="overlay" for taxonomies without a term thumbnail — that layout’s height comes entirely from the image, so a missing thumbnail can collapse the card to zero height.

Ordering & Limit

Control the sort direction, sort field, and maximum number of items returned.

AttributeDescriptionDefault
order=""ASC or DESCDESC
orderby=""date, title, or randdate
posts=""Limit items to value
posts="10", shows 10 items
posts="20", shows 20 items
-1

Layout

Select the template

AttributeDescriptionDefault
layout=""horizontal, horizontal responsive cards
grid, vertical grid column cards, required for products
overlay, card-overlay (hero)
custom, empty template to allow to create custom loop via action hooks
horizontal

Grid

AttributeDescriptionDefault
row-cols=""
row-cols-sm=""
row-cols-md=""
row-cols-lg=""
row-cols-xl=""
row-cols-xxl=""

[bsfa fa-solid fa-circle-xmark] bs Swiper (slides_per_view="")
Number of columns per breakpoint
1 – 12.
Not available for custom layout
and bs Swiper.
horizontal/overlay: 1 on all breakpoints
grid: 1, 1, 2, 3, 4, 4 (xs > xxl)
gap=""

[bsfa fa-solid fa-circle-xmark] bs Swiper (space_between="")
Grid gutter class,
Bootstrap 0 – 5
gap="0"
gap="3"
4
mb=""Grid wrapper margin bottom class,
Bootstrap 0 – 5
mb="0"
mb="3"
4

Context

The context="" attribute is a powerful tool to create unlimited custom loop variants and apply filters in a conditional way. Use it like CSS classes without a comma separator.

[bs-loop context="green-loop-cards red-border"]
/**
 * Change loop cards bg in context
 */
function green_loop_cards_function($class) {
  if (!empty($GLOBALS['bs_loop_context']) && $GLOBALS['bs_loop_context'] === 'green-loop-cards') {
    return "card horizontal overflow-hidden mb-4 bg-success";
  }
  return $class;
}
add_filter('bootscore/class/loop/card', 'green_loop_cards_function', 10, 2);

Context attribute can be used as a CSS selector as well:

.bs-loop.red-border {
  .card {
    border-color: var(--#{$prefix}danger);
    border-width: 5px;
  }
}

Predefined context filters

Use predefined filters in the context="" attribute to quickly show or hide elements within each loop item.

[bs-loop context="hide-category hide-meta hide-excerpt hide-read-more hide-tags truncate-heading truncate-excerpt"]
AttributeDescriptionDefault
context=""hide-category, removes the category badge
hide-meta, removes the meta date, author, comment etc.
hide-meta-date-updated, removes
the updated date
hide-meta-author, removes the
author
hide-excerpt, removes the excerpt
hide-read-more, removes the read-more link
hide-tags, removes the tags badge
hide-meta-wrapper, removes both
category badge and sticky post badge

Not working in default horizontal layout
truncate-heading, adds text-truncate to headings
truncate-excerpt, adds text-truncate to the excerpt
N/A

Examples

Posts

By category

[bs-loop type="post" tax="category" terms="post-demos, post-demos-2" posts="2"]

By tags, grid layout

[bs-loop type="post" tax="post_tag" terms="lorem, ipsum" layout="grid"]

By terms parent, custom columns

[bs-loop type="post" tax="category" terms_parent="47" posts="4" context="hide-category hide-meta hide-tags hide-read-more" row-cols-xl="2" gap="2"]

Single by id, overlay layout

[bs-loop type="post" id="30989, 30997" layout="overlay" posts="4" context="hide-category hide-meta hide-tags hide-read-more truncate-heading truncate-excerpt" row-cols-md="2"]

Pages

Child pages by parent page id

[bs-loop type="page" post_parent="29418" row-cols-lg="2"]

Single pages by id

[bs-loop type="page" id="25475, 9384"]

Taxonomy Terms

Show a grid of taxonomy terms instead of single posts.

layout="overlay" is not recommended for taxonomies without a term thumbnail (see note above) — the card can render with zero height. Stick to horizontal (default) or grid for category/post_tag/custom taxonomies unless you’ve set up term thumbnails.

Categories

[bs-loop layout="grid" type="category"]

Blog

Stay up to date with news, release updates, and important announcements for Bootscore.

Read more »

tags

[bs-loop type="post_tag" id="193, 54"]

Woocommerce

WooCommerce products require the layout="grid" attribute to show the correct loop cards.

Products

Basic
[bs-loop type="product" layout="grid"]

T-Shirt .btn-outline

A classic unisex t-shirt that works well with any outfit. Made of a heavier cotton with a double-stitched neckline and sleeves. Rolled-forward shoulders for a better fit Stylish fitted sleeve Seamless double-needle collar Taped neck and shoulders for durability Tubular fit for minimal torque Logo print 8x8cm This product is made on demand. No minimums. […]

Read more »

T-Shirt .btn

A classic unisex t-shirt that works well with any outfit. Made of a heavier cotton with a double-stitched neckline and sleeves. Rolled-forward shoulders for a better fit Stylish fitted sleeve Seamless double-needle collar Taped neck and shoulders for durability Tubular fit for minimal torque Logo print 8x8cm This product is made on demand. No minimums. […]

Read more »

By category
[bs-loop layout="grid" type="product" tax="product_cat" terms="plugins, t-shirts" posts="4" row-cols="2" row-cols-lg="3" orderby="title" order="ASC"]
By terms_parent
[bs-loop layout="grid" type="product" tax="product_cat" terms_parent="81, 190"]
By brands
[bs-loop layout="grid" type="product" tax="product_brand" terms="woo, sample-brand"]
By tags
[bs-loop layout="grid" type="product" tax="product_tag" terms="sample-tag-1, sample-tag-2"]

Show only featured products:

[bs-loop type="product" layout="grid" featured="true"]
Products on sale

Show only products on sale:

[bs-loop type="product" layout="grid" on_sale="true"]
Products out of stock

Show out of stock products as well:

[bs-loop type="product" layout="grid" out_of_stock="true"]
Products hidden from the catalog

Show products hidden from catalog:

[bs-loop type="product" layout="grid" show_hidden="true"]

Taxonomy Terms

Show a grid of taxonomy terms (e.g. WooCommerce product categories) instead of products.

Only type="product_cat" uses an own template. All other taxonomies like product_brand and product_tag uses the default layout="horizontal/grid/overlay" templates.

All product categories
[bs-loop layout="grid" type="product_cat"]
Specific categories by id
[bs-loop layout="grid" type="product_cat" id="31, 34"]
Child categories of a parent category
[bs-loop layout="grid" type="product_cat" terms_parent="912"]
Child categories of multiple parent categories
[bs-loop layout="grid" type="product_cat" terms_parent="213, 911"]
Brands
[bs-loop layout="grid" type="product_brand"]
Tags
[bs-loop type="product_tag" posts="2"]

Custom

Use layout="custom" together with context="" to build fully custom card designs via action hooks — without touching theme templates. Each example below is self-contained and only runs when its specific context is active.

Tailwind style loop cards

[bs-loop layout="custom" context="tailwind-style-loop-cards" type="post" id="9318, 9326, 9328, 9405"]
/**
 * Custom Loop Item for `layout="custom" context="tailwind-style-loop-cards"`
 * This only affects the shortcode, not theme templates.
 */

// Change tags badge class only in specific context
function change_tags_badge($class) {
  // Check if the context exists and matches
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('tailwind-style-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    return 'custom-tag badge bg-dark bg-opacity-25 backdrop-blur-10 text-white rounded-pill fw-lighter p-2 text-decoration-none';
  }
  return $class;
}
add_filter('bootscore/class/badge/tag', 'change_tags_badge');

// Change category badge
function change_category_badge($class) {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('tailwind-style-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    return 'small text-body-secondary text-uppercase fw-lighter text-decoration-none';
  }
  return $class;
}
add_filter('bootscore/class/badge/category', 'change_category_badge');



// Open row before loop
function open_row_before_loop() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('tailwind-style-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '<div class="row row-cols-1 row-cols-md-2 row-cols-lg-2 row-cols-xl-2 row-cols-xxl-3 g-4 mb-4 bs-loop-grid">';
  }
}
add_action('bootscore_before_loop', 'open_row_before_loop');


// Open col before the loop item.
function open_col_before_loop_item() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('tailwind-style-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '<div class="col bs-loop-grid-item">';
  }
}
add_action('bootscore_before_loop_item', 'open_col_before_loop_item');


// Custom Loop Item - The actual card
add_action('bootscore_custom_loop_item', function($context) {
  if ($context === 'custom' && isset($GLOBALS['bs_loop_contexts']) && in_array('tailwind-style-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    ?>
    <article class="card shadow h-100 rounded-5">
      
      <?php if ( has_post_thumbnail() ) : ?>
        <a class="pt-2 px-2" href="<?php the_permalink(); ?>" aria-hidden="true" tabindex="-1">
          <?php the_post_thumbnail('medium', array('class' => 'card-img', 'style' => 'border-radius: calc(var(--bs-border-radius-xxl) - 0.5rem);')); // border-radius-xxl - inner-padding p-2 ?>
        </a>
        <div class="position-absolute top-0 start-0 mt-4 ms-4 small">
          <?php bootscore_tags(); ?>
        </div>
      <?php endif; ?>
      
      <div class="card-body p-4 d-flex flex-column">
        
        <?php bootscore_category_badge(); ?>
        
        <?php the_title('<h2 class="h5">', '</h2>'); ?>

        <p class="card-text small fw-lighter">
          <?= esc_html(wp_strip_all_tags(get_the_excerpt())); ?>
        </p>

        <p class="card-text d-flex align-items-center mt-auto">
          <?php if ('post' === get_post_type()) : ?>
            <span class="small text-body-secondary fw-lighter w-100">
              <i class="fa-regular fa-calendar"></i> <?php bootscore_date();?>
            </span>
          <?php endif; ?>
          <a class="btn btn-sm btn-dark rounded-pill shadow w-100" href="<?php the_permalink(); ?>">
            Learn more <i class="fa-solid fa-arrow-right"></i>
          </a>
        </p>

      </div>
      
    </article>
    <?php
  }
});


// Close col after the loop item.
function close_col_after_loop_item() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('tailwind-style-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '</div>';
  }
}
add_action('bootscore_after_loop_item', 'close_col_after_loop_item');

// Close row after loop
function close_row_after_loop() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('tailwind-style-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '</div>';
  }
}
add_action('bootscore_after_loop', 'close_row_after_loop');

Custom overlay cards

[bs-loop layout="custom" context="custom-overlay-loop-cards" type="post" id="9318, 9326, 9328, 9405"]
/**
 * Custom Loop Item for `layout="custom" context="custom-overlay-loop-cards"`
 * This only affects the shortcode, not theme templates
 */

// Change tags badge
function change_overlay_tags_badge($class) {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('custom-overlay-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    return 'badge bg-dark bg-opacity-25 text-white backdrop-blur-10 rounded-pill fw-lighter p-2 text-decoration-none';
  }
  return $class;
}
add_filter('bootscore/class/badge/tag', 'change_overlay_tags_badge');


// Open row before loop
function open_overlay_row_before_loop() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('custom-overlay-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '<div class="row row-cols-1 row-cols-md-2 row-cols-lg-2 row-cols-xl-2 row-cols-xxl-3 g-4 mb-4 bs-loop-grid">';
  }
}
add_action('bootscore_before_loop', 'open_overlay_row_before_loop');


// Open col before the loop item.
function open_overlay_col_before_loop_item() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('custom-overlay-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '<div class="col bs-loop-grid-item">';
  }
}
add_action('bootscore_before_loop_item', 'open_overlay_col_before_loop_item');


// Custom Loop Item - The actual card
add_action('bootscore_custom_loop_item', function($context) {
  if ($context === 'custom' && isset($GLOBALS['bs_loop_contexts']) && in_array('custom-overlay-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    ?>
    <article class="card border-0 shadow bg-dark h-auto rounded-4 overflow-hidden">
      
        <?php the_post_thumbnail('medium', array('class' => 'card-img')); ?>

        <div class="position-absolute top-0 start-0 mt-3 ms-3 small">
          <?php bootscore_tags(); ?>
        </div>
      
      <div class="card-img-overlay d-flex flex-column">

        <a class="text-decoration-none mt-auto stretched-link" href="<?php the_permalink(); ?>">
          <?php the_title('<h2 class="text-white h3 lh-1 fw-bold">', '</h2>'); ?>
        </a>
        
        <div class="custom-card-overlay-meta d-flex align-items-center gap-2 mt-2 text-white">
          <p class="card-text d-flex align-items-center mt-auto w-100">
            <span class="author-avatar me-auto">
              <?php echo get_avatar(get_the_author_meta('ID'), 32, '', '', array('class' => 'rounded-circle border border-white shadow')); ?>
            </span>
            <span class="small text-white">
              <i class="fa-regular fa-calendar"></i> <?php bootscore_date();?>
            </span>
          </p>
        </div>
        
      </div>
      
    </article>
    <?php
  }
});


// Close col after the loop item.
function close_overlay_col_after_loop_item() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('custom-overlay-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '</div>';
  }
}
add_action('bootscore_after_loop_item', 'close_overlay_col_after_loop_item');

// Close row after loop
function close_overlay_row_after_loop() {
  if (isset($GLOBALS['bs_loop_contexts']) && in_array('custom-overlay-loop-cards', $GLOBALS['bs_loop_contexts'])) {
    echo '</div>';
  }
}
add_action('bootscore_after_loop', 'close_overlay_row_after_loop');

Minimal Product Loop

[bs-loop layout="custom" context="minimal-product-loop" type="product" tax="product_cat" terms="plugins, t-shirts"]
/**
 * Minimal Product Loop Item for     
    
* This only affects the shortcode, not theme templates. */ /** * Replace add to cart button with icon in minimal product loop context */ add_filter('woocommerce_loop_add_to_cart_link', function ($button, $product, $args) { if (!isset($GLOBALS['bs_loop_contexts']) || !in_array('minimal-product-loop', $GLOBALS['bs_loop_contexts'])) { return $button; } $icon = '<i class="fa-solid fa-bag-shopping"></i>'; if ($product->is_type('variable')) { $icon = '<i class="fa-solid fa-cart-shopping"></i>'; } elseif ($product->is_type('external')) { $icon = '<i class="fa-solid fa-arrow-up-right-from-square"></i>'; } return sprintf( '<a href="%s" data-quantity="%s" class="%s" %s>%s</a>', esc_url($product->add_to_cart_url()), esc_attr(isset($args['quantity']) ? $args['quantity'] : 1), esc_attr(isset($args['class']) ? $args['class'] : 'button'), isset($args['attributes']) ? wc_implode_html_attributes($args['attributes']) : '', $icon ); }, 10, 3); /** * Temporarily override the add to cart button class. * Named function required so remove_filter() can actually find and remove it. */ function minimal_product_loop_add_to_cart_class($classes) { return 'btn btn-sm btn-primary'; } /** * Open row before loop — not fired in AJAX context, so Load More handles its own wrapper */ function open_minimal_product_before_loop() { if (isset($GLOBALS['bs_loop_contexts']) && in_array('minimal-product-loop', $GLOBALS['bs_loop_contexts'])) { echo '<div class="row row-cols-1 row-cols-md-2 row-cols-lg-2 row-cols-xl-2 row-cols-xxl-3 g-4 mb-4 bs-loop-grid">'; } } add_action('bootscore_before_loop', 'open_minimal_product_before_loop'); /** * Open col before each loop item */ function open_minimal_product_col_before_loop_item() { if (isset($GLOBALS['bs_loop_contexts']) && in_array('minimal-product-loop', $GLOBALS['bs_loop_contexts'])) { echo '<div class="col bs-loop-grid-item">'; } } add_action('bootscore_before_loop_item', 'open_minimal_product_col_before_loop_item'); /** * Custom Loop Item — the actual card */ add_action('bootscore_custom_loop_item', function ($context) { if ($context !== 'custom') return; if (!isset($GLOBALS['bs_loop_contexts']) || !in_array('minimal-product-loop', $GLOBALS['bs_loop_contexts'])) return; global $product; ?> <div <?php wc_product_class(esc_attr(apply_filters('bootscore/class/woocommerce/product/card', 'card h-100', 'minimal-product-loop')), $product); ?>> <div class="card-body h-100"> <div class="row g-3 h-100"> <div class="col-4 h-100"> <a class="d-block" href="<?php the_permalink(); ?>" aria-hidden="true" tabindex="-1"> <?php if (has_post_thumbnail()) : ?> <?php the_post_thumbnail('thumbnail', array( 'class' => 'rounded-1 align-text-top', 'alt' => get_the_title(), )); ?> <?php else : ?> <img src="<?php echo esc_url(wc_placeholder_img_src('thumbnail')); ?>" alt="<?php echo esc_attr__('Placeholder', 'woocommerce'); ?>" class="rounded align-text-top"> <?php endif; ?> </a> </div> <div class="col-8 h-100 d-flex flex-column"> <?php the_title('<h3 class="h6 mb-0 text-truncate">', '</h3>'); ?> <p class="mb-0 small text-body-secondary text-truncate"> <?= esc_html(wp_strip_all_tags(get_the_excerpt())); ?> </p> <p><?php echo wc_price($product->get_price()); ?></p> <?php // Add named filter, display button, then remove it cleanly add_filter('bootscore/class/woocommerce/loop/add-to-cart/button', 'minimal_product_loop_add_to_cart_class'); woocommerce_template_loop_add_to_cart(); remove_filter('bootscore/class/woocommerce/loop/add-to-cart/button', 'minimal_product_loop_add_to_cart_class'); ?> </div> </div> </div> </div> <?php }); /** * Close col after each loop item */ function close_minimal_product_col_after_loop_item() { if (isset($GLOBALS['bs_loop_contexts']) && in_array('minimal-product-loop', $GLOBALS['bs_loop_contexts'])) { echo '</div>'; } } add_action('bootscore_after_loop_item', 'close_minimal_product_col_after_loop_item'); /** * Close row after loop */ function close_minimal_product_row_after_loop() { if (isset($GLOBALS['bs_loop_contexts']) && in_array('minimal-product-loop', $GLOBALS['bs_loop_contexts'])) { echo '</div>'; } } add_action('bootscore_after_loop', 'close_minimal_product_row_after_loop');