medical-boxes.php

I’ll build this as a reusable inc/shortcodes/medical-boxes.php file for your RX Theme: multiple medical content box shortcodes, safe escaping/sanitization, schema-friendly output, accessibility, responsive CSS, icon support, and optional CTA/list layouts.

Below is an advanced complete file for:

rx-theme/inc/shortcodes/medical-boxes.php

It adds many useful medical shortcodes for your RX Theme: medical boxes, alerts, symptoms, causes, treatments, diagnostic boxes, step boxes, FAQ schema, pros/cons, do/don’t boxes, stats, timeline, emergency warning, and reusable styling.

<?php
/**
 * RX Theme - Medical Boxes Shortcodes
 *
 * File: inc/shortcodes/medical-boxes.php
 *
 * Features:
 * - Medical content boxes
 * - Alert / warning / emergency boxes
 * - Symptom, cause, treatment, diagnosis, prevention boxes
 * - FAQ accordion with optional schema
 * - Pros and cons boxes
 * - Do and don't boxes
 * - Step/timeline boxes
 * - Medical stat boxes
 * - CTA medical box
 * - Responsive inline CSS loaded only when shortcode is used
 * - Accessibility-ready markup
 * - Sanitized and escaped output
 *
 * @package RX_Theme
 */

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

if ( ! class_exists( 'RX_Medical_Boxes_Shortcodes' ) ) :

final class RX_Medical_Boxes_Shortcodes {

	/**
	 * CSS printed once.
	 *
	 * @var bool
	 */
	private static $styles_printed = false;

	/**
	 * Schema data queue.
	 *
	 * @var array
	 */
	private static $schema_queue = array();

	/**
	 * Initialize shortcodes and hooks.
	 */
	public static function init() {
		add_shortcode( 'rx_medical_box', array( __CLASS__, 'medical_box_shortcode' ) );
		add_shortcode( 'rx_box', array( __CLASS__, 'medical_box_shortcode' ) );
		add_shortcode( 'rx_alert', array( __CLASS__, 'alert_shortcode' ) );
		add_shortcode( 'rx_emergency', array( __CLASS__, 'emergency_shortcode' ) );
		add_shortcode( 'rx_medical_grid', array( __CLASS__, 'medical_grid_shortcode' ) );
		add_shortcode( 'rx_medical_card', array( __CLASS__, 'medical_card_shortcode' ) );
		add_shortcode( 'rx_symptoms', array( __CLASS__, 'list_box_shortcode' ) );
		add_shortcode( 'rx_causes', array( __CLASS__, 'list_box_shortcode' ) );
		add_shortcode( 'rx_treatments', array( __CLASS__, 'list_box_shortcode' ) );
		add_shortcode( 'rx_diagnosis', array( __CLASS__, 'list_box_shortcode' ) );
		add_shortcode( 'rx_prevention', array( __CLASS__, 'list_box_shortcode' ) );
		add_shortcode( 'rx_steps', array( __CLASS__, 'steps_shortcode' ) );
		add_shortcode( 'rx_timeline', array( __CLASS__, 'timeline_shortcode' ) );
		add_shortcode( 'rx_faq', array( __CLASS__, 'faq_shortcode' ) );
		add_shortcode( 'rx_faq_item', array( __CLASS__, 'faq_item_shortcode' ) );
		add_shortcode( 'rx_pros_cons', array( __CLASS__, 'pros_cons_shortcode' ) );
		add_shortcode( 'rx_do_dont', array( __CLASS__, 'do_dont_shortcode' ) );
		add_shortcode( 'rx_stat', array( __CLASS__, 'stat_shortcode' ) );
		add_shortcode( 'rx_cta_box', array( __CLASS__, 'cta_box_shortcode' ) );
		add_shortcode( 'rx_disclaimer', array( __CLASS__, 'disclaimer_shortcode' ) );

		add_action( 'wp_footer', array( __CLASS__, 'print_schema' ), 30 );
	}

	/**
	 * Print inline CSS only once.
	 */
	private static function maybe_print_styles() {
		if ( self::$styles_printed ) {
			return;
		}

		self::$styles_printed = true;

		add_action(
			'wp_footer',
			function() {
				?>
				<style id="rx-medical-boxes-css">
					:root {
						--rx-box-bg: #ffffff;
						--rx-box-text: #1f2937;
						--rx-box-muted: #6b7280;
						--rx-box-border: #e5e7eb;
						--rx-box-primary: #2563eb;
						--rx-box-success: #16a34a;
						--rx-box-warning: #d97706;
						--rx-box-danger: #dc2626;
						--rx-box-info: #0891b2;
						--rx-box-purple: #7c3aed;
						--rx-box-radius: 16px;
						--rx-box-shadow: 0 10px 25px rgba(15, 23, 42, 0.08);
					}

					.rx-medical-box,
					.rx-medical-card,
					.rx-alert-box,
					.rx-stat-box,
					.rx-cta-box,
					.rx-disclaimer-box {
						position: relative;
						box-sizing: border-box;
						margin: 1.4rem 0;
						padding: 1.25rem 1.35rem;
						border: 1px solid var(--rx-box-border);
						border-radius: var(--rx-box-radius);
						background: var(--rx-box-bg);
						color: var(--rx-box-text);
						box-shadow: var(--rx-box-shadow);
						overflow: hidden;
					}

					.rx-medical-box *,
					.rx-medical-card *,
					.rx-alert-box *,
					.rx-stat-box *,
					.rx-cta-box *,
					.rx-disclaimer-box * {
						box-sizing: border-box;
					}

					.rx-medical-box::before,
					.rx-alert-box::before,
					.rx-cta-box::before,
					.rx-disclaimer-box::before {
						content: "";
						position: absolute;
						inset: 0 auto 0 0;
						width: 6px;
						background: var(--rx-box-primary);
					}

					.rx-type-info::before { background: var(--rx-box-info); }
					.rx-type-success::before { background: var(--rx-box-success); }
					.rx-type-warning::before { background: var(--rx-box-warning); }
					.rx-type-danger::before,
					.rx-type-emergency::before { background: var(--rx-box-danger); }
					.rx-type-purple::before { background: var(--rx-box-purple); }

					.rx-box-header {
						display: flex;
						align-items: flex-start;
						gap: 0.85rem;
						margin-bottom: 0.75rem;
					}

					.rx-box-icon {
						display: inline-flex;
						align-items: center;
						justify-content: center;
						flex: 0 0 auto;
						width: 2.45rem;
						height: 2.45rem;
						border-radius: 999px;
						background: rgba(37, 99, 235, 0.10);
						color: var(--rx-box-primary);
						font-size: 1.25rem;
						line-height: 1;
					}

					.rx-type-info .rx-box-icon {
						background: rgba(8, 145, 178, 0.10);
						color: var(--rx-box-info);
					}

					.rx-type-success .rx-box-icon {
						background: rgba(22, 163, 74, 0.10);
						color: var(--rx-box-success);
					}

					.rx-type-warning .rx-box-icon {
						background: rgba(217, 119, 6, 0.12);
						color: var(--rx-box-warning);
					}

					.rx-type-danger .rx-box-icon,
					.rx-type-emergency .rx-box-icon {
						background: rgba(220, 38, 38, 0.10);
						color: var(--rx-box-danger);
					}

					.rx-type-purple .rx-box-icon {
						background: rgba(124, 58, 237, 0.10);
						color: var(--rx-box-purple);
					}

					.rx-box-title {
						margin: 0;
						font-size: clamp(1.05rem, 2vw, 1.35rem);
						line-height: 1.35;
						font-weight: 800;
						color: var(--rx-box-text);
					}

					.rx-box-subtitle {
						margin: 0.2rem 0 0;
						color: var(--rx-box-muted);
						font-size: 0.95rem;
						line-height: 1.55;
					}

					.rx-box-content {
						color: var(--rx-box-text);
						line-height: 1.75;
					}

					.rx-box-content p:last-child {
						margin-bottom: 0;
					}

					.rx-box-list {
						margin: 0.75rem 0 0;
						padding-left: 1.25rem;
					}

					.rx-box-list li {
						margin: 0.4rem 0;
						line-height: 1.65;
					}

					.rx-medical-grid {
						display: grid;
						grid-template-columns: repeat(var(--rx-grid-columns, 3), minmax(0, 1fr));
						gap: var(--rx-grid-gap, 1rem);
						margin: 1.5rem 0;
					}

					.rx-medical-card {
						height: 100%;
						margin: 0;
						box-shadow: 0 6px 18px rgba(15, 23, 42, 0.07);
					}

					.rx-card-link {
						display: inline-flex;
						align-items: center;
						gap: 0.35rem;
						margin-top: 0.85rem;
						font-weight: 700;
						text-decoration: none;
					}

					.rx-card-link:hover {
						text-decoration: underline;
					}

					.rx-alert-box {
						background: #fffdf7;
					}

					.rx-alert-box.rx-type-danger,
					.rx-alert-box.rx-type-emergency {
						background: #fff7f7;
					}

					.rx-alert-label {
						display: inline-block;
						margin-bottom: 0.4rem;
						padding: 0.2rem 0.55rem;
						border-radius: 999px;
						background: rgba(15, 23, 42, 0.08);
						font-size: 0.75rem;
						font-weight: 800;
						text-transform: uppercase;
						letter-spacing: 0.04em;
					}

					.rx-steps {
						counter-reset: rx-step;
						margin: 1.5rem 0;
						padding: 0;
						list-style: none;
					}

					.rx-step-item {
						counter-increment: rx-step;
						position: relative;
						margin: 0 0 1rem;
						padding: 1rem 1rem 1rem 4rem;
						border: 1px solid var(--rx-box-border);
						border-radius: var(--rx-box-radius);
						background: #fff;
						box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
					}

					.rx-step-item::before {
						content: counter(rx-step);
						position: absolute;
						left: 1rem;
						top: 1rem;
						display: inline-flex;
						align-items: center;
						justify-content: center;
						width: 2rem;
						height: 2rem;
						border-radius: 999px;
						background: var(--rx-box-primary);
						color: #fff;
						font-weight: 800;
					}

					.rx-step-title {
						margin: 0 0 0.35rem;
						font-weight: 800;
						font-size: 1.05rem;
					}

					.rx-faq-wrap {
						margin: 1.5rem 0;
					}

					.rx-faq-item {
						margin-bottom: 0.85rem;
						border: 1px solid var(--rx-box-border);
						border-radius: var(--rx-box-radius);
						background: #fff;
						box-shadow: 0 4px 16px rgba(15, 23, 42, 0.05);
						overflow: hidden;
					}

					.rx-faq-question {
						display: block;
						width: 100%;
						padding: 1rem 1.15rem;
						border: 0;
						background: #fff;
						color: var(--rx-box-text);
						font-weight: 800;
						text-align: left;
						cursor: pointer;
					}

					.rx-faq-answer {
						padding: 0 1.15rem 1.15rem;
						color: var(--rx-box-text);
						line-height: 1.75;
					}

					.rx-pros-cons,
					.rx-do-dont {
						display: grid;
						grid-template-columns: repeat(2, minmax(0, 1fr));
						gap: 1rem;
						margin: 1.5rem 0;
					}

					.rx-pros-box,
					.rx-cons-box,
					.rx-do-box,
					.rx-dont-box {
						padding: 1.15rem;
						border: 1px solid var(--rx-box-border);
						border-radius: var(--rx-box-radius);
						background: #fff;
						box-shadow: 0 6px 18px rgba(15, 23, 42, 0.06);
					}

					.rx-pros-box,
					.rx-do-box {
						border-left: 6px solid var(--rx-box-success);
					}

					.rx-cons-box,
					.rx-dont-box {
						border-left: 6px solid var(--rx-box-danger);
					}

					.rx-small-heading {
						margin: 0 0 0.75rem;
						font-size: 1.05rem;
						font-weight: 900;
					}

					.rx-stat-box {
						display: flex;
						align-items: center;
						gap: 1rem;
					}

					.rx-stat-number {
						font-size: clamp(2rem, 5vw, 3.5rem);
						font-weight: 900;
						line-height: 1;
						color: var(--rx-box-primary);
					}

					.rx-stat-label {
						margin: 0;
						font-weight: 800;
					}

					.rx-stat-desc {
						margin: 0.25rem 0 0;
						color: var(--rx-box-muted);
						line-height: 1.55;
					}

					.rx-cta-box {
						text-align: center;
						padding: 1.6rem;
						background: linear-gradient(135deg, rgba(37,99,235,.08), rgba(8,145,178,.08));
					}

					.rx-cta-button {
						display: inline-flex;
						align-items: center;
						justify-content: center;
						margin-top: 1rem;
						padding: 0.75rem 1.15rem;
						border-radius: 999px;
						background: var(--rx-box-primary);
						color: #fff !important;
						font-weight: 800;
						text-decoration: none;
					}

					.rx-cta-button:hover {
						filter: brightness(0.95);
						text-decoration: none;
					}

					.rx-disclaimer-box {
						font-size: 0.94rem;
						background: #f9fafb;
						color: #374151;
					}

					.rx-hidden {
						display: none !important;
					}

					@media (max-width: 900px) {
						.rx-medical-grid {
							grid-template-columns: repeat(2, minmax(0, 1fr));
						}
					}

					@media (max-width: 640px) {
						.rx-medical-grid,
						.rx-pros-cons,
						.rx-do-dont {
							grid-template-columns: 1fr;
						}

						.rx-medical-box,
						.rx-alert-box,
						.rx-stat-box,
						.rx-cta-box,
						.rx-disclaimer-box {
							padding: 1rem;
						}

						.rx-box-header {
							gap: 0.65rem;
						}

						.rx-box-icon {
							width: 2.15rem;
							height: 2.15rem;
						}

						.rx-step-item {
							padding-left: 3.5rem;
						}
					}
				</style>

				<script id="rx-medical-boxes-js">
					document.addEventListener('click', function(event) {
						var button = event.target.closest('.rx-faq-question');
						if (!button) return;

						var answerId = button.getAttribute('aria-controls');
						var answer = document.getElementById(answerId);

						if (!answer) return;

						var expanded = button.getAttribute('aria-expanded') === 'true';

						button.setAttribute('aria-expanded', expanded ? 'false' : 'true');
						answer.hidden = expanded;
					});
				</script>
				<?php
			},
			20
		);
	}

	/**
	 * Normalize type.
	 */
	private static function normalize_type( $type ) {
		$type    = sanitize_key( $type );
		$allowed = array(
			'default',
			'info',
			'success',
			'warning',
			'danger',
			'emergency',
			'purple',
		);

		return in_array( $type, $allowed, true ) ? $type : 'default';
	}

	/**
	 * Icon map.
	 */
	private static function get_icon( $icon, $type = 'default' ) {
		$icon = sanitize_text_field( $icon );

		if ( ! empty( $icon ) ) {
			return $icon;
		}

		$map = array(
			'default'   => '⚕️',
			'info'      => 'ℹ️',
			'success'   => '✅',
			'warning'   => '⚠️',
			'danger'    => '⛔',
			'emergency' => '🚨',
			'purple'    => '🧬',
			'symptoms'  => '🩺',
			'causes'    => '🔎',
			'treatments'=> '💊',
			'diagnosis' => '🧪',
			'prevention'=> '🛡️',
		);

		return isset( $map[ $type ] ) ? $map[ $type ] : $map['default'];
	}

	/**
	 * Safe content processor.
	 */
	private static function safe_content( $content ) {
		$content = do_shortcode( $content );
		$content = wpautop( $content );

		return wp_kses_post( $content );
	}

	/**
	 * Convert pipe-separated or line-separated content to list.
	 */
	private static function parse_items( $items ) {
		$items = trim( wp_strip_all_tags( $items ) );

		if ( empty( $items ) ) {
			return array();
		}

		$split = preg_split( '/\r\n|\r|\n|\|/', $items );

		$clean = array();

		foreach ( $split as $item ) {
			$item = trim( $item );

			if ( ! empty( $item ) ) {
				$clean[] = sanitize_text_field( $item );
			}
		}

		return $clean;
	}

	/**
	 * Render common box.
	 */
	private static function render_box( $args = array(), $content = '' ) {
		self::maybe_print_styles();

		$defaults = array(
			'title'       => '',
			'subtitle'    => '',
			'type'        => 'default',
			'icon'        => '',
			'class'       => '',
			'id'          => '',
			'role'        => '',
			'items'       => '',
			'list'        => 'ul',
			'show_icon'   => 'yes',
			'aria_label'  => '',
		);

		$args = wp_parse_args( $args, $defaults );

		$type       = self::normalize_type( $args['type'] );
		$title      = sanitize_text_field( $args['title'] );
		$subtitle   = sanitize_text_field( $args['subtitle'] );
		$icon       = self::get_icon( $args['icon'], $type );
		$class      = sanitize_html_class( $args['class'] );
		$id         = sanitize_html_class( $args['id'] );
		$role       = sanitize_key( $args['role'] );
		$list_type  = 'ol' === strtolower( $args['list'] ) ? 'ol' : 'ul';
		$show_icon  = 'no' !== strtolower( $args['show_icon'] );
		$aria_label = sanitize_text_field( $args['aria_label'] );

		$items = self::parse_items( $args['items'] );

		$attrs = array();

		if ( ! empty( $id ) ) {
			$attrs[] = 'id="' . esc_attr( $id ) . '"';
		}

		if ( ! empty( $role ) ) {
			$attrs[] = 'role="' . esc_attr( $role ) . '"';
		}

		if ( ! empty( $aria_label ) ) {
			$attrs[] = 'aria-label="' . esc_attr( $aria_label ) . '"';
		}

		ob_start();
		?>
		<div class="rx-medical-box rx-type-<?php echo esc_attr( $type ); ?> <?php echo esc_attr( $class ); ?>" <?php echo implode( ' ', $attrs ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
			<?php if ( $title || $subtitle || $show_icon ) : ?>
				<div class="rx-box-header">
					<?php if ( $show_icon ) : ?>
						<span class="rx-box-icon" aria-hidden="true"><?php echo esc_html( $icon ); ?></span>
					<?php endif; ?>

					<div>
						<?php if ( $title ) : ?>
							<h3 class="rx-box-title"><?php echo esc_html( $title ); ?></h3>
						<?php endif; ?>

						<?php if ( $subtitle ) : ?>
							<p class="rx-box-subtitle"><?php echo esc_html( $subtitle ); ?></p>
						<?php endif; ?>
					</div>
				</div>
			<?php endif; ?>

			<div class="rx-box-content">
				<?php echo self::safe_content( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>

				<?php if ( ! empty( $items ) ) : ?>
					<<?php echo esc_attr( $list_type ); ?> class="rx-box-list">
						<?php foreach ( $items as $item ) : ?>
							<li><?php echo esc_html( $item ); ?></li>
						<?php endforeach; ?>
					</<?php echo esc_attr( $list_type ); ?>>
				<?php endif; ?>
			</div>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_medical_box]
	 */
	public static function medical_box_shortcode( $atts, $content = '' ) {
		$atts = shortcode_atts(
			array(
				'title'      => '',
				'subtitle'   => '',
				'type'       => 'default',
				'icon'       => '',
				'class'      => '',
				'id'         => '',
				'role'       => '',
				'items'      => '',
				'list'       => 'ul',
				'show_icon'  => 'yes',
				'aria_label' => '',
			),
			$atts,
			'rx_medical_box'
		);

		return self::render_box( $atts, $content );
	}

	/**
	 * [rx_alert]
	 */
	public static function alert_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'title'    => 'Medical Alert',
				'label'    => 'Important',
				'type'     => 'warning',
				'icon'     => '',
				'role'     => 'note',
				'class'    => '',
			),
			$atts,
			'rx_alert'
		);

		$type  = self::normalize_type( $atts['type'] );
		$icon  = self::get_icon( $atts['icon'], $type );
		$title = sanitize_text_field( $atts['title'] );
		$label = sanitize_text_field( $atts['label'] );
		$role  = sanitize_key( $atts['role'] );
		$class = sanitize_html_class( $atts['class'] );

		ob_start();
		?>
		<div class="rx-alert-box rx-type-<?php echo esc_attr( $type ); ?> <?php echo esc_attr( $class ); ?>" role="<?php echo esc_attr( $role ); ?>">
			<span class="rx-alert-label"><?php echo esc_html( $label ); ?></span>
			<div class="rx-box-header">
				<span class="rx-box-icon" aria-hidden="true"><?php echo esc_html( $icon ); ?></span>
				<h3 class="rx-box-title"><?php echo esc_html( $title ); ?></h3>
			</div>
			<div class="rx-box-content">
				<?php echo self::safe_content( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</div>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_emergency]
	 */
	public static function emergency_shortcode( $atts, $content = '' ) {
		$atts = shortcode_atts(
			array(
				'title' => 'Seek emergency medical help',
				'label' => 'Emergency warning',
				'icon'  => '🚨',
			),
			$atts,
			'rx_emergency'
		);

		return self::alert_shortcode(
			array(
				'title' => $atts['title'],
				'label' => $atts['label'],
				'icon'  => $atts['icon'],
				'type'  => 'emergency',
				'role'  => 'alert',
				'class' => 'rx-emergency-box',
			),
			$content
		);
	}

	/**
	 * [rx_medical_grid]
	 */
	public static function medical_grid_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'columns' => 3,
				'gap'     => '1rem',
				'class'   => '',
			),
			$atts,
			'rx_medical_grid'
		);

		$columns = absint( $atts['columns'] );

		if ( $columns < 1 ) {
			$columns = 1;
		}

		if ( $columns > 4 ) {
			$columns = 4;
		}

		$gap   = sanitize_text_field( $atts['gap'] );
		$class = sanitize_html_class( $atts['class'] );

		ob_start();
		?>
		<div class="rx-medical-grid <?php echo esc_attr( $class ); ?>" style="--rx-grid-columns: <?php echo esc_attr( $columns ); ?>; --rx-grid-gap: <?php echo esc_attr( $gap ); ?>;">
			<?php echo do_shortcode( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_medical_card]
	 */
	public static function medical_card_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'title'    => '',
				'subtitle' => '',
				'icon'     => '⚕️',
				'url'      => '',
				'linktext' => 'Read more',
				'type'     => 'default',
				'class'    => '',
			),
			$atts,
			'rx_medical_card'
		);

		$type     = self::normalize_type( $atts['type'] );
		$title    = sanitize_text_field( $atts['title'] );
		$subtitle = sanitize_text_field( $atts['subtitle'] );
		$icon     = self::get_icon( $atts['icon'], $type );
		$url      = esc_url( $atts['url'] );
		$linktext = sanitize_text_field( $atts['linktext'] );
		$class    = sanitize_html_class( $atts['class'] );

		ob_start();
		?>
		<article class="rx-medical-card rx-type-<?php echo esc_attr( $type ); ?> <?php echo esc_attr( $class ); ?>">
			<div class="rx-box-header">
				<span class="rx-box-icon" aria-hidden="true"><?php echo esc_html( $icon ); ?></span>
				<div>
					<?php if ( $title ) : ?>
						<h3 class="rx-box-title"><?php echo esc_html( $title ); ?></h3>
					<?php endif; ?>

					<?php if ( $subtitle ) : ?>
						<p class="rx-box-subtitle"><?php echo esc_html( $subtitle ); ?></p>
					<?php endif; ?>
				</div>
			</div>

			<div class="rx-box-content">
				<?php echo self::safe_content( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</div>

			<?php if ( $url ) : ?>
				<a class="rx-card-link" href="<?php echo esc_url( $url ); ?>">
					<?php echo esc_html( $linktext ); ?> <span aria-hidden="true"></span>
				</a>
			<?php endif; ?>
		</article>
		<?php

		return ob_get_clean();
	}

	/**
	 * List box shortcodes.
	 *
	 * Used by:
	 * [rx_symptoms]
	 * [rx_causes]
	 * [rx_treatments]
	 * [rx_diagnosis]
	 * [rx_prevention]
	 */
	public static function list_box_shortcode( $atts, $content = '', $tag = '' ) {
		$map = array(
			'rx_symptoms'   => array( 'title' => 'Common Symptoms', 'icon' => '🩺', 'type' => 'info' ),
			'rx_causes'     => array( 'title' => 'Possible Causes', 'icon' => '🔎', 'type' => 'purple' ),
			'rx_treatments' => array( 'title' => 'Treatment Options', 'icon' => '💊', 'type' => 'success' ),
			'rx_diagnosis'  => array( 'title' => 'Diagnostic Tests', 'icon' => '🧪', 'type' => 'info' ),
			'rx_prevention' => array( 'title' => 'Prevention Tips', 'icon' => '🛡️', 'type' => 'success' ),
		);

		$defaults = isset( $map[ $tag ] ) ? $map[ $tag ] : $map['rx_symptoms'];

		$atts = shortcode_atts(
			array(
				'title'    => $defaults['title'],
				'subtitle' => '',
				'items'    => '',
				'list'     => 'ul',
				'type'     => $defaults['type'],
				'icon'     => $defaults['icon'],
				'class'    => '',
			),
			$atts,
			$tag
		);

		if ( empty( $atts['items'] ) && ! empty( $content ) ) {
			$atts['items'] = $content;
			$content       = '';
		}

		return self::render_box( $atts, $content );
	}

	/**
	 * [rx_steps]
	 */
	public static function steps_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'items' => '',
				'class' => '',
			),
			$atts,
			'rx_steps'
		);

		$items = self::parse_items( ! empty( $atts['items'] ) ? $atts['items'] : $content );
		$class = sanitize_html_class( $atts['class'] );

		if ( empty( $items ) ) {
			return '';
		}

		ob_start();
		?>
		<ol class="rx-steps <?php echo esc_attr( $class ); ?>">
			<?php foreach ( $items as $item ) : ?>
				<li class="rx-step-item">
					<div class="rx-step-title"><?php echo esc_html( $item ); ?></div>
				</li>
			<?php endforeach; ?>
		</ol>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_timeline]
	 */
	public static function timeline_shortcode( $atts, $content = '' ) {
		$atts = shortcode_atts(
			array(
				'items' => '',
				'class' => '',
			),
			$atts,
			'rx_timeline'
		);

		return self::steps_shortcode( $atts, $content );
	}

	/**
	 * [rx_faq]
	 *
	 * Usage:
	 * [rx_faq schema="yes"]
	 * [rx_faq_item question="What is fever?"]Fever is high body temperature.[/rx_faq_item]
	 * [/rx_faq]
	 */
	public static function faq_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'title'  => '',
				'schema' => 'yes',
				'class'  => '',
			),
			$atts,
			'rx_faq'
		);

		$class = sanitize_html_class( $atts['class'] );
		$title = sanitize_text_field( $atts['title'] );

		ob_start();
		?>
		<section class="rx-faq-wrap <?php echo esc_attr( $class ); ?>">
			<?php if ( $title ) : ?>
				<h2><?php echo esc_html( $title ); ?></h2>
			<?php endif; ?>

			<?php echo do_shortcode( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
		</section>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_faq_item]
	 */
	public static function faq_item_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'question' => '',
				'open'     => 'no',
				'schema'   => 'yes',
			),
			$atts,
			'rx_faq_item'
		);

		$question = sanitize_text_field( $atts['question'] );
		$answer   = wp_strip_all_tags( do_shortcode( $content ) );
		$is_open  = 'yes' === strtolower( $atts['open'] );
		$id       = 'rx-faq-' . wp_generate_uuid4();

		if ( empty( $question ) || empty( $answer ) ) {
			return '';
		}

		if ( 'yes' === strtolower( $atts['schema'] ) ) {
			self::$schema_queue[] = array(
				'@type'          => 'Question',
				'name'           => $question,
				'acceptedAnswer' => array(
					'@type' => 'Answer',
					'text'  => $answer,
				),
			);
		}

		ob_start();
		?>
		<div class="rx-faq-item">
			<button
				type="button"
				class="rx-faq-question"
				aria-expanded="<?php echo $is_open ? 'true' : 'false'; ?>"
				aria-controls="<?php echo esc_attr( $id ); ?>"
			>
				<?php echo esc_html( $question ); ?>
			</button>

			<div
				id="<?php echo esc_attr( $id ); ?>"
				class="rx-faq-answer"
				<?php echo $is_open ? '' : 'hidden'; ?>
			>
				<?php echo self::safe_content( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</div>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_pros_cons]
	 */
	public static function pros_cons_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'pros_title' => 'Benefits',
				'cons_title' => 'Limitations',
				'pros'       => '',
				'cons'       => '',
			),
			$atts,
			'rx_pros_cons'
		);

		$pros = self::parse_items( $atts['pros'] );
		$cons = self::parse_items( $atts['cons'] );

		ob_start();
		?>
		<div class="rx-pros-cons">
			<div class="rx-pros-box">
				<h3 class="rx-small-heading"><?php echo esc_html( $atts['pros_title'] ); ?></h3>
				<ul class="rx-box-list">
					<?php foreach ( $pros as $item ) : ?>
						<li><?php echo esc_html( $item ); ?></li>
					<?php endforeach; ?>
				</ul>
			</div>

			<div class="rx-cons-box">
				<h3 class="rx-small-heading">⚠️ <?php echo esc_html( $atts['cons_title'] ); ?></h3>
				<ul class="rx-box-list">
					<?php foreach ( $cons as $item ) : ?>
						<li><?php echo esc_html( $item ); ?></li>
					<?php endforeach; ?>
				</ul>
			</div>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_do_dont]
	 */
	public static function do_dont_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'do_title'   => 'What to Do',
				'dont_title' => 'What to Avoid',
				'do'         => '',
				'dont'       => '',
			),
			$atts,
			'rx_do_dont'
		);

		$do_items   = self::parse_items( $atts['do'] );
		$dont_items = self::parse_items( $atts['dont'] );

		ob_start();
		?>
		<div class="rx-do-dont">
			<div class="rx-do-box">
				<h3 class="rx-small-heading"><?php echo esc_html( $atts['do_title'] ); ?></h3>
				<ul class="rx-box-list">
					<?php foreach ( $do_items as $item ) : ?>
						<li><?php echo esc_html( $item ); ?></li>
					<?php endforeach; ?>
				</ul>
			</div>

			<div class="rx-dont-box">
				<h3 class="rx-small-heading"><?php echo esc_html( $atts['dont_title'] ); ?></h3>
				<ul class="rx-box-list">
					<?php foreach ( $dont_items as $item ) : ?>
						<li><?php echo esc_html( $item ); ?></li>
					<?php endforeach; ?>
				</ul>
			</div>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_stat]
	 */
	public static function stat_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'number' => '',
				'label'  => '',
				'desc'   => '',
				'type'   => 'default',
				'class'  => '',
			),
			$atts,
			'rx_stat'
		);

		$type   = self::normalize_type( $atts['type'] );
		$class  = sanitize_html_class( $atts['class'] );
		$number = sanitize_text_field( $atts['number'] );
		$label  = sanitize_text_field( $atts['label'] );
		$desc   = sanitize_text_field( $atts['desc'] );

		if ( empty( $desc ) && ! empty( $content ) ) {
			$desc = wp_strip_all_tags( $content );
		}

		ob_start();
		?>
		<div class="rx-stat-box rx-type-<?php echo esc_attr( $type ); ?> <?php echo esc_attr( $class ); ?>">
			<div class="rx-stat-number"><?php echo esc_html( $number ); ?></div>
			<div>
				<?php if ( $label ) : ?>
					<p class="rx-stat-label"><?php echo esc_html( $label ); ?></p>
				<?php endif; ?>

				<?php if ( $desc ) : ?>
					<p class="rx-stat-desc"><?php echo esc_html( $desc ); ?></p>
				<?php endif; ?>
			</div>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_cta_box]
	 */
	public static function cta_box_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'title'       => '',
				'button_text' => '',
				'button_url'  => '',
				'type'        => 'info',
			),
			$atts,
			'rx_cta_box'
		);

		$title      = sanitize_text_field( $atts['title'] );
		$button     = sanitize_text_field( $atts['button_text'] );
		$url        = esc_url( $atts['button_url'] );
		$type       = self::normalize_type( $atts['type'] );

		ob_start();
		?>
		<div class="rx-cta-box rx-type-<?php echo esc_attr( $type ); ?>">
			<?php if ( $title ) : ?>
				<h3 class="rx-box-title"><?php echo esc_html( $title ); ?></h3>
			<?php endif; ?>

			<div class="rx-box-content">
				<?php echo self::safe_content( $content ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
			</div>

			<?php if ( $button && $url ) : ?>
				<a class="rx-cta-button" href="<?php echo esc_url( $url ); ?>">
					<?php echo esc_html( $button ); ?>
				</a>
			<?php endif; ?>
		</div>
		<?php

		return ob_get_clean();
	}

	/**
	 * [rx_disclaimer]
	 */
	public static function disclaimer_shortcode( $atts, $content = '' ) {
		self::maybe_print_styles();

		$atts = shortcode_atts(
			array(
				'title' => 'Medical Disclaimer',
				'icon'  => '⚕️',
			),
			$atts,
			'rx_disclaimer'
		);

		if ( empty( trim( $content ) ) ) {
			$content = 'This information is for educational purposes only and should not replace professional medical advice, diagnosis, or treatment. Always consult a qualified healthcare provider for personal medical concerns.';
		}

		return self::render_box(
			array(
				'title' => $atts['title'],
				'icon'  => $atts['icon'],
				'type'  => 'info',
				'role'  => 'note',
				'class' => 'rx-disclaimer-box',
			),
			$content
		);
	}

	/**
	 * Print FAQ schema.
	 */
	public static function print_schema() {
		if ( empty( self::$schema_queue ) || ! is_singular() ) {
			return;
		}

		$schema = array(
			'@context'   => 'https://schema.org',
			'@type'      => 'FAQPage',
			'mainEntity' => self::$schema_queue,
		);

		echo '<script type="application/ld+json" class="rx-faq-schema">';
		echo wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		echo '</script>';
	}
}

endif;

RX_Medical_Boxes_Shortcodes::init();

Now add this file in your theme loading system.

In functions.php, add:

/**
 * RX Theme shortcodes.
 */
$rx_medical_boxes_file = get_template_directory() . '/inc/shortcodes/medical-boxes.php';

if ( file_exists( $rx_medical_boxes_file ) ) {
	require_once $rx_medical_boxes_file;
}

Example use in WordPress post

[rx_medical_box title="What is Hypertension?" subtitle="Simple medical explanation" type="info"]
Hypertension means blood pressure stays higher than normal for a long time.
[/rx_medical_box]
[rx_symptoms items="Headache|Dizziness|Chest discomfort|Shortness of breath"]
[rx_alert title="Important Warning" type="warning"]
Do not stop prescribed medicine without talking to your doctor.
[/rx_alert]
[rx_emergency]
Call emergency help if there is severe chest pain, fainting, severe breathing difficulty, or stroke-like symptoms.
[/rx_emergency]
[rx_do_dont 
do="Drink enough water|Take medicine as prescribed|Follow up with your doctor"
dont="Do not ignore severe symptoms|Do not self-medicate|Do not delay emergency care"]
[rx_faq title="Frequently Asked Questions"]
[rx_faq_item question="Is this article medical advice?"]
No. It is educational information only.
[/rx_faq_item]

[rx_faq_item question="When should I see a doctor?"]
See a doctor if symptoms are severe, persistent, worsening, or unusual.
[/rx_faq_item]
[/rx_faq]

This file is safe, reusable, SEO-friendly, and very useful for your medical article website.

Leave a Reply

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