DEV3 Technologies home

Monthly / yearly pricing toggle shortcode

A pricing table shortcode with an accessible monthly and yearly switch driven by data attributes and a few lines of script.

WordPressPricingpricingshortcodetoggleswitch

Live preview

Code

<?php
/**
 * [acme_pricing] shortcode.
 *
 * @package Acme_Theme
 */

defined( 'ABSPATH' ) || exit;

function acme_pricing_shortcode(): string {
	$plans = array(
		array( 'name' => 'Starter', 'month' => 12, 'year' => 120, 'features' => array( '1 project', 'Email support' ) ),
		array( 'name' => 'Pro', 'month' => 29, 'year' => 290, 'features' => array( '10 projects', 'Priority support', 'Team seats' ), 'featured' => true ),
	);

	wp_enqueue_script( 'acme-pricing', get_template_directory_uri() . '/assets/pricing.js', array(), '1.0.0', array( 'in_footer' => true, 'strategy' => 'defer' ) );

	$out  = '<div class="acme-pricing" data-period="month">';
	$out .= '<div class="acme-pricing__switch" role="group" aria-label="' . esc_attr__( 'Billing period', 'acme-theme' ) . '">';
	$out .= '<button type="button" data-set="month" aria-pressed="true">' . esc_html__( 'Monthly', 'acme-theme' ) . '</button>';
	$out .= '<button type="button" data-set="year" aria-pressed="false">' . esc_html__( 'Yearly', 'acme-theme' ) . '</button></div>';
	$out .= '<div class="acme-pricing__grid">';

	foreach ( $plans as $plan ) {
		$out .= '<article class="acme-plan' . ( ! empty( $plan['featured'] ) ? ' is-featured' : '' ) . '">';
		$out .= '<h3>' . esc_html( $plan['name'] ) . '</h3>';
		$out .= sprintf(
			'<p class="acme-plan__price"><span data-price-month>$%1$d</span><span data-price-year hidden>$%2$d</span> <small data-unit>%3$s</small></p>',
			(int) $plan['month'],
			(int) $plan['year'],
			esc_html__( 'per month', 'acme-theme' )
		);
		$out .= '<ul>';
		foreach ( $plan['features'] as $feature ) {
			$out .= '<li>' . esc_html( $feature ) . '</li>';
		}
		$out .= '</ul></article>';
	}

	return $out . '</div></div>';
}
add_shortcode( 'acme_pricing', 'acme_pricing_shortcode' );

How it works

What it solves

SaaS price tables usually offer a cheaper yearly option, and a switch keeps the page compact. The shortcode renders every price into the HTML, so search engines and non-JavaScript readers see real numbers, then a small script flips which one is visible.

How it works

Plans live in a PHP array, so changing prices is a code or option edit rather than page markup. Both the monthly and yearly figures are printed, with the yearly ones hidden. Two buttons form a labelled group and use aria-pressed to expose their state. On click the script sets the period on the wrapper, hides and reveals the matching spans, and swaps the unit text.

Only integers and escaped strings reach the output.

Adapting it

Load the plans from an options page or custom post type instead of the literal array. Add a savings badge by computing 1 - year / (month * 12). Style .is-featured and use [data-period] in CSS if you prefer to show and hide with selectors rather than the hidden attribute.

Pitfalls

  • Translate unit labels in PHP and pass them as data attributes if the script must show them.
  • Make buttons real buttons, not links, so Space and Enter work.
  • Do not rely on colour alone to show the active period.
  • Currency formatting differs by locale; use number_format_i18n() for large figures.

How to use it

Save the PHP as inc/pricing.php, require it, and put the script in assets/.

Add [acme_pricing] to any page.

Change the $plans array or feed it from your own data source.

Licence: GPL-2.0-or-later. This snippet comes from WordPress code and is GPL-2.0-or-later.

Related snippets