Theme Customization
Customizing WooCommerce themes requires understanding CSS styling, WordPress hooks, child theme architecture, and upgrade-safe development practices. This guide covers all aspects of theme customization from basic styling to advanced functionality modifications.
Child Themes
Child themes provide an upgrade-safe way to customize parent themes. Changes in child themes persist through parent theme updates, preventing loss of customizations.
Why Use Child Themes?
Update Safety Customizations survive parent theme updates without being overwritten
Organized Customization All modifications live in one directory, separate from parent theme
Easy Rollback Deactivate child theme to revert to parent theme instantly
Development Flexibility Override any parent template or add new functionality independently
Creating a Child Theme
Create Child Theme Directory
Create a new directory in wp-content/themes/: wp-content/themes/storefront-child/
Name it descriptively: parenttheme-child
Create style.css
Create style.css with required header information: /*
Theme Name: Storefront Child
Theme URI: https://example.com/storefront-child
Description: Child theme for Storefront
Author: Your Name
Author URI: https://example.com
Template: storefront
Version: 1.0.0
License: GNU General Public License v2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Text Domain: storefront-child
*/
/* --------------- Theme customization starts here ----------------- */
Critical: Template must match parent theme directory name exactly.
Create functions.php
Create a blank functions.php file: <? php
/**
* Storefront Child Theme Functions
*/
// Add your custom functions here
Do NOT copy parent theme’s functions.php content. Child theme functions.php loads before parent theme’s.
Activate Child Theme
Go to Appearance → Themes and activate your child theme. The parent theme must remain installed.
Child Theme File Structure
storefront-child/
├── style.css # Required: Theme stylesheet and metadata
├── functions.php # Theme functions and hooks
├── screenshot.png # Theme thumbnail (880x660px recommended)
├── woocommerce/ # WooCommerce template overrides
│ ├── single-product.php
│ ├── archive-product.php
│ └── cart/
│ └── cart.php
├── template-parts/ # Custom template parts
├── assets/
│ ├── css/
│ │ └── custom.css
│ ├── js/
│ │ └── custom.js
│ └── images/
└── languages/ # Translation files
CSS Customization
CSS provides the primary method for visual customization. WooCommerce includes comprehensive CSS architecture that can be extended or completely replaced.
Basic CSS Overrides
Add custom CSS to child theme’s style.css:
/* Customize WooCommerce buttons */
.woocommerce a .button ,
.woocommerce button .button ,
.woocommerce input .button ,
.woocommerce #respond input #submit {
background-color : #2c3e50 ;
color : #ffffff ;
border-radius : 4 px ;
padding : 12 px 24 px ;
font-weight : 600 ;
text-transform : uppercase ;
letter-spacing : 0.5 px ;
}
.woocommerce a .button:hover ,
.woocommerce button .button:hover ,
.woocommerce input .button:hover {
background-color : #34495e ;
}
/* Product page layout */
.woocommerce div .product .summary {
margin-bottom : 2 rem ;
}
.woocommerce div .product .woocommerce-product-gallery {
margin-bottom : 2 rem ;
}
/* Price styling */
.woocommerce div .product p .price ,
.woocommerce div .product span .price {
color : #e63946 ;
font-size : 1.75 rem ;
font-weight : 700 ;
}
/* Sale badge */
.woocommerce span .onsale {
background-color : #e63946 ;
color : #ffffff ;
border-radius : 50 % ;
padding : 0.5 rem ;
}
Targeting WooCommerce Body Classes
WooCommerce adds helpful body classes for targeting specific pages:
/* Shop page only */
.woocommerce-shop .site-header {
background-color : #f8f9fa ;
}
/* Single product pages */
.single-product .site-main {
max-width : 1200 px ;
margin : 0 auto ;
}
/* Product category pages */
.woocommerce-archive .woocommerce-breadcrumb {
margin-bottom : 2 rem ;
}
/* Cart page */
.woocommerce-cart .woocommerce {
padding : 2 rem ;
}
/* Checkout page */
.woocommerce-checkout .woocommerce-form-coupon-toggle {
display : none ;
}
Product Loop Customization
Customize product grid displays:
/* Product grid layout */
.woocommerce ul .products {
display : grid ;
grid-template-columns : repeat ( auto-fill , minmax ( 250 px , 1 fr ));
gap : 2 rem ;
}
.woocommerce ul .products li .product {
border : 1 px solid #e0e0e0 ;
border-radius : 8 px ;
padding : 1.5 rem ;
transition : transform 0.3 s ease , box-shadow 0.3 s ease ;
}
.woocommerce ul .products li .product:hover {
transform : translateY ( -4 px );
box-shadow : 0 4 px 12 px rgba ( 0 , 0 , 0 , 0.1 );
}
/* Product images in loop */
.woocommerce ul .products li .product img {
border-radius : 4 px ;
margin-bottom : 1 rem ;
}
/* Product titles */
.woocommerce ul .products li .product .woocommerce-loop-product__title {
font-size : 1.125 rem ;
font-weight : 600 ;
margin-bottom : 0.5 rem ;
}
/* Add to cart buttons in loop */
.woocommerce ul .products li .product .button {
width : 100 % ;
text-align : center ;
}
Responsive Design
Add mobile-responsive styles:
/* Mobile styles */
@media ( max-width : 768 px ) {
.woocommerce div .product div .images ,
.woocommerce div .product div .summary {
width : 100 % ;
float : none ;
}
.woocommerce table .shop_table {
font-size : 0.875 rem ;
}
.woocommerce ul .products {
grid-template-columns : repeat ( auto-fill , minmax ( 150 px , 1 fr ));
gap : 1 rem ;
}
}
/* Tablet styles */
@media ( min-width : 769 px ) and ( max-width : 1024 px ) {
.woocommerce ul .products {
grid-template-columns : repeat ( 3 , 1 fr );
}
}
/* Desktop styles */
@media ( min-width : 1025 px ) {
.woocommerce ul .products {
grid-template-columns : repeat ( 4 , 1 fr );
}
}
Disabling WooCommerce Styles
For complete CSS control, disable WooCommerce’s default stylesheet:
// In child theme's functions.php
add_filter ( 'woocommerce_enqueue_styles' , '__return_false' );
Disabling WooCommerce styles requires styling all WooCommerce elements from scratch. Only use this for complete redesigns. See the WooCommerce Theme Testing Checklist for elements that need styling.
Hooks and Filters
WordPress hooks provide upgrade-safe functionality customization without modifying core files.
Understanding Actions vs Filters
Actions execute functions at specific points. Use for adding/removing content.// Add custom content after product title
add_action ( 'woocommerce_single_product_summary' , 'mytheme_custom_badge' , 6 );
function mytheme_custom_badge () {
global $product ;
if ( $product -> is_featured () ) {
echo '<div class="featured-badge">Featured Product</div>' ;
}
}
Priority matters: Lower numbers execute earlier (default is 10).Filters modify data before use. Use for changing values or output.// Modify "Add to cart" button text
add_filter ( 'woocommerce_product_single_add_to_cart_text' , 'mytheme_custom_cart_button_text' );
function mytheme_custom_cart_button_text ( $text ) {
return 'Buy Now' ;
}
Always return modified value in filter functions.
Common Customization Hooks
Product Page Hooks
Shop Loop Hooks
Cart & Checkout Hooks
Email Hooks
/**
* Add custom content to product page
*/
// Add delivery information before add to cart
add_action ( 'woocommerce_before_add_to_cart_form' , 'mytheme_delivery_info' );
function mytheme_delivery_info () {
echo '<div class="delivery-info">' ;
echo '<p>Free delivery on orders over $50</p>' ;
echo '</div>' ;
}
// Add trust badges after price
add_action ( 'woocommerce_single_product_summary' , 'mytheme_trust_badges' , 11 );
function mytheme_trust_badges () {
echo '<div class="trust-badges">' ;
echo '<img src="' . get_stylesheet_directory_uri () . '/assets/images/secure-checkout.png" alt="Secure Checkout">' ;
echo '</div>' ;
}
// Remove product meta (SKU, categories, tags)
remove_action ( 'woocommerce_single_product_summary' , 'woocommerce_template_single_meta' , 40 );
// Reposition price after product title
remove_action ( 'woocommerce_single_product_summary' , 'woocommerce_template_single_price' , 10 );
add_action ( 'woocommerce_single_product_summary' , 'woocommerce_template_single_price' , 6 );
Removing Default Hooks
Remove WooCommerce’s default functionality:
/**
* Remove WooCommerce default actions
*/
// Remove breadcrumbs
remove_action ( 'woocommerce_before_main_content' , 'woocommerce_breadcrumb' , 20 );
// Remove product rating
remove_action ( 'woocommerce_single_product_summary' , 'woocommerce_template_single_rating' , 10 );
// Remove related products
remove_action ( 'woocommerce_after_single_product_summary' , 'woocommerce_output_related_products' , 20 );
// Remove upsells
remove_action ( 'woocommerce_after_single_product_summary' , 'woocommerce_upsell_display' , 15 );
// Remove product tabs
remove_action ( 'woocommerce_after_single_product_summary' , 'woocommerce_output_product_data_tabs' , 10 );
Always use the same priority when removing an action that was used when adding it. Check WooCommerce template files for default priorities.
Enqueuing Custom Assets
Add custom CSS and JavaScript files properly:
/**
* Enqueue child theme styles and scripts
*/
function mytheme_enqueue_assets () {
// Enqueue parent theme styles (if needed)
wp_enqueue_style ( 'parent-style' ,
get_template_directory_uri () . '/style.css'
);
// Enqueue child theme styles
wp_enqueue_style ( 'child-style' ,
get_stylesheet_directory_uri () . '/style.css' ,
array ( 'parent-style' ),
wp_get_theme () -> get ( 'Version' )
);
// Enqueue custom CSS
wp_enqueue_style ( 'custom-woocommerce' ,
get_stylesheet_directory_uri () . '/assets/css/custom.css' ,
array ( 'woocommerce-general' ),
'1.0.0'
);
// Enqueue custom JavaScript
wp_enqueue_script ( 'custom-woocommerce-js' ,
get_stylesheet_directory_uri () . '/assets/js/custom.js' ,
array ( 'jquery' , 'wc-cart-fragments' ),
'1.0.0' ,
true
);
// Localize script with AJAX URL
wp_localize_script ( 'custom-woocommerce-js' , 'mytheme_vars' , array (
'ajax_url' => admin_url ( 'admin-ajax.php' ),
'nonce' => wp_create_nonce ( 'mytheme_nonce' ),
) );
}
add_action ( 'wp_enqueue_scripts' , 'mytheme_enqueue_assets' );
/**
* Enqueue custom scripts only on WooCommerce pages
*/
function mytheme_woocommerce_scripts () {
if ( is_woocommerce () || is_cart () || is_checkout () ) {
wp_enqueue_script ( 'mytheme-woocommerce' ,
get_stylesheet_directory_uri () . '/assets/js/woocommerce.js' ,
array ( 'jquery' ),
'1.0.0' ,
true
);
}
}
add_action ( 'wp_enqueue_scripts' , 'mytheme_woocommerce_scripts' );
Template Overrides in Child Themes
Override WooCommerce templates by copying them to child theme:
wp-content/themes/storefront-child/woocommerce/
├── single-product.php
├── archive-product.php
├── cart/
│ └── cart.php
└── single-product/
├── title.php
└── price.php
Best Practices for Template Overrides
Copy Complete Template
Always copy the entire template file, never partial code.
Document Version
Add a comment noting the WooCommerce version you copied from: /**
* Overridden template from WooCommerce 8.5.0
* @version 3.6.0
*/
Preserve Hooks
Never remove do_action() or apply_filters() calls. Plugins depend on these hooks.
Monitor Updates
Check WooCommerce → Status → System Status for outdated template warnings.
Advanced Customization Techniques
Adding Custom Product Fields
/**
* Add custom product field to admin
*/
add_action ( 'woocommerce_product_options_general_product_data' , 'mytheme_custom_field' );
function mytheme_custom_field () {
woocommerce_wp_text_input ( array (
'id' => '_custom_field' ,
'label' => 'Custom Field' ,
'desc_tip' => 'true' ,
'description' => 'Enter custom information here' ,
) );
}
/**
* Save custom product field
*/
add_action ( 'woocommerce_process_product_meta' , 'mytheme_save_custom_field' );
function mytheme_save_custom_field ( $post_id ) {
$custom_field = isset ( $_POST [ '_custom_field' ] ) ? sanitize_text_field ( $_POST [ '_custom_field' ] ) : '' ;
update_post_meta ( $post_id , '_custom_field' , $custom_field );
}
/**
* Display custom field on product page
*/
add_action ( 'woocommerce_single_product_summary' , 'mytheme_display_custom_field' , 25 );
function mytheme_display_custom_field () {
global $product ;
$custom_field = get_post_meta ( $product -> get_id (), '_custom_field' , true );
if ( $custom_field ) {
echo '<div class="custom-field">' . esc_html ( $custom_field ) . '</div>' ;
}
}
Modifying Product Query
/**
* Exclude specific products from shop page
*/
add_action ( 'woocommerce_product_query' , 'mytheme_exclude_products' );
function mytheme_exclude_products ( $q ) {
if ( is_shop () ) {
$q -> set ( 'post__not_in' , array ( 123 , 456 , 789 ) );
}
}
/**
* Show only products on sale
*/
add_action ( 'woocommerce_product_query' , 'mytheme_show_only_sale_products' );
function mytheme_show_only_sale_products ( $q ) {
if ( isset ( $_GET [ 'on_sale' ] ) && $_GET [ 'on_sale' ] === '1' ) {
$q -> set ( 'post__in' , array_merge ( array ( 0 ), wc_get_product_ids_on_sale () ) );
}
}
Custom Cart Item Data
/**
* Add custom data to cart item
*/
add_filter ( 'woocommerce_add_cart_item_data' , 'mytheme_add_cart_item_data' , 10 , 2 );
function mytheme_add_cart_item_data ( $cart_item_data , $product_id ) {
if ( isset ( $_POST [ 'custom_option' ] ) ) {
$cart_item_data [ 'custom_option' ] = sanitize_text_field ( $_POST [ 'custom_option' ] );
}
return $cart_item_data ;
}
/**
* Display custom data in cart
*/
add_filter ( 'woocommerce_get_item_data' , 'mytheme_display_cart_item_data' , 10 , 2 );
function mytheme_display_cart_item_data ( $item_data , $cart_item ) {
if ( isset ( $cart_item [ 'custom_option' ] ) ) {
$item_data [] = array (
'name' => 'Custom Option' ,
'value' => $cart_item [ 'custom_option' ],
);
}
return $item_data ;
}
Best Practices
Always Use Child Themes Never modify parent themes or plugin files directly. Use child themes for all customizations.
Prefer Hooks Over Templates Use hooks whenever possible. They’re more maintainable than template overrides.
Sanitize and Validate Always sanitize user input and validate data before saving or displaying.
Test After Updates Test all customizations after WooCommerce, WordPress, or theme updates.
Document Your Code Add clear comments explaining what customizations do and why they exist.
Version Control Use Git or similar version control for all theme customizations.
Monitor Template Versions Regularly check System Status for outdated template warnings.
Backup Before Changes Always backup your site before making significant customizations.
Debugging and Testing
Enable Debugging
Add to wp-config.php for development:
define ( 'WP_DEBUG' , true );
define ( 'WP_DEBUG_LOG' , true );
define ( 'WP_DEBUG_DISPLAY' , false );
define ( 'SCRIPT_DEBUG' , true );
Template Debugging
Identify which template is loading:
/**
* Show template file path in HTML comments
*/
add_action ( 'woocommerce_before_template_part' , 'mytheme_template_debug_start' , 10 , 4 );
function mytheme_template_debug_start ( $template_name , $template_path , $located , $args ) {
if ( defined ( 'WP_DEBUG' ) && WP_DEBUG ) {
echo " \n <!-- Template: " . esc_html ( $template_name ) . " --> \n " ;
}
}
Testing Checklist
Test All Page Types
Verify customizations on shop, product, cart, checkout, and account pages.
Test Responsive Design
Check mobile, tablet, and desktop views for all customizations.
Test Product Types
Ensure customizations work with simple, variable, grouped, and external products.
Cross-Browser Testing
Test in Chrome, Firefox, Safari, and Edge browsers.
Performance Testing
Measure page load times before and after customizations.
Common Pitfalls to Avoid
Never modify core WooCommerce files. Updates will overwrite your changes. Always use hooks or template overrides.
Don’t remove template hooks. Plugins rely on these hooks. Removing them breaks extensibility.
Avoid using !important in CSS. It creates specificity issues. Use more specific selectors instead.
Don’t override functions.php completely. Child theme functions.php should only contain additions, not parent theme content.
Resources and References
Next Steps
Classic Theme Development Deep dive into PHP-based theme development
Block Theme Development Explore modern FSE and block theme architecture