Repository: Automattic/wp-calypso
Pull request #103418: Experiment streamlining the plans grid + checkout: Update the plan interval dropdown
Author: login: claudiucelfilip
Created: 2025-05-14T11:15:01Z | Merged: 2025-05-16T06:19:46Z

<!--
Link a related Github/Linear issue to this PR. If the PR does not immediately resolve the issue,
for example, it requires a separate deployment to production, avoid
using the "fixes" keyword. Consider using "part of" instead.
-->

Related to p58i-kUT-p2

## Proposed Changes

* adapts the plan interval dropdown to match the new design
<img width="480" alt="Screenshot 2025-05-14 at 14 13 08" src="https://github.com/user-attachments/assets/d6eb6e8b-b8be-45a5-8e7b-22f7a9e9a6c4" />

## Why are these changes being made?
<!--
It's easy to see what a PR does but much harder to find out why it was made,
particularly when researching old changes in history. Record an explanation of
the motivation behind this change and how it will help.
-->

We're experimenting with streamlining the plans and checkout user experience.

## Testing Instructions

<!--
Add as many details as possible to help others reproduce the issue and test the fix.
"Before / After" screenshots can also be very helpful when the change is visual.
-->
* To test this feature, you'll need to be assigned to eithe `plans_1y_checkout_dropdown` or`plans_3y_checkout_dropdown` of the experiment variations of calypso_streamlined_plans_checkout
* Add a WPCOM plan, a domain and a Titan email subscription to the cart
* The updated plan interval dropdown component should be updated for the WPCOM plan

|Before|After|
|------|-----|
|<img width="720" alt="Screenshot 2025-05-15 at 10 21 04" src="https://github.com/user-attachments/assets/b035af74-ff76-4622-ac93-2a39974a27d4" />|<img width="715" alt="Screenshot 2025-05-15 at 10 24 21" src="https://github.com/user-attachments/assets/0d8d0caf-fdf0-4600-af6d-379b40b9f7c1" />|
## Pre-merge Checklist

<!--
Complete applicable items on this checklist **before** merging into trunk. Inapplicable items can be left unchecked.

Both the PR author and reviewer are responsible for ensuring the checklist is completed.
-->

- [ ] Has the general commit checklist been followed? (PCYsg-hS-p2)
- [ ] [Have you written new tests](https://wpcalypso.wordpress.com/devdocs/docs/testing/index.md) for your changes?
- [ ] Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
- [ ] Have you checked for TypeScript, React or other console errors?
- [ ] Have you used memoizing on expensive computations? More info in [Memoizing with create-selector](https://github.com/Automattic/wp-calypso/blob/trunk/packages/state-utils/src/create-selector/README.md) and [Using memoizing selectors](https://react-redux.js.org/api/hooks#using-memoizing-selectors) and [Our Approach to Data](https://github.com/Automattic/wp-calypso/blob/trunk/docs/our-approach-to-data.md)
- [ ] Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    - [ ] For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages. 
- [ ] For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?



Diff:
diff --git a/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/index.tsx b/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/index.tsx
index 6dd7755ae634..7da772fda86b 100644
--- a/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/index.tsx
+++ b/client/my-sites/checkout/src/components/checkout-sidebar-plan-upsell/index.tsx
@@ -18,7 +18,7 @@ import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { useGetProductVariants } from '../../hooks/product-variants';
 import {
 	getItemVariantCompareToPrice,
-	getItemVariantDiscountPercentage,
+	getItemVariantDiscount,
 } from '../item-variation-picker/util';
 import type { WPCOMProductVariant } from '../item-variation-picker';
 import './style.scss';
@@ -187,7 +187,7 @@ export function CheckoutSidebarPlanUpsell() {
 		upsellVariant,
 		currentVariant
 	);
-	const percentSavings = getItemVariantDiscountPercentage( upsellVariant, currentVariant );
+	const percentSavings = getItemVariantDiscount( upsellVariant, currentVariant );
 	if ( percentSavings === 0 ) {
 		debug( 'percent savings is too low', percentSavings );
 		return null;
diff --git a/client/my-sites/checkout/src/components/item-variation-picker/item-variation-dropdown.tsx b/client/my-sites/checkout/src/components/item-variation-picker/item-variation-dropdown.tsx
index 2bd4e000fbed..9a9db3b1a816 100644
--- a/client/my-sites/checkout/src/components/item-variation-picker/item-variation-dropdown.tsx
+++ b/client/my-sites/checkout/src/components/item-variation-picker/item-variation-dropdown.tsx
@@ -2,13 +2,18 @@ import {
 	isJetpackPlan,
 	isJetpackProduct,
 	isMultiYearDomainProduct,
+	isWpComPlan,
 } from '@automattic/calypso-products';
 import { Gridicon } from '@automattic/components';
 import { useTranslate } from 'i18n-calypso';
-import { FunctionComponent, useCallback, useEffect, useState } from 'react';
+import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 'react';
 import isJetpackCheckout from 'calypso/lib/jetpack/is-jetpack-checkout';
+import {
+	useStreamlinedPriceExperiment,
+	isStreamlinedPriceDropdownTreatment,
+} from 'calypso/my-sites/plans-features-main/hooks/use-streamlined-price-experiment';
 import { JetpackItemVariantDropDownPrice } from './jetpack-variant-dropdown-price';
-import { CurrentOption, Dropdown, OptionList, Option } from './styles';
+import { CurrentOption, Dropdown, OptionList, Option, WPCheckoutCheckIcon } from './styles';
 import { ItemVariantDropDownPrice } from './variant-dropdown-price';
 import type { ItemVariationPickerProps, WPCOMProductVariant } from './types';
 import type { ResponseCartProduct } from '@automattic/shopping-cart';
@@ -27,15 +32,32 @@ export const ItemVariationDropDown: FunctionComponent< ItemVariationPickerProps
 } ) => {
 	const translate = useTranslate();
 	const [ highlightedVariantIndex, setHighlightedVariantIndex ] = useState< number | null >( null );
+	const [ , streamlinedPriceExperimentAssignment ] = useStreamlinedPriceExperiment();
+	const isStreamlinedPrice =
+		isStreamlinedPriceDropdownTreatment( streamlinedPriceExperimentAssignment ) &&
+		isWpComPlan( selectedItem.product_slug );
+
+	const [ optimisticSelectedItem, setOptimisticSelectedItem ] = useState< string | null >( null );
+
+	useEffect( () => {
+		if ( isStreamlinedPrice ) {
+			setOptimisticSelectedItem( selectedItem.product_slug );
+		}
+	}, [ selectedItem.product_slug, isStreamlinedPrice ] );
 
 	// Multi-year domain products must be compared by volume because they have the same product id.
-	const selectedVariantIndexRaw = variants.findIndex( ( variant ) =>
-		isMultiYearDomainProduct( selectedItem )
-			? selectedItem.volume === variant.volume
-			: selectedItem.product_id === variant.productId
-	);
-	// findIndex returns -1 if it fails and we want null.
-	const selectedVariantIndex = selectedVariantIndexRaw > -1 ? selectedVariantIndexRaw : null;
+	const selectedVariantIndex = useMemo( () => {
+		const rawIndex = variants.findIndex( ( variant ) => {
+			if ( isStreamlinedPrice && optimisticSelectedItem ) {
+				return variant.productSlug === optimisticSelectedItem;
+			}
+			// If not optimistic, or optimisticSelectedItem is not set, use the original logic
+			return isMultiYearDomainProduct( selectedItem )
+				? selectedItem.volume === variant.volume && variant.productId === selectedItem.product_id
+				: selectedItem.product_id === variant.productId;
+		} );
+		return rawIndex > -1 ? rawIndex : null;
+	}, [ variants, isStreamlinedPrice, optimisticSelectedItem, selectedItem ] );
 
 	// reset the dropdown highlight when the selected product changes
 	useEffect( () => {
@@ -45,10 +67,13 @@ export const ItemVariationDropDown: FunctionComponent< ItemVariationPickerProps
 	// wrapper around onChangeItemVariant to close up dropdown on change
 	const handleChange = useCallback(
 		( uuid: string, productSlug: string, productId: number, volume?: number ) => {
+			if ( isStreamlinedPrice ) {
+				setOptimisticSelectedItem( productSlug );
+			}
 			onChangeItemVariant( uuid, productSlug, productId, volume );
 			toggle( null );
 		},
-		[ onChangeItemVariant, toggle ]
+		[ onChangeItemVariant, toggle, isStreamlinedPrice ]
 	);
 
 	const selectNextVariant = useCallback( () => {
@@ -121,13 +146,22 @@ export const ItemVariationDropDown: FunctionComponent< ItemVariationPickerProps
 		return null;
 	}
 
+	let compareTo = undefined;
+	if ( isStreamlinedPrice ) {
+		compareTo = variants.find( ( variant ) => variant.termIntervalInMonths === 1 );
+	}
 	const ItemVariantDropDownPriceWrapper: FunctionComponent< { variant: WPCOMProductVariant } > = (
 		props
 	) =>
 		isJetpack( props.variant ) ? (
 			<JetpackItemVariantDropDownPrice { ...props } allVariants={ variants } />
 		) : (
-			<ItemVariantDropDownPrice { ...props } product={ selectedItem } />
+			<ItemVariantDropDownPrice
+				{ ...props }
+				product={ selectedItem }
+				isStreamlinedPrice={ isStreamlinedPrice }
+				compareTo={ compareTo }
+			/>
 		);
 
 	return (
@@ -143,6 +177,7 @@ export const ItemVariationDropDown: FunctionComponent< ItemVariationPickerProps
 				onClick={ () => toggle( id ) }
 				open={ isOpen }
 				role="button"
+				detached={ isStreamlinedPrice }
 			>
 				{ selectedVariantIndex !== null ? (
 					<ItemVariantDropDownPriceWrapper variant={ variants[ selectedVariantIndex ] } />
@@ -157,6 +192,7 @@ export const ItemVariationDropDown: FunctionComponent< ItemVariationPickerProps
 					highlightedVariantIndex={ highlightedVariantIndex }
 					selectedItem={ selectedItem }
 					handleChange={ handleChange }
+					isStreamlinedPrice={ isStreamlinedPrice }
 				/>
 			) }
 		</Dropdown>
@@ -168,15 +204,19 @@ function ItemVariantOptionList( {
 	highlightedVariantIndex,
 	selectedItem,
 	handleChange,
+	isStreamlinedPrice,
 }: {
 	variants: WPCOMProductVariant[];
 	highlightedVariantIndex: number | null;
 	selectedItem: ResponseCartProduct;
 	handleChange: ( uuid: string, productSlug: string, productId: number, volume?: number ) => void;
+	isStreamlinedPrice: boolean;
 } ) {
-	const compareTo = variants.find( ( variant ) => variant.productId === selectedItem.product_id );
+	const compareTo = isStreamlinedPrice
+		? variants.find( ( variant ) => variant.termIntervalInMonths === 1 )
+		: variants.find( ( variant ) => variant.productId === selectedItem.product_id );
 	return (
-		<OptionList role="listbox" tabIndex={ -1 }>
+		<OptionList role="listbox" tabIndex={ -1 } detached={ isStreamlinedPrice }>
 			{ variants.map( ( variant, index ) => (
 				<ItemVariantOption
 					key={ variant.productSlug + variant.variantLabel.noun }
@@ -193,6 +233,7 @@ function ItemVariantOptionList( {
 					variant={ variant }
 					allVariants={ variants }
 					selectedItem={ selectedItem }
+					isStreamlinedPrice={ isStreamlinedPrice }
 				/>
 			) ) }
 		</OptionList>
@@ -206,6 +247,7 @@ function ItemVariantOption( {
 	variant,
 	allVariants,
 	selectedItem,
+	isStreamlinedPrice,
 }: {
 	isSelected: boolean;
 	onSelect: () => void;
@@ -213,6 +255,7 @@ function ItemVariantOption( {
 	variant: WPCOMProductVariant;
 	allVariants: WPCOMProductVariant[];
 	selectedItem: ResponseCartProduct;
+	isStreamlinedPrice: boolean;
 } ) {
 	const { variantLabel, productId, productSlug } = variant;
 	return (
@@ -224,6 +267,7 @@ function ItemVariantOption( {
 			role="option"
 			onClick={ onSelect }
 			selected={ isSelected }
+			detached={ isStreamlinedPrice }
 		>
 			{ isJetpack( variant ) ? (
 				<JetpackItemVariantDropDownPrice variant={ variant } allVariants={ allVariants } />
@@ -232,8 +276,10 @@ function ItemVariantOption( {
 					variant={ variant }
 					compareTo={ compareTo }
 					product={ selectedItem }
+					isStreamlinedPrice={ isStreamlinedPrice }
 				/>
 			) }
+			{ isStreamlinedPrice && isSelected && <WPCheckoutCheckIcon /> }
 		</Option>
 	);
 }
diff --git a/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx b/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx
index bb29d4136777..73de8187d82d 100644
--- a/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx
+++ b/client/my-sites/checkout/src/components/item-variation-picker/styles.tsx
@@ -1,5 +1,6 @@
 import { css } from '@emotion/react';
 import styled from '@emotion/styled';
+import { CheckIcon } from '../check-icon';
 import { CurrentOptionProps, OptionProps } from './types';
 
 export const CurrentOption = styled.button< CurrentOptionProps >`
@@ -21,6 +22,7 @@ export const CurrentOption = styled.button< CurrentOptionProps >`
 
 	${ ( props ) =>
 		props.open &&
+		! props.detached &&
 		css`
 			border-radius: 3px 3px 0 0;
 		` }
@@ -39,14 +41,46 @@ export const Option = styled.li< OptionProps >`
 	align-items: center;
 	/* the calc aligns the price with the price in CurrentOption */
 	padding: 10px calc( 14px + 24px + 16px ) 10px 16px;
+	position: relative;
+
+	${ ( props ) =>
+		props.detached &&
+		css`
+			padding-top: 14px;
+			padding-bottom: 14px;
+		` }
 
 	&:hover {
 		background: var( --studio-wordpress-blue-5 );
 	}
 
-	&.item-variant-option--selected {
-		background: var( --studio-wordpress-blue-50 );
-		color: #fff;
+	${ ( props ) =>
+		! props.detached
+			? css`
+					&.item-variant-option--selected {
+						background: var( --studio-wordpress-blue-50 );
+						color: #fff;
+					}
+			  `
+			: css`
+					&.item-variant-option--selected * {
+						color: var( --studio-black );
+					}
+			  ` }
+`;
+
+export const WPCheckoutCheckIcon = styled( CheckIcon )`
+	fill: ${ ( props ) => props.theme.colors.success };
+	margin-right: 4px;
+	position: absolute;
+	top: 50%;
+	right: 16px;
+	transform: translateY( -50% );
+	.rtl & {
+		margin-right: 0;
+		margin-left: 4px;
+		right: auto;
+		left: 16px;
 	}
 `;
 
@@ -60,12 +94,27 @@ export const Dropdown = styled.div`
 	}
 `;
 
-export const OptionList = styled.ul`
+export const OptionList = styled.ul< { detached: boolean } >`
 	position: absolute;
 	width: 100%;
 	z-index: 4;
 	margin: 0;
 	box-shadow: rgba( 0, 0, 0, 0.16 ) 0px 1px 4px;
+	${ ( props ) =>
+		props.detached &&
+		css`
+			box-shadow:
+				0px 50px 43px 0px rgba( 0, 0, 0, 0.02 ),
+				0px 30px 36px 0px rgba( 0, 0, 0, 0.04 ),
+				0px 15px 27px 0px rgba( 0, 0, 0, 0.07 ),
+				0px 5px 15px 0px rgba( 0, 0, 0, 0.08 );
+			margin-top: 10px;
+
+			${ Option }:first-of-type {
+				border-top-left-radius: 3px;
+				border-top-right-radius: 3px;
+			}
+		` }
 
 	${ Option } {
 		margin-top: -1px;
@@ -100,6 +149,28 @@ export const Discount = styled.span`
 	}
 `;
 
+export const DiscountAbsolute = styled.span< {
+	color: string;
+	backgroundColor: string;
+} >`
+	text-align: center;
+	color: ${ ( props ) => props.color };
+	display: block;
+	background-color: ${ ( props ) => props.backgroundColor };
+	padding: 0 10px;
+	border-radius: 4px;
+	font-size: 12px;
+	line-height: 20px;
+	.rtl & {
+		margin-right: 0;
+		margin-left: 8px;
+	}
+	// Keep selected state override for now, adjust if needed
+	.item-variant-option--selected & {
+		color: ${ ( props ) => props.color };
+	}
+`;
+
 export const DoNotPayThis = styled.del`
 	text-decoration: line-through;
 	margin-right: 8px;
@@ -173,4 +244,7 @@ export const IntroPricingText = styled.span`
 export const PriceTextContainer = styled.span`
 	font-size: 14px;
 	text-align: right;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-end;
 `;
diff --git a/client/my-sites/checkout/src/components/item-variation-picker/types.ts b/client/my-sites/checkout/src/components/item-variation-picker/types.ts
index ede3a358ae61..dbd7096ece6d 100644
--- a/client/my-sites/checkout/src/components/item-variation-picker/types.ts
+++ b/client/my-sites/checkout/src/components/item-variation-picker/types.ts
@@ -42,8 +42,10 @@ export type OnChangeItemVariant = (
 
 export type CurrentOptionProps = {
 	open: boolean;
+	detached?: boolean;
 };
 
 export type OptionProps = {
 	selected: boolean;
+	detached?: boolean;
 };
diff --git a/client/my-sites/checkout/src/components/item-variation-picker/util.ts b/client/my-sites/checkout/src/components/item-variation-picker/util.ts
index 1068dcb618ec..22861b9d3b59 100644
--- a/client/my-sites/checkout/src/components/item-variation-picker/util.ts
+++ b/client/my-sites/checkout/src/components/item-variation-picker/util.ts
@@ -61,9 +61,10 @@ export function getItemVariantCompareToPrice(
 	return ( compareTo.priceInteger / compareTo.termIntervalInMonths ) * variant.termIntervalInMonths;
 }
 
-export function getItemVariantDiscountPercentage(
+export function getItemVariantDiscount(
 	variant: WPCOMProductVariant,
-	compareTo?: WPCOMProductVariant
+	compareTo?: WPCOMProductVariant,
+	discountType: 'percentage' | 'absolute' = 'percentage'
 ): number {
 	const compareToPriceForVariantTerm = getItemVariantCompareToPrice( variant, compareTo );
 
@@ -73,11 +74,19 @@ export function getItemVariantDiscountPercentage(
 			? variant.priceBeforeDiscounts
 			: variant.priceInteger;
 
+	if ( ! compareToPriceForVariantTerm ) {
+		return 0;
+	}
+
+	if ( discountType === 'absolute' ) {
+		return compareToPriceForVariantTerm - variantPrice;
+	}
+
 	// Extremely low "discounts" are possible if the price of the longer term has been rounded
 	// if they cannot be rounded to at least a percentage point we should not show them.
-	const discountPercentage = compareToPriceForVariantTerm
-		? Math.round( 100 - ( variantPrice / compareToPriceForVariantTerm ) * 100 )
-		: 0;
+	const discountPercentage = Math.round(
+		100 - ( variantPrice / compareToPriceForVariantTerm ) * 100
+	);
 
 	return discountPercentage;
 }
diff --git a/client/my-sites/checkout/src/components/item-variation-picker/variant-dropdown-price.tsx b/client/my-sites/checkout/src/components/item-variation-picker/variant-dropdown-price.tsx
index bd52712ee54e..28db0f352b61 100644
--- a/client/my-sites/checkout/src/components/item-variation-picker/variant-dropdown-price.tsx
+++ b/client/my-sites/checkout/src/components/item-variation-picker/variant-dropdown-price.tsx
@@ -1,4 +1,5 @@
 import { isMultiYearDomainProduct } from '@automattic/calypso-products';
+import colorStudio from '@automattic/color-studio';
 import { formatCurrency } from '@automattic/number-formatters';
 import { useMobileBreakpoint } from '@automattic/viewport-react';
 import { useTranslate } from 'i18n-calypso';
@@ -6,6 +7,7 @@ import { FunctionComponent } from 'react';
 import { preventWidows } from 'calypso/lib/formatting';
 import {
 	Discount,
+	DiscountAbsolute,
 	DoNotPayThis,
 	IntroPricing,
 	IntroPricingText,
@@ -14,7 +16,7 @@ import {
 	PriceTextContainer,
 	Variant,
 } from './styles';
-import { getItemVariantDiscountPercentage, getItemVariantCompareToPrice } from './util';
+import { getItemVariantDiscount, getItemVariantCompareToPrice } from './util';
 import type { WPCOMProductVariant } from './types';
 import type { ResponseCartProduct } from '@automattic/shopping-cart';
 
@@ -31,14 +33,49 @@ const DiscountPercentage: FunctionComponent< { percent: number } > = ( { percent
 	);
 };
 
+const DiscountPrice: FunctionComponent< {
+	price: string;
+	termIntervalInMonths: number;
+} > = ( { price, termIntervalInMonths } ) => {
+	const translate = useTranslate();
+
+	// Determine colors for DiscountAbsolute based on term
+	let discountColor = colorStudio.colors[ 'Green 80' ];
+	let discountBackgroundColor = colorStudio.colors[ 'Green 10' ];
+
+	if ( termIntervalInMonths === 12 ) {
+		discountColor = colorStudio.colors[ 'Green 50' ];
+		discountBackgroundColor = colorStudio.colors[ 'Green 0' ]; // As per original request for yearly
+	} else if ( termIntervalInMonths === 24 ) {
+		discountColor = colorStudio.colors[ 'Green 80' ];
+		discountBackgroundColor = colorStudio.colors[ 'Green 5' ]; // As per original request for 2-yearly (using Green 5 as "Green" is ambiguous)
+	}
+
+	return (
+		<DiscountAbsolute color={ discountColor } backgroundColor={ discountBackgroundColor }>
+			{ translate( 'Save %(price)s', { args: { price } } ) }
+		</DiscountAbsolute>
+	);
+};
+
 export const ItemVariantDropDownPrice: FunctionComponent< {
 	variant: WPCOMProductVariant;
 	compareTo?: WPCOMProductVariant;
 	product: ResponseCartProduct;
-} > = ( { variant, compareTo, product } ) => {
+	isStreamlinedPrice?: boolean;
+} > = ( { variant, compareTo, product, isStreamlinedPrice } ) => {
 	const isMobile = useMobileBreakpoint();
 	const compareToPriceForVariantTerm = getItemVariantCompareToPrice( variant, compareTo );
-	const discountPercentage = getItemVariantDiscountPercentage( variant, compareTo );
+	const discountPercentage = getItemVariantDiscount( variant, compareTo );
+	const discount = formatCurrency(
+		getItemVariantDiscount( variant, compareTo, 'absolute' ),
+		variant.currency,
+		{
+			stripZeros: true,
+			isSmallestUnit: true,
+		}
+	);
+
 	const formattedCurrentPrice = formatCurrency( variant.priceInteger, variant.currency, {
 		stripZeros: true,
 		isSmallestUnit: true,
@@ -180,28 +217,61 @@ export const ItemVariantDropDownPrice: FunctionComponent< {
 	// or if it's a Jetpack 2 or 3-year plan
 	const canDisplayDiscountPercentage = ! isIntroductoryOffer;
 
+	const label =
+		variant.termIntervalInMonths === 1 && isStreamlinedPrice
+			? translate( 'Month' )
+			: variant.variantLabel.noun;
+
 	return (
 		<Variant>
 			<Label>
-				{ variant.variantLabel.noun }
-				{ hasDiscount && isMobile && <DiscountPercentage percent={ discountPercentage } /> }
+				{ label }
+				{ hasDiscount &&
+					isMobile &&
+					( isStreamlinedPrice ? (
+						<DiscountPrice
+							price={ discount }
+							termIntervalInMonths={ variant.termIntervalInMonths }
+						/>
+					) : (
+						<DiscountPercentage percent={ discountPercentage } />
+					) ) }
 			</Label>
 			<PriceTextContainer>
-				{ hasDiscount && ! isMobile && canDisplayDiscountPercentage && (
-					<DiscountPercentage percent={ discountPercentage } />
-				) }
-				{ hasDiscount && ! isIntroductoryOffer && (
+				{ hasDiscount &&
+					! isMobile &&
+					canDisplayDiscountPercentage &&
+					( isStreamlinedPrice ? (
+						<DiscountPrice
+							price={ discount }
+							termIntervalInMonths={ variant.termIntervalInMonths }
+						/>
+					) : (
+						<DiscountPercentage percent={ discountPercentage } />
+					) ) }
+				{ hasDiscount && ! isIntroductoryOffer && ! isStreamlinedPrice && (
 					<DoNotPayThis>{ formattedCompareToPriceForVariantTerm }</DoNotPayThis>
 				) }
 
-				<Price aria-hidden={ isIntroductoryOffer }>{ formattedCurrentPrice }</Price>
-				<IntroPricing>
-					{ ! isMultiYearDomain && (
-						<IntroPricingText>
-							{ isIntroductoryOffer && translatedIntroOfferDetails() }
-						</IntroPricingText>
-					) }
-				</IntroPricing>
+				{ isStreamlinedPrice ? (
+					! canDisplayDiscountPercentage && (
+						<DiscountPrice
+							price={ discount }
+							termIntervalInMonths={ variant.termIntervalInMonths }
+						/>
+					)
+				) : (
+					<Price aria-hidden={ isIntroductoryOffer }>{ formattedCurrentPrice }</Price>
+				) }
+				{ ! isStreamlinedPrice && (
+					<IntroPricing>
+						{ ! isMultiYearDomain && (
+							<IntroPricingText>
+								{ isIntroductoryOffer && translatedIntroOfferDetails() }
+							</IntroPricingText>
+						) }
+					</IntroPricing>
+				) }
 			</PriceTextContainer>
 		</Variant>
 	);
diff --git a/client/my-sites/checkout/src/components/item-variation-picker/variant-radio-price.tsx b/client/my-sites/checkout/src/components/item-variation-picker/variant-radio-price.tsx
index 1e1ea4fcec15..4882e75a175e 100644
--- a/client/my-sites/checkout/src/components/item-variation-picker/variant-radio-price.tsx
+++ b/client/my-sites/checkout/src/components/item-variation-picker/variant-radio-price.tsx
@@ -3,7 +3,7 @@ import { formatCurrency } from '@automattic/number-formatters';
 import { styled } from '@automattic/wpcom-checkout';
 import { useTranslate } from 'i18n-calypso';
 import { FunctionComponent } from 'react';
-import { getItemVariantDiscountPercentage } from './util';
+import { getItemVariantDiscount } from './util';
 import type { WPCOMProductVariant } from './types';
 
 const Discount = styled.span`
@@ -69,7 +69,7 @@ export const ItemVariantRadioPrice: FunctionComponent< {
 	compareTo?: WPCOMProductVariant;
 } > = ( { variant, compareTo } ) => {
 	const translate = useTranslate();
-	const discountPercentage = getItemVariantDiscountPercentage( variant, compareTo );
+	const discountPercentage = getItemVariantDiscount( variant, compareTo );
 
 	const pricePerMonth = Math.round( variant.priceInteger / variant.termIntervalInMonths );
 
diff --git a/client/my-sites/checkout/src/components/jetpack-akismet-checkout-sidebar-plan-upsell/index.tsx b/client/my-sites/checkout/src/components/jetpack-akismet-checkout-sidebar-plan-upsell/index.tsx
index 9ab6d2f54926..47aeaec85510 100644
--- a/client/my-sites/checkout/src/components/jetpack-akismet-checkout-sidebar-plan-upsell/index.tsx
+++ b/client/my-sites/checkout/src/components/jetpack-akismet-checkout-sidebar-plan-upsell/index.tsx
@@ -14,7 +14,7 @@ import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
 import { useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { useGetProductVariants } from '../../hooks/product-variants';
-import { getItemVariantDiscountPercentage } from '../item-variation-picker/util';
+import { getItemVariantDiscount } from '../item-variation-picker/util';
 
 import './style.scss';
 
@@ -172,7 +172,7 @@ const useCalculatedDiscounts = () => {
 	];
 
 	return {
-		percentSavings: getItemVariantDiscountPercentage( biennial, current ),
+		percentSavings: getItemVariantDiscount( biennial, current ),
 		priceBreakdown,
 		finalBreakdown,
 	};
diff --git a/client/my-sites/checkout/src/components/wp-order-review-line-items.tsx b/client/my-sites/checkout/src/components/wp-order-review-line-items.tsx
index 4f244a68e300..5d8202b5a1b5 100644
--- a/client/my-sites/checkout/src/components/wp-order-review-line-items.tsx
+++ b/client/my-sites/checkout/src/components/wp-order-review-line-items.tsx
@@ -24,8 +24,8 @@ import { has100YearPlan } from 'calypso/lib/cart-values/cart-items';
 import { isWcMobileApp } from 'calypso/lib/mobile-app';
 import { useGetProductVariants } from 'calypso/my-sites/checkout/src/hooks/product-variants';
 import {
-	isStreamlinedPriceRadioTreatment,
 	useStreamlinedPriceExperiment,
+	isStreamlinedPriceCheckoutTreatment,
 } from 'calypso/my-sites/plans-features-main/hooks/use-streamlined-price-experiment';
 import { getSignupCompleteFlowName } from 'calypso/signup/storageUtils';
 import { useDispatch, useSelector } from 'calypso/state';
@@ -383,6 +383,12 @@ function LineItemWrapper( {
 
 	const [ isStreamlinedPriceExperimentLoading, streamlinedPriceExperimentAssignment ] =
 		useStreamlinedPriceExperiment();
+
+	const isStreamlinedPrice =
+		! isStreamlinedPriceExperimentLoading &&
+		isStreamlinedPriceCheckoutTreatment( streamlinedPriceExperimentAssignment ) &&
+		isWpComPlan( product.product_slug );
+
 	const variants = useGetProductVariants( product, ( variant ) => {
 		// Only show term variants which are equal to or longer than the variant that
 		// was in the cart when checkout finished loading (not necessarily the
@@ -398,11 +404,7 @@ function LineItemWrapper( {
 			return true;
 		}
 
-		if (
-			isWpComPlan( variant.productSlug ) &&
-			! isStreamlinedPriceExperimentLoading &&
-			isStreamlinedPriceRadioTreatment( streamlinedPriceExperimentAssignment )
-		) {
+		if ( isStreamlinedPrice ) {
 			return true;
 		}
 
diff --git a/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts b/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts
index d37831f4bcb7..58e82a424196 100644
--- a/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts
+++ b/client/my-sites/plans-features-main/hooks/use-streamlined-price-experiment.ts
@@ -41,6 +41,13 @@ export function isStreamlinedPriceRadioTreatment( variationName?: string | null
 	return variationName.includes( 'checkout_radio' );
 }
 
+export function isStreamlinedPriceDropdownTreatment( variationName?: string | null ) {
+	if ( ! variationName ) {
+		return false;
+	}
+	return variationName.includes( 'checkout_dropdown' );
+}
+
 export function isStreamlinedPriceCheckoutTreatment( variationName?: string | null ) {
 	if ( ! variationName ) {
 		return false;


Reviews:
user: login: dlind1
body: The P2 design shows the selected plan price as $20 /month with a crossed-out $40 /month underneath and the PR screenshots have the same, but somehow I'm getting the full plan price when testing this locally.

<img width="987" alt="Screenshot 2025-05-15 at 00 38 27" src="https://github.com/user-attachments/assets/6156758a-915f-413c-a642-c9857eb8c712" />

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:41:50Z
user: login: claudiucelfilip
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T07:11:44Z
user: login: claudiucelfilip
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T07:14:16Z
user: login: dlind1
body: LGTM!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T00:43:01Z

Review comments:
diff_hunk: @@ -398,6 +399,15 @@ function LineItemWrapper( {
 			return true;
 		}
 
+		if (
path: client/my-sites/checkout/src/components/wp-order-review-line-items.tsx
user: login: dlind1
body: This looks like a duplicate of lines 411-477 with the addition of isStreamlinedPriceDropdownTreatment check.
created_at: 2025-05-14T22:10:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -81,3 +81,22 @@ export function getItemVariantDiscountPercentage(
 
 	return discountPercentage;
 }
+
+export function getItemVariantDiscount(
+	variant: WPCOMProductVariant,
+	compareTo?: WPCOMProductVariant
+): number {
+	const compareToPriceForVariantTerm = getItemVariantCompareToPrice( variant, compareTo );
+
+	// Ignore intro discount if it is a 1 month only discount
+	const variantPrice =
path: client/my-sites/checkout/src/components/item-variation-picker/util.ts
user: login: dlind1
body: What's a best way to test with a 1 month introductory offer? What other test cases would be good to check?
created_at: 2025-05-14T22:30:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -81,3 +81,22 @@ export function getItemVariantDiscountPercentage(
 
 	return discountPercentage;
 }
+
+export function getItemVariantDiscount(
+	variant: WPCOMProductVariant,
+	compareTo?: WPCOMProductVariant
+): number {
+	const compareToPriceForVariantTerm = getItemVariantCompareToPrice( variant, compareTo );
+
+	// Ignore intro discount if it is a 1 month only discount
+	const variantPrice =
path: client/my-sites/checkout/src/components/item-variation-picker/util.ts
user: login: claudiucelfilip
body: I'm not sure how. These changes were copied over from the `getItemVariantDiscountPercentage` to return an absolute value instead of a percentage.
This does create a duplicate code, so I decided to refactor the original and return the absolute value, if specified.
created_at: 2025-05-15T07:11:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -398,6 +399,15 @@ function LineItemWrapper( {
 			return true;
 		}
 
+		if (
path: client/my-sites/checkout/src/components/wp-order-review-line-items.tsx
user: login: claudiucelfilip
body: Thanks for catching that!
I replaced it with a  single`isStreamlinedPriceCheckoutTreatment` outside the hooks callback. It'll be useful for the rest of the changes in the Order list.
created_at: 2025-05-15T07:14:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-14T11:15:13Z
body: <details>
<summary>Calypso Live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-144491">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-144491%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-144491">https://calypso.live?image=registry.a8c.com/calypso/app:build-144491</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Jetpack Cloud live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-144491&env=jetpack">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-144491%26env%3Djetpack%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-144491&env=jetpack">https://calypso.live?image=registry.a8c.com/calypso/app:build-144491&env=jetpack</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Automattic for Agencies live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-144491&env=a8c-for-agencies">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-144491%26env%3Da8c-for-agencies%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-144491&env=a8c-for-agencies">https://calypso.live?image=registry.a8c.com/calypso/app:build-144491&env=a8c-for-agencies</a>
</td>
</tr>
</table>
</details>
<!--calypso-live-watermark:apr@v1-->
author_association: NONE
user: login: matticbot
created_at: 2025-05-14T11:16:49Z
body: <!-- icfy-watermark: c52822 -->
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

**Sections** (~735 bytes added 📈 [gzipped])
<details>

```
name                      parsed_size           gzip_size
checkout                      +2438 B  (+0.1%)     +722 B  (+0.1%)
a8c-for-agencies-client       +2438 B  (+0.1%)     +722 B  (+0.1%)
plugins                         +66 B  (+0.0%)      +13 B  (+0.0%)
plans                           +66 B  (+0.0%)      +13 B  (+0.0%)
jetpack-app                     +66 B  (+0.0%)      +13 B  (+0.0%)
async-step-unified-plans        +66 B  (+0.0%)      +13 B  (+0.0%)
```

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.
</details>

**Async-loaded Components** (~30 bytes added 📈 [gzipped])
<details>

```
name                                                                              parsed_size           gzip_size
async-load-signup-steps-plans-theme-preselected                                         +66 B  (+0.0%)      +13 B  (+0.0%)
async-load-signup-steps-plans                                                           +66 B  (+0.0%)      +13 B  (+0.0%)
async-load-signup-steps-page-picker                                                     +66 B  (+0.0%)      +17 B  (+0.0%)
async-load-purchase-modal-wrapper                                                       +66 B  (+0.0%)      +17 B  (+0.0%)
async-load-my-sites-checkout-purchase-modal-is-eligible-for-one-click-checkou...        +66 B  (+0.0%)      +17 B  (+0.0%)
```

React components that are loaded lazily, when a certain part of UI is displayed for the first time.
</details>

**Legend**
<details>
<summary>What is parsed and gzip size?</summary>

**Parsed Size:** Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
**Gzip Size:** Compressed size of the JS and CSS files. This much data needs to be downloaded over network.
</details>

Generated by performance advisor bot at [iscalypsofastyet.com](http://iscalypsofastyet.com).
author_association: CONTRIBUTOR
user: login: matticbot
created_at: 2025-05-14T11:31:29Z
body: **This PR modifies the release build for the following Calypso Apps:**

_For info about this notification, see here: PCYsg-OT6-p2_

* notifications

To test WordPress.com changes, run `install-plugin.sh $pluginSlug update/streamlined-pricing-experiment-dropdown` on your sandbox.
<!--calypso-app-artifacts-watermark:apr@v1-->
author_association: CONTRIBUTOR
user: login: claudiucelfilip
created_at: 2025-05-15T07:31:41Z
body: > The P2 design shows the selected plan price as $20 /month with a crossed-out $40 /month underneath and the PR screenshots have the same, but somehow I'm getting the full plan price when testing this locally.

@dlind1 The changes in this PR should be restricted to the dropdown only. I'll follow up in [this PR](https://github.com/Automattic/wp-calypso/pull/103465) with the line item changes, such as:
<img width="240" alt="Screenshot 2025-05-15 at 10 18 15" src="https://github.com/user-attachments/assets/e1808e98-c104-40cd-8790-60a52e0873c0" />

The test instruction screenshots I added have monthly term set as the plan variation which might be confusing compared to the design screenshot. I'll update them with the same term selection.

author_association: CONTRIBUTOR