Create a strip of product attribute icons and HTML snippets for the WooCommerce single product page. It’s a straightforward copy-and-paste tutorial that’s fully customisable with snippet files and filters, so you can use the same code in multiple projects.
infoMake sure you’re using a custom child theme so you can edit functions.php.
Project Requirements
We want to show a list of product attributes and terms for any WooCommerce product. Usually this will be on the single product page, but we might want to show a product’s attributes in some other context too, so we’ll create a reusable core function. This function will have a single parameter, $product
, and it’ll return some HTML for us. We can call the core function from a shortcode wrapper, an action-handler, or anywhere we want.
General requirements…
- Render the list of product attributes for a product
- We need to ignore/skip product attributes that are linked to a variable product’s variations. If we were to implement this for variations, we’d be making a product variation pills plugin, which is a bit different.
- Inject the product attribute tiles into a product’s meta area, just after the add-to-cart button
- Embed the product attributes in any content by using a shortcode
- Create a fully customisable per-attribute HTML, via individual PHP snippet files
- Support the ability to display only a subset of a product’s attributes (optional)
- The attributes list can replace the built-in “Additional Information” tab, so we need a way of hiding it
The Template Files
We’re going to use small PHP files to render each of the attribute tiles. As we loop through a product’s attributes, we’ll determine which template file to use based on its slug, like this:
- Get the attribute’s slug, e.g. “pa_size”. All product attribute taxonomy slugs start with “pa_” in WooCommerce.
- Derive the template file name based on the attribute slug, “tile-pa_size.php”
- If the template file exists, then…
- Use it
- Else….
- Fall back to using “tile-default.php”
We’re going to use “tile-default.php” to render all of the tiles, but the ability to use files like “tile-pa_size.php” is there in case we need to do something fancy/unusual in the future.
Scaffold the Code
In your custom child theme, create a file called wpt-product-attribute-snippets.php and paste the following into it:
<?php /** * WP Tutorials : Product attribute snippets (WPTPAS) * * https://wp-tutorials.tech/refine-wordpress/product-attribute-icons-in-woocommerce/ */ defined('WPINC') || die(); const WPTPAS_SNIPPETS_DIR_NAME = 'wpt-product-attribute-snippets'; const WPTPAS_DEFAULT_TEMPLATE_FILE_NAME = 'tile-default.php'; /** * Enqueue our front-end assets. */ function wptpas_enqueue_assets() { global $wptpas_have_assets_been_enqueued; if (is_null($wptpas_have_assets_been_enqueued)) { $theme_version = wp_get_theme()->get('Version'); $base_uri = get_stylesheet_directory_uri(); wp_enqueue_style( 'wptpas', $base_uri . '/' . WPTPAS_SNIPPETS_DIR_NAME . '/product-attribute-snippets.css', null, $theme_version ); $wptpas_have_assets_been_enqueued = true; } } /** * Always enqueue our frontend assets if we're on the single product page. */ function wptpas_enqueue_scripts() { // We need to check that is_product() is a function, in case WooCommerce // isn't installed. if (function_exists('is_product') && is_product()) { wptpas_enqueue_assets(); } } add_action('wp_enqueue_scripts', 'wptpas_enqueue_scripts'); /** * Create the HTML for our product attributes and terms, based on snippet * file(s). */ function get_product_attribute_snippets_html($product = null) { // TODO: Write some amazing code... return ''; }
Next, create a new folder called “wpt-product-attribute-snippets” and create two empty text files in there:
- product-attribute-snippets.css
- tile-default.php
To tie it all together, open your child theme’s functions.php file and add the following couple of lines:
// WP Tutorials : Product attribute snippets require_once dirname(__FILE__) . '/wpt-product-attribute-snippets.php';
Save all that and go to a single product page… nothing should’ve changed, and nothing should be broken 👍
Core Functionality
As with most of our WordPress tutorials, the core logic is embodied in a core function, with smaller/helper functions to integrate the feature into the site. Our core logic breaks down like this:
- Check that WooCommerce is installed.
- If the WooCommerce plugin is not active, and we try to call functions like
is_product()
, we’re going to crash the site.
- If the WooCommerce plugin is not active, and we try to call functions like
- Get an instance of the WC_Product we’re working with – a WooCommerce product object.
- If the front-end assets have not been enqueued, enqueue them now. For this project it’s just a single CSS file.
- For each product attribute…
- If the attribute is not visible, continue to the next attribute
- If this is a product variation attribute, continue to the next attribute
- If we have an only-allow list of product attributes, and this attribute is not in it, continue to the next attribute
- Otherwise…
- Derive the template file name. If the template file does not exist, use the default file name, “tile-default.php”
- Set up some useful variables to use inside our template file, like
$attribute_label
and$term_names
. This helps us reduce the amount of code inside the template file. - Include the template file
- Wrap the content of the template file in
<li>...</li>
- Wrap all of the tiles in
<ul>...</ul>
The final HTML will come out looking like this (example):
<div class="pa-snippets pa-snippets-product-999"> <ul> <li class="pa-snippet-att-pa_colour"> <div class="wptpas-attribute"> <span class="wptpas-attribute-icon"> <i class="fas fa-palette"></i> </span> <span class="wptpas-attribute-label"> Colour </span> </div> <div class="wptpas-term wptpas-term-white"> White </div> </li> <li class="pa-snippet-att-pa_size"> ... </li> <li class="pa-snippet-att-pa_texture"> ... </li> </ul> </div>
Now we can move on to the core function itself. Open wpt-product-attribute-snippets.php and replace get_product_attribute_snippets_html()
with the following:
function get_product_attribute_snippets_html($product = null) { $html = ''; if (!function_exists('WC')) { $html .= '<p>WooCommerce is not installed</p>'; } elseif (empty($product) && empty($product = wc_get_product())) { $html .= '<p>Failed to determine the current prodyct</p>'; } else { wptpas_enqueue_assets(); $product_id = $product->get_id(); // Get our base directory name and the full path of the default template // file (tile-default.php). $base_dir = dirname(__FILE__); $default_template_file_name = $base_dir . '/' . WPTPAS_SNIPPETS_DIR_NAME . '/' . WPTPAS_DEFAULT_TEMPLATE_FILE_NAME; $product_attributes = $product->get_attributes(); $is_a_variable_product = $product->is_type('variable'); // If you only want explicit attributes to be shown, you can hook this // filter and return an array of attribute slugs: $only_attributes = apply_filters('wptpas_only_these_attributes', null); // This will hold our <li>...</li> snippets. $inner_html = ''; foreach ($product_attributes as $product_attribute) { $attribute_slug = $product_attribute->get_name(); if (!$product_attribute->get_visible()) { // This attribute is marked as not visible. } elseif ($is_a_variable_product && $product_attribute->get_variation()) { // This attribute is for a variation. } elseif (is_array($only_attributes) && !in_array($attribute_slug, $only_attributes)) { // We've specified an explicit attribute list and this attribute's // slug is not in the list. } else { // Do we have a PHP template for this actual attribute, or should // we fallback to tile-default.php? $template_file_name = $base_dir . '/' . WPTPAS_SNIPPETS_DIR_NAME . '/tile-' . $attribute_slug . '.php'; $does_template_file_exist = is_file($template_file_name); $actual_template_file_name = $template_file_name; if (!$does_template_file_exist) { $actual_template_file_name = $default_template_file_name; } $attribute_terms = $product_attribute->get_terms(); $attribute_label = wc_attribute_label($attribute_slug); $term_names = wp_list_pluck($attribute_terms, 'name', null); $inner_html .= sprintf( '<li class="pa-snippet-att-%s">', $attribute_slug ); // Include the PHP template snippet and append it to $inner_html. ob_start(); include $actual_template_file_name; $inner_html .= ob_get_clean(); $inner_html .= '</li>'; // .pa-snippet-att-XXX } } // If we've got anuthing in $inner_html then wrap it up with <ul>...</ul> // and our outer div. if (!empty($inner_html)) { $html .= sprintf('<div class="pa-snippets pa-snippets-product-%d">', $product_id); $html .= '<ul>'; $html .= $inner_html; $html .= '</ul>'; $html .= '</div>'; // .pa-snippets } } return $html; }
That gives us something we can work with, but we need some CSS and a default template file, before we can test it properly.
Supplementary Files
Go in to the “wpt-product-attribute-snippets” folder and paste the following into product-attribute-snippets.css. It’s fairly basic, but it’s enough to get you started.
/** * WP Tutorials : Product attribute snippets (WPTPAS) * * https://wp-tutorials.tech/refine-wordpress/product-attribute-icons-in-woocommerce/ */ .pa-snippets { margin-top: 2em; margin-bottom: 2em; line-height: 2em; } .pa-snippets>ul { list-style-type: none; padding: 0; margin: 0; display: flex; flex-direction: column; align-items: stretch; gap: 1rem; } .pa-snippets>ul>li { text-align: center; background-color: darkblue; color: white; border-radius: 1rem; overflow: hidden; } @media(min-width: 768px) { .pa-snippets ul { flex-direction: row; } .pa-snippets ul li { flex-basis: 0; flex-grow: 1; } } .wptpas-attribute { font-size: 12pt; margin: 2rem 0 0.5rem 0; } .wptpas-attribute-icon, .wptpas-attribute-label { display: block; } .wptpas-attribute-icon { font-size: 24pt; margin-bottom: 0.1rem; } .wptpas-attribute-label { color: silver; font-size: 11pt; } .wptpas-term { font-size: 12pt; font-weight: bold; background-color: #0002; padding: 0.75rem; }
…then open tile-default.php and paste the following:
<?php /** * WP Tutorials : Product attribute snippets (WPTPAS) * * https://wp-tutorials.tech/refine-wordpress/product-attribute-icons-in-woocommerce/ * * The following variables have already been set for us by the calling code: * $product_attribute : a WC_Product_Attribute instance * $attribute_slug : string * $attribute_label : string * $attribute_terms : array of WP_Term objects */ defined('WPINC') || die(); echo '<div class="wptpas-attribute">'; // To set product attribute icons, you need to hook our // "product_attribute_icon_html" filter and return a HTML string with for your // icon. $icon_html = (string) apply_filters('product_attribute_icon_html', '', $product_attribute); if (!empty($icon_html)) { echo '<span class="wptpas-attribute-icon">'; echo $icon_html; echo '</span>'; // .wptpas-attribute-icon } printf( '<span class="wptpas-attribute-label">%s</span>', esc_html($attribute_label) ); echo '</div>'; // .wptpas-attribute // Usually there'll only be one attribute term, but we need a foreach(...) here // in case there are multiple terms. foreach ($attribute_terms as $attribute_term) { printf( '<div class="wptpas-term wptpas-term-%s">%s</div>', esc_attr($attribute_term->slug), esc_html($attribute_term->name) ); }
This is our main/default PHP template. Notice that we pick up variables like $product_attribute
and $attribute_slug
because the template has been included from get_product_attribute_snippets_html()
, so we inherit that function’s scope and all its variables.
That’s all the core function and support files in place. Now we can start using it.
Display the Product Attributes
Automatically Insert the Attributes
The quickest way to get things up-and-running is to use an action-hook. We’ll use the woocommerce_product_meta_end action, but you could use any of the single product page actions if you want to inject it elsewhere. Add the following to your custom child theme’s functions.php file, after the lines where you’ve required “wpt-product-attribute-snippets.php”:
// Add the product attribute snippets add_action('woocommerce_product_meta_end', 'wptpas_product_attribute_snippets');
Save the changes and reload the single product page. If your product has some attributes on the Additional Information tab, you should see them in the product meta area now, too 👍
Using a Shortcode
It’s easy to create a little custom shortcode that lets you call the snippets from anywhere on your site. Add the following to your child theme’s functions.php to define the shortcode:
/** * Use the wpt_pa_snippets shortcode with product_id="123" to show the * attributes for product 123. If you don't specify product_id then it'll try * to use the current product automatically. */ function wptpas_do_shortcode_product_attributes($atts) { $html = ''; if (is_admin() || wp_doing_ajax()) { // Don't do anything. } elseif (!function_exists('WC')) { $html .= '<p>WooCommerce is not installed</p>'; } else { $args = shortcode_atts( array( 'product_id' => 0, // You can add more shortcode args here... ), $atts ); if (($product_id = intval($args['product_id'])) <= 0) { $product_id = false; } // If $product_id is false then wc_get_product() will try to figure out // the current product based on the current content. $product = wc_get_product($product_id); $html .= get_product_attribute_snippets_html($product); } return $html; } add_shortcode('wpt_pa_snippets', 'wptpas_do_shortcode_product_attributes');
Then you can call the shortcode from anywhere you like. You only need to pass product_id if you’re calling the shortcode from outside the product content. So, if you’re using the shortcode within a product tab, you can call the shortcode without any parameters.
Remove the “Additional Information” tab
If you’re automatically injecting the attributes into the product meta area, or if you’re adding them into the description with a shortcode, you don’t need the built-in Additional Information tab. You can remove it from the single product page with a couple of lines of code in your functions.php file:
/** * Remove the "Additional Information" tab from the single product page. */ function wptpas_remove_additional_information_tab($tabs) { unset($tabs['additional_information']); return $tabs; } add_filter('woocommerce_product_tabs', 'wptpas_remove_additional_information_tab', 99);
Restricting the List of Product Attributes
You might encounter situations where a product has many attributes, but you only want to show a subset of those as product attribute tiles. If you look in get_product_attribute_snippets_html(), you’ll see we created a custom filter called “wptpas_only_these_attributes“. We can use this filter to return an only-allow array of product attribute slugs by adding something like this to your functions.php:
/** * Restrict the product attributes that are used to create the tiles. */ function custom_wptpas_only_these_attributes($attribute_slugs) { $attribute_slugs = array( 'pa_colour', 'pa_texture', ); return $attribute_slugs; } add_filter('wptpas_only_these_attributes', 'custom_wptpas_only_these_attributes', 10, 1);
Add the Product Attribute Icons
We’ve got two ways of adding product attribute icons into the tiles…
Create a unique PHP files for each product attribute. This gives you 100% control over each snippet, so you can put whatever you want in there. Just copy “tile-default.php” to “tile-pa_size.php” (replace “pa_size” with your attribute). The only trouble is that you end up with a lot of very similar template files, and the only thing that’s different between them all is the icon HTML.
OR
Use “tile-default.php” and hook our product_attribute_icon_html filter. Here’s an example of how you can use the filter to inject Font Awesome 5 icons – just add this to your child theme’s functions.php:
/** * Specify the icon HTML for the product attribute snippets. */ function custom_product_attribute_icon_html($icon_html, $product_attribute) { $attribute_slug = $product_attribute->get_name(); switch ($attribute_slug) { case 'pa_colour': $icon_html = '<i class="fas fa-palette"></i>'; break; case 'pa_marque': $icon_html = '<i class="fas fa-industry"></i>'; break; case 'pa_size': $icon_html = '<i class="fas fa-ruler"></i>'; break; case 'pa_texture': $icon_html = '<i class="fas fa-square"></i>'; break; default: if (empty($icon_html)) { $icon_html = '<i class="fas fa-question-circle"></i>'; } break; } return $icon_html; } add_filter('product_attribute_icon_html', 'custom_product_attribute_icon_html', 10, 2);
Wrapping Up
So that’s it – you can use the get_product_attribute_snippets_html()
function in your own projects by…
- wrapping it in a shortcode
- injecting it with action hooks
- calling it directly from your own code
You can cover most cases with “tile-default.php”, and advanced cases can use template files like “tile-pa_colour.php” and “tile-pa_size.php”. The HTML is easy to customise via CSS.
Happy product attributing 😎 👍