Elementor Widget WooCommerce
August 7, 2025 · AI Conversations
—
### ✅ **Step-by-Step: Create "Latest Arrival" Elementor Widget for 5 Products**
—
### 1. **Register Custom Elementor Widget**
Create a new PHP file inside your custom plugin or child theme (e.g., `widgets/class-latest-arrivals-widget.php`):
<?php
use ElementorWidget_Base;
use ElementorControls_Manager;
if (!defined('ABSPATH')) exit;
class Latest_Arrivals_Widget extends ElementorWidget_Base {
public function get_name() {
return 'latest_arrivals';
}
public function get_title() {
return __('Latest Arrivals', 'plugin-name');
}
public function get_icon() {
return 'eicon-products';
}
public function get_categories() {
return ['general'];
}
public function render() {
$args = [
'post_type' => 'product',
'posts_per_page' => 5,
'orderby' => 'date',
'order' => 'DESC'
];
$query = new WP_Query($args);
if ($query->have_posts()) {
echo '<div class="latest-arrivals">';
while ($query->have_posts()) : $query->the_post();
global $product;
?>
<div class="arrival-item">
<div class="arrival-image">
<img src="<?php echo get_the_post_thumbnail_url(); ?>" alt="<?php the_title(); ?>">
<span class="badge">LATEST ARRIVAL</span>
</div>
<div class="arrival-content">
<h3><?php the_title(); ?></h3>
<p class="desc"><?php echo wc_trim_string(get_the_excerpt(), 20); ?></p>
<div class="price"><?php echo $product->get_price_html(); ?></div>
<a href="?add-to-cart=<?php echo esc_attr($product->get_id()); ?>" class="button">Add to Cart</a>
<a href="<?php echo get_permalink(); ?>" class="similar-button">Shop for Similar Products</a>
</div>
</div>
<?php
endwhile;
echo '</div>';
wp_reset_postdata();
}
}
}
—
### 2. **Enqueue Widget CSS**
In your plugin or child theme:
function enqueue_latest_arrival_styles() {
wp_enqueue_style('latest-arrival-style', get_stylesheet_directory_uri() . '/css/latest-arrivals.css');
}
add_action('wp_enqueue_scripts', 'enqueue_latest_arrival_styles');
—
### 3. **CSS Layout (`/css/latest-arrivals.css`)**
.latest-arrivals {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 30px;
}
.arrival-item {
border: 1px solid #ccc;
padding: 20px;
position: relative;
background: #fff;
}
.arrival-image {
position: relative;
}
.arrival-image img {
max-width: 100%;
height: auto;
}
.badge {
position: absolute;
top: 10px;
right: 10px;
background: red;
color: #fff;
font-size: 12px;
padding: 3px 8px;
}
.arrival-content h3 {
font-weight: 700;
}
.desc {
font-size: 14px;
color: #555;
}
.price {
font-size: 20px;
font-weight: bold;
margin: 10px 0;
}
.button {
display: inline-block;
background: red;
color: white;
padding: 10px 15px;
margin-right: 10px;
text-decoration: none;
border-radius: 5px;
}
.similar-button {
background: black;
color: white;
padding: 10px 15px;
text-decoration: none;
border-radius: 5px;
}
—
### 4. **Register Widget in Elementor**
In your Elementor custom plugin `plugin.php`:
add_action('elementor/widgets/widgets_registered', function($widgets_manager) {
require_once(__DIR__ . '/widgets/class-latest-arrivals-widget.php');
$widgets_manager->register(new Latest_Arrivals_Widget());
});
—
### 🔁 Optional Enhancements
– Add star ratings with `wc_get_rating_html($product->get_average_rating())`.
– Add custom fallback for products without a description.
– Use Swiper or Carousel if you want horizontal scroll.
—
Would you like me to generate this as a complete plugin zip for direct install?
> “**Bosch Easy Sander 12** has been added to your cart. [Undo]”
) when clicking the **“Add to Cart”** button in your custom Elementor widget, you should use WooCommerce’s built-in `wc_add_to_cart_message()` mechanism **and** make sure the **button uses the standard WooCommerce structure**.
—
### ✅ **Solution Overview**
You need to:
1. Use a correct WooCommerce Add to Cart URL.
2. Let WooCommerce handle the redirection/message.
3. (Optional) Use AJAX add to cart if your theme supports it.
—
### ✅ Modify the “Add to Cart” Button in Widget Render Function
Replace this:
<a href="?add-to-cart=<?php echo esc_attr($product->get_id()); ?>" class="button">Add to Cart</a>
With this:
<a href="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
data-quantity="1"
class="button add_to_cart_button ajax_add_to_cart"
data-product_id="<?php echo esc_attr( $product->get_id() ); ?>"
data-product_sku="<?php echo esc_attr( $product->get_sku() ); ?>"
aria-label="<?php echo esc_attr( $product->add_to_cart_description() ); ?>"
rel="nofollow">
<?php echo esc_html( $product->add_to_cart_text() ); ?>
</a>
✅ **Explanation**:
– `add_to_cart_url()` generates the correct link with nonce.
– `ajax_add_to_cart` class triggers WooCommerce’s AJAX handler (if your theme supports it).
– WooCommerce will then automatically show the **“Product added”** message with **Undo**.
—
### 🧩 Ensure AJAX Add to Cart is Enabled
Make sure your theme supports WooCommerce AJAX add to cart. If you use Storefront or any standard Woo theme, it already does.
Otherwise, enqueue WooCommerce's `add-to-cart.js` manually:
function enable_ajax_add_to_cart_support() {
wp_enqueue_script('wc-add-to-cart');
}
add_action('wp_enqueue_scripts', 'enable_ajax_add_to_cart_support');
—
### 🧪 Testing
After implementing:
1. Click “Add to Cart” → ✅ Product should be added with success message.
2. ✅ Message includes “Undo” link that removes product from cart.
—
Would you like me to provide a full updated version of the widget PHP file including this improvement?
1. ✅ Displays the 5 latest WooCommerce products
2. ✅ Shows **"Add to Cart"** button with correct AJAX + message + Undo
3. ✅ Displays **product rating stars** below the price
—
### ✅ **Final Widget Render Code** (replace your `render()` method with this)
public function render() {
$args = [
'post_type' => 'product',
'posts_per_page' => 5,
'orderby' => 'date',
'order' => 'DESC'
];
$query = new WP_Query($args);
if ($query->have_posts()) {
echo '<div class="latest-arrivals">';
while ($query->have_posts()) : $query->the_post();
global $product;
?>
<div class="arrival-item">
<div class="arrival-image">
<img src="<?php echo get_the_post_thumbnail_url(); ?>" alt="<?php the_title(); ?>">
<span class="badge">LATEST ARRIVAL</span>
</div>
<div class="arrival-content">
<h3><?php the_title(); ?></h3>
<p class="desc"><?php echo wc_trim_string(get_the_excerpt(), 20); ?></p>
<div class="price"><?php echo $product->get_price_html(); ?></div>
<!-- ⭐ RATING -->
<div class="rating">
<?php echo wc_get_rating_html($product->get_average_rating()); ?>
</div>
<!-- 🛒 Add to Cart Button with AJAX -->
<a href="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
data-quantity="1"
class="button add_to_cart_button ajax_add_to_cart"
data-product_id="<?php echo esc_attr( $product->get_id() ); ?>"
data-product_sku="<?php echo esc_attr( $product->get_sku() ); ?>"
aria-label="<?php echo esc_attr( $product->add_to_cart_description() ); ?>"
rel="nofollow">
<?php echo esc_html( $product->add_to_cart_text() ); ?>
</a>
<a href="<?php echo get_permalink(); ?>" class="similar-button">Shop for Similar Products</a>
</div>
</div>
<?php
endwhile;
echo '</div>';
wp_reset_postdata();
}
}
—
### 🧩 Add Rating Styles (CSS)
In your `latest-arrivals.css`:
.rating {
margin: 10px 0;
}
.star-rating {
color: #f5a623;
font-size: 16px;
}
—
### ✅ Result:
– Looks like your original layout
– Shows star rating if available
– “Add to Cart” triggers WooCommerce’s native AJAX add-to-cart with message & Undo
– Fully dynamic
—
Let me know if you want this packed into a ready-to-install plugin ZIP or converted into an Elementor Pro dynamic loop instead.
– ✅ **One product per row**, matching your screenshot layout
– ✅ “Latest Arrival” badge
– ✅ Product image left, content right
– ✅ Title, short description, price, rating stars
– ✅ "Add to Cart" button (Woo default AJAX + message)
– ✅ "Shop for Similar Products" button
—
### ✅ Final Widget `render()` Code (Single Product per Row, Styled)
Replace your widget's `render()` method with:
public function render() {
$args = [
'post_type' => 'product',
'posts_per_page' => 5,
'orderby' => 'date',
'order' => 'DESC'
];
$query = new WP_Query($args);
if ($query->have_posts()) {
echo '<div class="latest-arrivals-wrapper">';
while ($query->have_posts()) : $query->the_post();
global $product;
?>
<div class="latest-arrival-row">
<div class="arrival-image">
<img src="<?php echo get_the_post_thumbnail_url(); ?>" alt="<?php the_title(); ?>">
<div class="arrival-badge">LATEST ARRIVAL</div>
</div>
<div class="arrival-content">
<h3 class="arrival-title"><?php the_title(); ?></h3>
<p class="arrival-subtitle"><?php echo $product->get_short_description(); ?></p>
<div class="arrival-price"><?php echo $product->get_price_html(); ?></div>
<div class="arrival-rating"><?php echo wc_get_rating_html($product->get_average_rating()); ?></div>
<div class="arrival-actions">
<a href="<?php echo esc_url( $product->add_to_cart_url() ); ?>"
data-quantity="1"
class="arrival-cart-btn button add_to_cart_button ajax_add_to_cart"
data-product_id="<?php echo esc_attr( $product->get_id() ); ?>"
data-product_sku="<?php echo esc_attr( $product->get_sku() ); ?>"
aria-label="<?php echo esc_attr( $product->add_to_cart_description() ); ?>"
rel="nofollow">
<?php echo esc_html( $product->add_to_cart_text() ); ?>
</a>
<a href="<?php echo get_permalink(); ?>" class="arrival-similar-btn">SHOP FOR SIMILAR PRODUCTS</a>
</div>
</div>
</div>
<?php
endwhile;
echo '</div>';
wp_reset_postdata();
}
}
—
### 🎨 CSS for 1-Row Layout (in `/css/latest-arrivals.css`)
.latest-arrivals-wrapper {
display: flex;
flex-direction: column;
gap: 40px;
}
.latest-arrival-row {
display: flex;
flex-wrap: wrap;
border: 1px solid #ccc;
padding: 25px;
align-items: center;
background: #fff;
position: relative;
}
.arrival-image {
flex: 0 0 250px;
margin-right: 30px;
position: relative;
}
.arrival-image img {
width: 100%;
height: auto;
border-radius: 6px;
}
.arrival-badge {
position: absolute;
top: 10px;
right: 10px;
background: red;
color: #fff;
font-weight: bold;
font-size: 12px;
padding: 5px 12px;
border-radius: 3px;
}
.arrival-content {
flex: 1;
}
.arrival-title {
font-weight: 700;
font-size: 18px;
margin-bottom: 5px;
}
.arrival-subtitle {
font-size: 14px;
color: #555;
margin-bottom: 10px;
}
.arrival-price {
font-size: 22px;
font-weight: bold;
margin-bottom: 10px;
color: #000;
}
.arrival-rating {
margin-bottom: 15px;
}
.arrival-actions {
display: flex;
gap: 15px;
flex-wrap: wrap;
}
.arrival-cart-btn {
background: #ff0000;
color: #fff;
padding: 10px 20px;
font-weight: bold;
border: none;
border-radius: 4px;
text-transform: uppercase;
transition: background 0.3s;
}
.arrival-cart-btn:hover {
background: #cc0000;
}
.arrival-similar-btn {
background: #000;
color: #fff;
padding: 10px 20px;
font-weight: bold;
text-transform: uppercase;
text-decoration: none;
border-radius: 4px;
transition: background 0.3s;
}
.arrival-similar-btn:hover {
background: #222;
}
—
### ✅ Enqueue CSS in Your Plugin or Theme
function enqueue_latest_arrival_styles() {
wp_enqueue_style('latest-arrival-style', get_stylesheet_directory_uri() . '/css/latest-arrivals.css');
}
add_action('wp_enqueue_scripts', 'enqueue_latest_arrival_styles');
—
Let me know if you'd like:
– 🎁 A ZIP file ready to install as a plugin
– 🧩 Conversion to an Elementor Loop Template (Pro)
– 🛍️ Add "Sale" tag or out-of-stock label
Happy to help polish this even further!
image
then
right side
description,star and shop for similar product button
—
### ✅ **Product Row Layout (One per row):**
**Left Side (stacked vertically):**
– Product **Title**
– **Price**
– **Add to Cart** button
**Middle:**
– Product **Image**
**Right Side (stacked vertically):**
– **Description**
– **Star Rating**
– **"Shop for Similar Products"** button
—
### ✅ Updated `render()` Code
Replace your widget's `render()` function with this:
public function render() {
$args = [
'post_type' => 'product',
'posts_per_page' => 5,
'orderby' => 'date',
'order' => 'DESC'
];
$query = new WP_Query($args);
if ($query->have_posts()) {
echo '<div class="latest-arrivals-wrapper">';
while ($query->have_posts()) : $query->the_post();
global $product;
?>
<div class="latest-arrival-row">
<!-- LEFT: Title, Price, Add to Cart -->
<div class="arrival-left">
<h3 class="arrival-title"><?php the_title(); ?></h3>
<div class="arrival-price"><?php echo $product->get_price_html(); ?></div>
<a href="<?php echo esc_url($product->add_to_cart_url()); ?>"
data-quantity="1"
class="arrival-cart-btn button add_to_cart_button ajax_add_to_cart"
data-product_id="<?php echo esc_attr($product->get_id()); ?>"
data-product_sku="<?php echo esc_attr($product->get_sku()); ?>"
aria-label="<?php echo esc_attr($product->add_to_cart_description()); ?>"
rel="nofollow">
<?php echo esc_html($product->add_to_cart_text()); ?>
</a>
</div>
<!-- MIDDLE: Product Image -->
<div class="arrival-image">
<img src="<?php echo get_the_post_thumbnail_url(); ?>" alt="<?php the_title(); ?>">
<div class="arrival-badge">LATEST ARRIVAL</div>
</div>
<!-- RIGHT: Description, Rating, Similar Btn -->
<div class="arrival-right">
<p class="arrival-subtitle"><?php echo $product->get_short_description(); ?></p>
<div class="arrival-rating"><?php echo wc_get_rating_html($product->get_average_rating()); ?></div>
<a href="<?php echo get_permalink(); ?>" class="arrival-similar-btn">SHOP FOR SIMILAR PRODUCTS</a>
</div>
</div>
<?php
endwhile;
echo '</div>';
wp_reset_postdata();
}
}
—
### 🎨 Updated CSS (`latest-arrivals.css`)
.latest-arrivals-wrapper {
display: flex;
flex-direction: column;
gap: 40px;
}
.latest-arrival-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 30px;
padding: 25px;
border: 1px solid #ccc;
background: #fff;
flex-wrap: wrap;
position: relative;
}
/* LEFT COLUMN */
.arrival-left {
flex: 0 0 200px;
display: flex;
flex-direction: column;
gap: 12px;
}
.arrival-title {
font-size: 18px;
font-weight: 700;
margin: 0;
}
.arrival-price {
font-size: 20px;
font-weight: bold;
color: #000;
}
.arrival-cart-btn {
background: #ff0000;
color: white;
padding: 10px 20px;
font-weight: bold;
border-radius: 5px;
text-transform: uppercase;
text-align: center;
}
/* IMAGE COLUMN */
.arrival-image {
flex: 0 0 200px;
position: relative;
}
.arrival-image img {
width: 100%;
height: auto;
border-radius: 6px;
}
.arrival-badge {
position: absolute;
top: 10px;
right: 10px;
background: red;
color: #fff;
font-weight: bold;
font-size: 12px;
padding: 5px 10px;
border-radius: 3px;
}
/* RIGHT COLUMN */
.arrival-right {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.arrival-subtitle {
font-size: 14px;
color: #444;
}
.arrival-rating .star-rating {
color: #f5a623;
}
.arrival-similar-btn {
background: #000;
color: #fff;
padding: 10px 20px;
font-weight: bold;
text-transform: uppercase;
border-radius: 4px;
text-decoration: none;
width: fit-content;
}
—
### ✅ Result
Each row will now display:
| Title |
| Price | [Product Image] | Description |
| Add to Cart | ★★★★★ |
| Shop for Similar Btn |
Would you like this bundled into a downloadable WordPress plugin zip file?
✅ Instead, use this fallback approach to **always show a trimmed excerpt** from the post content or excerpt field:
—
### ✅ Recommended Code for Description (Handles All Cases)
Replace:
<?php echo $product->get_short_description(); ?>
With:
<?php
$desc = get_the_excerpt();
if (empty($desc)) {
$desc = wp_trim_words(strip_tags(get_the_content()), 20, '...');
}
echo esc_html($desc);
?>
—
### ✅ What It Does:
– Tries `get_the_excerpt()` first.
– If it's empty, it trims the post content to 20 words.
– Escapes it safely for output.
—
Now your updated **description block** in `arrival-right` will reliably show a short summary for each product.
Let me know if you want to:
– ✅ Add support for multilingual (e.g. WPML)
– ✅ Limit by characters instead of words
– ✅ Add a “Read More” link after the excerpt
Ready for any of those!
—
### ✅ Final CSS Update to Justify Sections
Update your `.latest-arrival-row` block to this:
.latest-arrival-row {
display: flex;
justify-content: space-between; /* this is the key */
align-items: flex-start;
gap: 30px;
padding: 25px;
border: 1px solid #ccc;
background: #fff;
flex-wrap: wrap;
position: relative;
}
—
### 🔧 Additional Tweaks for Consistency
To ensure each section has the right sizing:
.arrival-left {
flex: 0 0 220px;
display: flex;
flex-direction: column;
gap: 12px;
}
.arrival-image {
flex: 0 0 200px;
text-align: center;
}
.arrival-right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 10px;
}
—
### ✅ Result
Your product row layout will now be neatly spaced out:
| Title + Price + Cart | Product Image | Description + Rating + Button |
All three sections will distribute across the full row with even space between them.
Let me know if you'd like:
– Items to align **vertically centered**
– Mobile responsive behavior (stacked layout)
– A plugin zip with all this built-in
Happy to bundle it!
get_description is working
—
### ✅ Correct Usage in WooCommerce:
– `$product->get_short_description()` → may return nothing unless explicitly set in **Product > Short Description**
– `$product->get_description()` → returns the **main long description** (from the editor content)
—
### 🔄 Recommended Logic for Your Widget:
To ensure a fallback and still get a trimmed, presentable description:
<?php
$desc = $product->get_short_description();
if (empty($desc)) {
$desc = wp_trim_words(strip_tags($product->get_description()), 20, '...');
}
echo esc_html($desc);
?>
—
### ✅ Why This Is Best:
– If Short Description is set → it shows that.
– Else → it trims the main product description (long content).
– Keeps output clean and safe.
Let me know if you want:
– A character limit instead of words
– HTML formatting allowed (like “ or “)
– Mobile styling adjustments
Happy to include!