Stock Status Widget for Elementor

A lightweight Elementor widget that checks the product’s stock status within a loop and displays a conditional badge (visible only when the product is out of stock, or configurable to display all statuses).

Two display modes configurable in the widget controls: either visible only when the product is out of stock or being restocked (default mode, ideal for product cards), or always visible with all 4 statuses (in stock, low stock, restocking, out of stock).

Smart low-stock detection: it cross-references `get_stock_status()` with `get_stock_quantity()` and the `woocommerce_notify_low_stock_amount` threshold to distinguish a product that is “in stock” from one with “low stock”—something WooCommerce does not do natively in its stock status.

Full Elementor controls in the Style tab: typography, text/background colors by status (via separate tabs), padding, border-radius, alignment, icon size, and spacing. Everything is controlled by Elementor selectors, so it works responsively and in the editor preview.

Loop Builder compatibility: the widget retrieves the current product via `wc_get_product( get_the_ID() )` as a fallback for the global `$product`, and displays an “Out of Stock” preview in the editor when there is no product in context.

To install it: place the file in wp-content/mu-plugins/.
The widget will appear in the “WooCommerce” category in Elementor under the name “Wycan – Stock Status,” ready to be dragged into the Loop Builder template.

				
					<?php
/**
 * Plugin Name: Wycan - Stock Status Widget for Elementor
 * Description: Widget Elementor affichant le statut de stock des produits WooCommerce. Conçu pour le Loop Builder (cards produit dans les archives/catégories).
 * Version: 1.0.0
 * Author: Wycan
 * Author URI: https://wycan.fr
 * Requires Plugins: elementor, woocommerce
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Enregistrement du widget auprès d'Elementor
 */
add_action( 'elementor/widgets/register', function ( $widgets_manager ) {

	if ( ! class_exists( 'WooCommerce' ) ) {
		return;
	}

	class Wycan_Stock_Status_Widget extends \Elementor\Widget_Base {

		public function get_name() {
			return 'wycan-stock-status';
		}

		public function get_title() {
			return 'Wycan – Statut Stock';
		}

		public function get_icon() {
			return 'eicon-info-circle-o';
		}

		public function get_categories() {
			return [ 'woocommerce-elements', 'general' ];
		}

		public function get_keywords() {
			return [ 'stock', 'rupture', 'disponibilité', 'woocommerce', 'wycan' ];
		}

		/* =====================================================================
		   CONTRÔLES ELEMENTOR
		   ===================================================================== */
		protected function register_controls() {

			/* ---- Section : Contenu ---- */
			$this->start_controls_section( 'section_content', [
				'label' => 'Contenu',
				'tab'   => \Elementor\Controls_Manager::TAB_CONTENT,
			] );

			$this->add_control( 'display_mode', [
				'label'   => 'Affichage',
				'type'    => \Elementor\Controls_Manager::SELECT,
				'default' => 'out_of_stock_only',
				'options' => [
					'out_of_stock_only' => 'Uniquement si rupture de stock',
					'always'            => 'Toujours (tous les statuts)',
				],
			] );

			$this->add_control( 'label_in_stock', [
				'label'     => 'Libellé – En stock',
				'type'      => \Elementor\Controls_Manager::TEXT,
				'default'   => 'En stock',
				'condition' => [ 'display_mode' => 'always' ],
			] );

			$this->add_control( 'label_low_stock', [
				'label'     => 'Libellé – Stock faible',
				'type'      => \Elementor\Controls_Manager::TEXT,
				'default'   => 'Stock faible',
				'condition' => [ 'display_mode' => 'always' ],
			] );

			$this->add_control( 'label_on_backorder', [
				'label'   => 'Libellé – En réapprovisionnement',
				'type'    => \Elementor\Controls_Manager::TEXT,
				'default' => 'En réapprovisionnement',
			] );

			$this->add_control( 'label_out_of_stock', [
				'label'   => 'Libellé – Rupture de stock',
				'type'    => \Elementor\Controls_Manager::TEXT,
				'default' => 'Rupture de stock',
			] );

			$this->add_control( 'show_icon', [
				'label'        => 'Afficher une icône',
				'type'         => \Elementor\Controls_Manager::SWITCHER,
				'default'      => 'yes',
				'label_on'     => 'Oui',
				'label_off'    => 'Non',
			] );

			$this->end_controls_section();

			/* ---- Section : Style – Badge ---- */
			$this->start_controls_section( 'section_style_badge', [
				'label' => 'Badge',
				'tab'   => \Elementor\Controls_Manager::TAB_STYLE,
			] );

			$this->add_responsive_control( 'badge_alignment', [
				'label'   => 'Alignement',
				'type'    => \Elementor\Controls_Manager::CHOOSE,
				'options' => [
					'flex-start' => [ 'title' => 'Gauche',  'icon' => 'eicon-text-align-left' ],
					'center'     => [ 'title' => 'Centre',  'icon' => 'eicon-text-align-center' ],
					'flex-end'   => [ 'title' => 'Droite',  'icon' => 'eicon-text-align-right' ],
				],
				'default'   => 'flex-start',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status' => 'justify-content: {{VALUE}};',
				],
			] );

			$this->add_group_control( \Elementor\Group_Control_Typography::get_type(), [
				'name'     => 'badge_typography',
				'selector' => '{{WRAPPER}} .wycan-stock-status__badge',
			] );

			/* -- Onglets couleurs par statut -- */
			$this->start_controls_tabs( 'tabs_badge_colors' );

			// En stock
			$this->start_controls_tab( 'tab_in_stock', [
				'label'     => 'En stock',
				'condition' => [ 'display_mode' => 'always' ],
			] );
			$this->add_control( 'color_in_stock', [
				'label'     => 'Couleur texte',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#16a34a',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--instock' => 'color: {{VALUE}};',
				],
			] );
			$this->add_control( 'bg_in_stock', [
				'label'     => 'Couleur fond',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#dcfce7',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--instock' => 'background-color: {{VALUE}};',
				],
			] );
			$this->end_controls_tab();

			// Stock faible
			$this->start_controls_tab( 'tab_low_stock', [
				'label'     => 'Stock faible',
				'condition' => [ 'display_mode' => 'always' ],
			] );
			$this->add_control( 'color_low_stock', [
				'label'     => 'Couleur texte',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#ca8a04',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--lowstock' => 'color: {{VALUE}};',
				],
			] );
			$this->add_control( 'bg_low_stock', [
				'label'     => 'Couleur fond',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#fef9c3',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--lowstock' => 'background-color: {{VALUE}};',
				],
			] );
			$this->end_controls_tab();

			// Réapprovisionnement
			$this->start_controls_tab( 'tab_backorder', [ 'label' => 'Réappro.' ] );
			$this->add_control( 'color_backorder', [
				'label'     => 'Couleur texte',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#2563eb',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--onbackorder' => 'color: {{VALUE}};',
				],
			] );
			$this->add_control( 'bg_backorder', [
				'label'     => 'Couleur fond',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#dbeafe',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--onbackorder' => 'background-color: {{VALUE}};',
				],
			] );
			$this->end_controls_tab();

			// Rupture
			$this->start_controls_tab( 'tab_out_of_stock', [ 'label' => 'Rupture' ] );
			$this->add_control( 'color_out_of_stock', [
				'label'     => 'Couleur texte',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#dc2626',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--outofstock' => 'color: {{VALUE}};',
				],
			] );
			$this->add_control( 'bg_out_of_stock', [
				'label'     => 'Couleur fond',
				'type'      => \Elementor\Controls_Manager::COLOR,
				'default'   => '#fee2e2',
				'selectors' => [
					'{{WRAPPER}} .wycan-stock-status__badge--outofstock' => 'background-color: {{VALUE}};',
				],
			] );
			$this->end_controls_tab();

			$this->end_controls_tabs();

			$this->add_responsive_control( 'badge_padding', [
				'label'      => 'Padding',
				'type'       => \Elementor\Controls_Manager::DIMENSIONS,
				'size_units' => [ 'px', 'em', '%' ],
				'default'    => [
					'top'    => '4',
					'right'  => '10',
					'bottom' => '4',
					'left'   => '10',
					'unit'   => 'px',
				],
				'selectors'  => [
					'{{WRAPPER}} .wycan-stock-status__badge' => 'padding: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
				],
				'separator' => 'before',
			] );

			$this->add_control( 'badge_border_radius', [
				'label'      => 'Border Radius',
				'type'       => \Elementor\Controls_Manager::DIMENSIONS,
				'size_units' => [ 'px', '%' ],
				'default'    => [
					'top'    => '4',
					'right'  => '4',
					'bottom' => '4',
					'left'   => '4',
					'unit'   => 'px',
				],
				'selectors'  => [
					'{{WRAPPER}} .wycan-stock-status__badge' => 'border-radius: {{TOP}}{{UNIT}} {{RIGHT}}{{UNIT}} {{BOTTOM}}{{UNIT}} {{LEFT}}{{UNIT}};',
				],
			] );

			$this->end_controls_section();

			/* ---- Section : Style – Icône ---- */
			$this->start_controls_section( 'section_style_icon', [
				'label'     => 'Icône',
				'tab'       => \Elementor\Controls_Manager::TAB_STYLE,
				'condition' => [ 'show_icon' => 'yes' ],
			] );

			$this->add_responsive_control( 'icon_size', [
				'label'      => 'Taille',
				'type'       => \Elementor\Controls_Manager::SLIDER,
				'size_units' => [ 'px' ],
				'range'      => [ 'px' => [ 'min' => 8, 'max' => 32 ] ],
				'default'    => [ 'size' => 14, 'unit' => 'px' ],
				'selectors'  => [
					'{{WRAPPER}} .wycan-stock-status__icon svg' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
				],
			] );

			$this->add_responsive_control( 'icon_gap', [
				'label'      => 'Espacement',
				'type'       => \Elementor\Controls_Manager::SLIDER,
				'size_units' => [ 'px' ],
				'range'      => [ 'px' => [ 'min' => 0, 'max' => 20 ] ],
				'default'    => [ 'size' => 6, 'unit' => 'px' ],
				'selectors'  => [
					'{{WRAPPER}} .wycan-stock-status__badge' => 'gap: {{SIZE}}{{UNIT}};',
				],
			] );

			$this->end_controls_section();
		}

		/* =====================================================================
		   RENDU
		   ===================================================================== */
		protected function render() {

			global $product;

			// Compatibilité Loop Builder : récupérer le produit courant
			if ( ! $product instanceof \WC_Product ) {
				$product = wc_get_product( get_the_ID() );
			}

			if ( ! $product instanceof \WC_Product ) {
				if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ) {
					echo '<div class="wycan-stock-status"><span class="wycan-stock-status__badge wycan-stock-status__badge--outofstock">Rupture de stock (aperçu)</span></div>';
				}
				return;
			}

			$settings     = $this->get_settings_for_display();
			$stock_status = $product->get_stock_status(); // instock | outofstock | onbackorder
			$stock_qty    = $product->get_stock_quantity();
			$low_amount   = absint( get_option( 'woocommerce_notify_low_stock_amount', 2 ) );

			// Détection stock faible
			$is_low_stock = ( 'instock' === $stock_status && $product->managing_stock() && null !== $stock_qty && $stock_qty <= $low_amount && $stock_qty > 0 );

			$resolved_status = $is_low_stock ? 'lowstock' : $stock_status;

			// Mode "rupture uniquement" : ne rien afficher si en stock / stock faible
			if ( 'out_of_stock_only' === $settings['display_mode'] && ! in_array( $resolved_status, [ 'outofstock', 'onbackorder' ], true ) ) {
				return;
			}

			// Mapping libellé / classe CSS / icône SVG
			$map = [
				'instock'     => [
					'label' => $settings['label_in_stock'] ?? 'En stock',
					'icon'  => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
				],
				'lowstock'    => [
					'label' => $settings['label_low_stock'] ?? 'Stock faible',
					'icon'  => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
				],
				'onbackorder' => [
					'label' => $settings['label_on_backorder'] ?? 'En réapprovisionnement',
					'icon'  => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
				],
				'outofstock'  => [
					'label' => $settings['label_out_of_stock'] ?? 'Rupture de stock',
					'icon'  => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
				],
			];

			if ( ! isset( $map[ $resolved_status ] ) ) {
				return;
			}

			$data  = $map[ $resolved_status ];
			$class = 'wycan-stock-status__badge wycan-stock-status__badge--' . esc_attr( $resolved_status );

			echo '<div class="wycan-stock-status">';
			echo '<span class="' . $class . '">';

			if ( 'yes' === $settings['show_icon'] ) {
				echo '<span class="wycan-stock-status__icon">' . $data['icon'] . '</span>';
			}

			echo '<span class="wycan-stock-status__label">' . esc_html( $data['label'] ) . '</span>';
			echo '</span>';
			echo '</div>';
		}

		protected function content_template() {
			// Rendu côté serveur uniquement (données WooCommerce dynamiques)
		}
	}

	$widgets_manager->register( new Wycan_Stock_Status_Widget() );
} );

/**
 * Styles de base (inline, pas de fichier CSS externe)
 */
add_action( 'wp_head', function () {
	if ( ! class_exists( 'WooCommerce' ) ) {
		return;
	}
	?>
	<style id="wycan-stock-status-css">
		.wycan-stock-status {
			display: flex;
			line-height: 1;
		}
		.wycan-stock-status__badge {
			display: inline-flex;
			align-items: center;
			gap: 6px;
			font-size: 13px;
			font-weight: 600;
			line-height: 1;
			white-space: nowrap;
			padding: 4px 10px;
			border-radius: 4px;
			transition: opacity .2s ease;
		}
		.wycan-stock-status__icon {
			display: inline-flex;
			flex-shrink: 0;
		}
		.wycan-stock-status__icon svg {
			width: 14px;
			height: 14px;
			display: block;
		}
		/* Couleurs par défaut (surchargées par les contrôles Elementor) */
		.wycan-stock-status__badge--instock {
			color: #16a34a;
			background-color: #dcfce7;
		}
		.wycan-stock-status__badge--lowstock {
			color: #ca8a04;
			background-color: #fef9c3;
		}
		.wycan-stock-status__badge--onbackorder {
			color: #2563eb;
			background-color: #dbeafe;
		}
		.wycan-stock-status__badge--outofstock {
			color: #dc2626;
			background-color: #fee2e2;
		}
	</style>
	<?php
} );

/**
 * Style éditeur Elementor (preview iframe)
 */
add_action( 'elementor/editor/after_enqueue_styles', function () {
	wp_add_inline_style( 'elementor-editor', '
		.wycan-stock-status { display: flex; line-height: 1; }
		.wycan-stock-status__badge { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; padding: 4px 10px; border-radius: 4px; }
		.wycan-stock-status__badge--outofstock { color: #dc2626; background-color: #fee2e2; }
	' );
} );

				
			
Want to leave a comment ?

Leave a Reply

Your email address will not be published. Required fields are marked *

Want to see more code snippets?

Stock Status Widget for Elementor

Elementor widget displaying the stock status of WooCommerce products. Designed for the Loop Builder (product cards in archives/categories).

Back to Top for Elementor

“Back to Top” button with progressive fill based on scroll. Automatically uses Elementor's main color.

WooCommerce Gallery Hover for Elementor Loop

Displays the first image in the gallery when hovering over the featured image in Elementor loops.

Free shipping method with WooCommerce coupon

Offer free shipping… but only for the shipping method you choose.

WooCommerce Product Buyers List

Added a tab in WooCommerce to display buyers of a specific product with date filter.

Additional Emails per Products for WooCommerce

Adds a custom tab in WooCommerce products to write an email specific to each product. This will be sent in a dedicated email.
Let's talk ?

A question about WordPress?
A web project to be outsourced to a freelancer?
I am your man!