Commit 72b8d3edb92 for woocommerce

commit 72b8d3edb9294e43b89d86cfd26894c8be49a72c
Author: Lucio Giannotta <lucio.giannotta@a8c.com>
Date:   Mon Mar 2 22:15:49 2026 +0800

    Add dropdown navigation to Customer Account block (#63301)

    * Add dropdown navigation to Customer Account block

    Implements a new dropdown navigation feature for the Customer Account
    block that displays account menu items (Dashboard, Orders, Downloads,
    etc.) when clicked. Disabled by default, can be enabled via a toggle
    in the block settings.

    - Add WordPress Interactivity API store for dropdown state management
    - Implement dropdown positioning logic to avoid viewport overflow
    - Add keyboard navigation (Escape key closes dropdown)
    - Add click-outside-to-close functionality
    - Extend PHP render method to support both link and dropdown variants
    - Add comprehensive SCSS styling with viewport-aware positioning
    - Add caret icon for visual indicator

    ---------

    Co-authored-by: Karol Manijak <20098064+kmanijak@users.noreply.github.com>

diff --git a/plugins/woocommerce/changelog/63301-wooplug-6258-customer-account-block-enable-dropdown-navigation b/plugins/woocommerce/changelog/63301-wooplug-6258-customer-account-block-enable-dropdown-navigation
new file mode 100644
index 00000000000..982cb6708a6
--- /dev/null
+++ b/plugins/woocommerce/changelog/63301-wooplug-6258-customer-account-block-enable-dropdown-navigation
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Add optional dropdown navigation to the Customer Account block, displaying account menu links in a dropdown menu when enabled.
\ No newline at end of file
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json
index 95b9bf03825..313fe84e4ba 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.json
@@ -5,13 +5,11 @@
 	"category": "woocommerce",
 	"keywords": [ "WooCommerce", "My Account" ],
 	"supports": {
-		"interactivity": {
-			"clientNavigation": true
-		},
 		"align": true,
 		"color": {
 			"text": true
 		},
+		"interactivity": true,
 		"typography": {
 			"fontSize": true,
 			"__experimentalFontFamily": true
@@ -26,6 +24,10 @@
 			"type": "string",
 			"default": "icon_and_text"
 		},
+		"hasDropdownNavigation": {
+			"type": "boolean",
+			"default": false
+		},
 		"iconStyle": {
 			"type": "string",
 			"default": "default"
@@ -35,6 +37,9 @@
 			"default": "icon"
 		}
 	},
+	"viewScriptModule": "woocommerce/customer-account",
+	"style": "file:../woocommerce/customer-account-style.css",
+	"editorStyle": "file:../woocommerce/customer-account-editor.css",
 	"textdomain": "woocommerce",
 	"apiVersion": 3,
 	"$schema": "https://schemas.wp.org/trunk/block.json"
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx
index 27391f321eb..27dc9205597 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/block.tsx
@@ -6,6 +6,7 @@ import {
 	customerAccountStyle,
 	customerAccountStyleAlt,
 	customerAccountStyleLine,
+	caret,
 } from '@woocommerce/icons';
 import { getSetting } from '@woocommerce/settings';
 import { __ } from '@wordpress/i18n';
@@ -56,7 +57,8 @@ export const CustomerAccountBlock = ( {
 }: {
 	attributes: Attributes;
 } ): JSX.Element => {
-	const { displayStyle, iconStyle, iconClass } = attributes;
+	const { displayStyle, hasDropdownNavigation, iconStyle, iconClass } =
+		attributes;

 	const ariaAttributes =
 		displayStyle === DisplayStyle.ICON_ONLY
@@ -67,6 +69,7 @@ export const CustomerAccountBlock = ( {

 	return (
 		<a
+			className="wc-block-customer-account__link"
 			href={ getSetting(
 				'dashboardUrl',
 				getSetting( 'wpLoginUrl', '/wp-login.php' )
@@ -79,6 +82,13 @@ export const CustomerAccountBlock = ( {
 				iconClass={ iconClass }
 			/>
 			<Label displayStyle={ displayStyle } />
+			{ hasDropdownNavigation && (
+				<Icon
+					className="wc-block-customer-account__caret"
+					icon={ caret }
+					size={ 10 }
+				/>
+			) }
 		</a>
 	);
 };
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/frontend.ts
new file mode 100644
index 00000000000..2482485c0b4
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/frontend.ts
@@ -0,0 +1,148 @@
+/**
+ * External dependencies
+ */
+import { store, getContext, getElement } from '@wordpress/interactivity';
+
+export interface CustomerAccountContext {
+	isDropdownOpen: boolean;
+	showAbove: boolean;
+	alignRight: boolean;
+}
+
+const SELECTORS = {
+	wrapper: '.wc-block-customer-account--has-dropdown',
+	dropdown: '.wc-block-customer-account__dropdown',
+	trigger: '.wc-block-customer-account__toggle',
+};
+
+const FLIP_THRESHOLD = 16;
+const SHOW_ABOVE_THRESHOLD = 0.6;
+
+const getWrapper = (): HTMLElement | null => {
+	const { ref } = getElement();
+	return ref?.closest( SELECTORS.wrapper ) as HTMLElement | null;
+};
+
+const getDropdown = ( wrapper: HTMLElement ): HTMLElement | null => {
+	return wrapper.querySelector( SELECTORS.dropdown ) as HTMLElement | null;
+};
+
+const focusTrigger = () => {
+	const { ref } = getElement();
+	const trigger = ref?.querySelector(
+		SELECTORS.trigger
+	) as HTMLElement | null;
+	trigger?.focus();
+};
+
+const updateDropdownPosition = (
+	context: CustomerAccountContext,
+	wrapper: HTMLElement
+) => {
+	const rect = wrapper.getBoundingClientRect();
+	const viewportHeight = window.innerHeight;
+	const viewportWidth = document.documentElement.clientWidth;
+
+	context.showAbove = rect.bottom > viewportHeight * SHOW_ABOVE_THRESHOLD;
+
+	const dropdown = getDropdown( wrapper );
+	if ( ! dropdown ) {
+		return;
+	}
+
+	dropdown.hidden = false;
+	const dropdownWidth = dropdown.offsetWidth;
+	dropdown.hidden = true;
+
+	const rightSpace = viewportWidth - ( rect.left + dropdownWidth );
+	context.alignRight = rightSpace < FLIP_THRESHOLD;
+};
+
+const { actions: privateActions } = store(
+	'woocommerce/customer-account/private',
+	{
+		actions: {
+			handleDocumentClick: ( event: MouseEvent ) => {
+				const context = getContext< CustomerAccountContext >();
+				if ( ! context.isDropdownOpen ) {
+					return;
+				}
+				const { ref } = getElement();
+				if ( ref && ! ref.contains( event.target as Node ) ) {
+					context.isDropdownOpen = false;
+				}
+			},
+			handleKeydown: ( event: KeyboardEvent ) => {
+				if ( event.key !== 'Escape' ) {
+					return;
+				}
+
+				const context = getContext< CustomerAccountContext >();
+				if ( ! context.isDropdownOpen ) {
+					return;
+				}
+
+				context.isDropdownOpen = false;
+				focusTrigger();
+			},
+			handleFocusOut: ( event: FocusEvent ) => {
+				const context = getContext< CustomerAccountContext >();
+				if ( ! context.isDropdownOpen ) {
+					return;
+				}
+
+				const { ref } = getElement();
+				const relatedTarget = event.relatedTarget as Node | null;
+				if (
+					ref &&
+					( ! relatedTarget || ! ref.contains( relatedTarget ) )
+				) {
+					context.isDropdownOpen = false;
+				}
+			},
+			toggleDropdown: ( event: MouseEvent ) => {
+				event.preventDefault();
+				event.stopPropagation();
+
+				const context = getContext< CustomerAccountContext >();
+				if ( context.isDropdownOpen ) {
+					context.isDropdownOpen = false;
+					return;
+				}
+
+				const wrapper = getWrapper();
+				if ( wrapper ) {
+					updateDropdownPosition( context, wrapper );
+				}
+
+				context.isDropdownOpen = true;
+			},
+		},
+	}
+);
+
+store( 'woocommerce/customer-account', {
+	state: {
+		/**
+		 * Whether the dropdown is open.
+		 *
+		 * @type {boolean}
+		 */
+		get isDropdownOpen() {
+			const context = getContext< CustomerAccountContext >();
+			return context.isDropdownOpen;
+		},
+	},
+	actions: {
+		/**
+		 * Toggle the dropdown.
+		 *
+		 * Public API for third-party toggling of the dropdown.
+		 *
+		 * @param event MouseEvent The event that triggered the toggle.
+		 */
+		toggleDropdown: ( event: MouseEvent ) => {
+			privateActions.toggleDropdown( event as MouseEvent );
+		},
+	},
+} );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx
index 7985c4d8066..f89a2e780f1 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/index.tsx
@@ -11,7 +11,6 @@ import { __ } from '@wordpress/i18n';
  */
 import metadata from './block.json';
 import edit from './edit';
-import './style.scss';

 registerBlockType( metadata, {
 	icon: {
@@ -43,6 +42,7 @@ registerBlockVariation( 'woocommerce/customer-account', {
 	attributes: {
 		...metadata.attributes,
 		displayStyle: 'icon_and_text',
+		hasDropdownNavigation: false,
 		iconStyle: 'default',
 		iconClass: 'wc-block-customer-account__account-icon',
 	},
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx
index da9543edc04..6edd0ff0473 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/sidebar-settings.tsx
@@ -16,6 +16,7 @@ import { createInterpolateElement } from '@wordpress/element';
 import {
 	PanelBody,
 	SelectControl,
+	ToggleControl,
 	// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
 	__experimentalToggleGroupControl as ToggleGroupControl,
 	// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
@@ -159,6 +160,20 @@ export const BlockSettings = ( {
 						/>
 					</ToggleGroupControl>
 				) : null }
+				<ToggleControl
+					__nextHasNoMarginBottom
+					label={ __( 'Show dropdown navigation', 'woocommerce' ) }
+					help={ __(
+						'Display a dropdown menu with account navigation links when clicked.',
+						'woocommerce'
+					) }
+					checked={ attributes.hasDropdownNavigation ?? false }
+					onChange={ ( value: boolean ) => {
+						setAttributes( {
+							hasDropdownNavigation: value,
+						} );
+					} }
+				/>
 			</PanelBody>
 		</InspectorControls>
 	);
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss
index 5c5738da13a..f0b5268b4b9 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/style.scss
@@ -15,24 +15,146 @@
 	@include flex-justify-content(center);
 }

-.wp-block-woocommerce-customer-account {
-	a {
-		text-decoration: none !important;
-		align-items: center;
-		display: flex;
-		color: currentColor !important;
-		gap: em($gap-smaller);
-		white-space: nowrap;
-		line-height: 1;
-		padding: em($gap-smaller);
-
-		&:hover {
-			text-decoration: underline !important;
+:where(.wp-block-woocommerce-customer-account) {
+	--wc-customer-account-padding: var(
+		--wp--preset--spacing--20,
+		#{em($gap-smaller)}
+	);
+	--wc-customer-account-viewport-gutter: var(--wp--preset--spacing--20, 16px);
+	--wc-customer-account-dropdown-padding-inline: var(
+		--wp--preset--spacing--30,
+		#{$gap}
+	);
+	--wc-customer-account-dropdown-padding-block: var(
+		--wp--preset--spacing--20,
+		#{$gap-smaller}
+	);
+	--wc-customer-account-dropdown-surface: var(
+		--wp--preset--color--base,
+		#fff
+	);
+	--wc-customer-account-dropdown-border-color: color-mix(
+		in srgb,
+		currentColor 20%,
+		transparent
+	);
+	--wc-customer-account-dropdown-item-hover-bg: color-mix(
+		in srgb,
+		currentColor 8%,
+		transparent
+	);
+}
+
+:where(.wp-block-woocommerce-customer-account .wc-block-customer-account__link),
+:where(
+		.wp-block-woocommerce-customer-account.wc-block-customer-account--has-dropdown
+			.wc-block-customer-account__toggle
+	) {
+	align-items: center;
+	color: inherit;
+	display: flex;
+	font-family: inherit;
+	font-size: inherit;
+	font-weight: inherit;
+	line-height: 1;
+	padding: var(--wc-customer-account-padding);
+	text-decoration: none;
+	white-space: nowrap;
+
+	&:hover {
+		text-decoration: underline;
+	}
+
+	.wc-block-customer-account__account-icon {
+		width: em($grid-unit-30);
+		height: em($grid-unit-30);
+	}
+}
+
+:where(.wp-block-woocommerce-customer-account) {
+	.wc-block-customer-account__caret {
+		margin-left: em($gap-smallest);
+		transition: transform 0.2s ease;
+		width: em(10px);
+		height: em(6px);
+	}
+
+	&.wc-block-customer-account--has-dropdown {
+		position: relative;
+
+		.wc-block-customer-account__toggle {
+			background: none;
+			border: none;
+			cursor: pointer;
+			font: inherit;
+		}
+
+		&.is-dropdown-open .wc-block-customer-account__caret {
+			transform: rotate(180deg);
 		}

-		.wc-block-customer-account__account-icon {
-			width: em($grid-unit-30);
-			height: em($grid-unit-30);
+		.wc-block-customer-account__dropdown {
+			background: var(--wc-customer-account-dropdown-surface);
+			border: 1px solid var(--wc-customer-account-dropdown-border-color);
+			border-radius: $universal-border-radius;
+			box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
+			position: absolute;
+			top: 100%;
+			left: 0;
+			min-width: 200px;
+			max-width: calc(
+				100vw - (2 * var(--wc-customer-account-viewport-gutter))
+			);
+			z-index: 100;
+
+			&[hidden] {
+				display: none;
+			}
+		}
+
+		// Show above the trigger when near viewport bottom.
+		&.wc-block-customer-account--show-above
+			.wc-block-customer-account__dropdown {
+			top: auto;
+			bottom: 100%;
+		}
+
+		// Align right when too close to the edge.
+		&.wc-block-customer-account--align-right
+			.wc-block-customer-account__dropdown {
+			left: auto;
+			right: 0;
+		}
+
+		.wc-block-customer-account__dropdown-divider {
+			background: var(--wc-customer-account-dropdown-border-color);
+			height: 1px;
+		}
+
+		.wc-block-customer-account__dropdown-item {
+			color: inherit;
+			display: block;
+			font-size: var(--wp--preset--font-size--small);
+			line-height: 1.5;
+			padding: calc(var(--wc-customer-account-dropdown-padding-block) / 2)
+				var(--wc-customer-account-dropdown-padding-block);
+			text-decoration: none;
+			white-space: nowrap;
+
+			&:only-child {
+				padding: var(--wc-customer-account-dropdown-padding-block);
+			}
+
+			&:hover,
+			&:focus {
+				background: var(--wc-customer-account-dropdown-item-hover-bg);
+				text-decoration: none;
+			}
+
+			&:focus-visible {
+				outline: 2px solid currentColor;
+				outline-offset: -2px;
+			}
 		}
 	}
 }
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts
index fe5e88b9ba2..7b9b7b75553 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/customer-account/types.ts
@@ -1,6 +1,7 @@
 export interface Attributes {
 	className?: string;
 	displayStyle: DisplayStyle;
+	hasDropdownNavigation?: boolean;
 	iconStyle: IconStyle;
 	iconClass: string;
 }
diff --git a/plugins/woocommerce/client/blocks/assets/js/icons/index.js b/plugins/woocommerce/client/blocks/assets/js/icons/index.js
index cfd63540290..6702fa1e334 100644
--- a/plugins/woocommerce/client/blocks/assets/js/icons/index.js
+++ b/plugins/woocommerce/client/blocks/assets/js/icons/index.js
@@ -2,6 +2,7 @@ export { default as Alert } from './library/alert';
 export { default as bag } from './library/bag';
 export { default as bagAlt } from './library/bag-alt';
 export { default as barcode } from './library/barcode';
+export { default as caret } from './library/caret';
 export { default as cart } from './library/cart';
 export { default as cartOutline } from './library/cart-outline';
 export { default as checkMark } from './library/check-mark';
diff --git a/plugins/woocommerce/client/blocks/assets/js/icons/library/caret.tsx b/plugins/woocommerce/client/blocks/assets/js/icons/library/caret.tsx
new file mode 100644
index 00000000000..b7e7cc31946
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/assets/js/icons/library/caret.tsx
@@ -0,0 +1,18 @@
+/**
+ * External dependencies
+ */
+import { Path, SVG } from '@wordpress/primitives';
+
+const caret = (
+	<SVG viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
+		<Path
+			d="M1 1L5 5L9 1"
+			stroke="currentColor"
+			strokeWidth="1.5"
+			strokeLinecap="round"
+			strokeLinejoin="round"
+		/>
+	</SVG>
+);
+
+export default caret;
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php b/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php
index 29b49118fba..55ebef2a9e9 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/CustomerAccount.php
@@ -10,6 +10,7 @@ use Automattic\WooCommerce\Blocks\Utils\BlockHooksTrait;
  */
 class CustomerAccount extends AbstractBlock {
 	use BlockHooksTrait;
+	use EnableBlockJsonAssetsTrait;

 	const TEXT_ONLY    = 'text_only';
 	const ICON_ONLY    = 'icon_only';
@@ -121,10 +122,191 @@ class CustomerAccount extends AbstractBlock {
 	 */
 	protected function render( $attributes, $content, $block ) {
 		$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
+		$has_myaccount_page = get_option( 'woocommerce_myaccount_page_id' );
+		$account_link       = $has_myaccount_page ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
+		$has_dropdown       = ! empty( $attributes['hasDropdownNavigation'] ) && is_user_logged_in() && $has_myaccount_page;

-		$account_link = get_option( 'woocommerce_myaccount_page_id' ) ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
+		$aria_label   = self::ICON_ONLY === $attributes['displayStyle'] ? ' aria-label="' . esc_attr( $this->render_label() ) . '"' : '';
+		$label_markup = self::ICON_ONLY === $attributes['displayStyle'] ? '' : '<span class="label">' . wp_kses( $this->render_label(), array() ) . '</span>';
+
+		if ( ! $has_dropdown ) {
+			return $this->render_link( $attributes, $classes_and_styles, $account_link, $aria_label, $label_markup );
+		}
+
+		return $this->render_dropdown( $attributes, $classes_and_styles, $aria_label, $label_markup );
+	}
+
+	/**
+	 * Render the block as a simple link (default behavior).
+	 *
+	 * @param array  $attributes        Block attributes.
+	 * @param array  $classes_and_styles Classes and styles from block attributes.
+	 * @param string $account_link      URL to link to.
+	 * @param string $aria_label        Pre-computed aria-label attribute string.
+	 * @param string $label_markup      Pre-computed label HTML markup.
+	 *
+	 * @return string Rendered block output.
+	 */
+	private function render_link( $attributes, $classes_and_styles, $account_link, $aria_label, $label_markup ) {
+		$allowed_svg = $this->get_allowed_svg();
+
+		ob_start();
+		?>
+		<div
+			class="wp-block-woocommerce-customer-account <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
+			style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
+		>
+			<a
+				class="wc-block-customer-account__link"
+				href="<?php echo esc_url( $account_link ); ?>"
+				<?php echo $aria_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+			>
+				<?php echo wp_kses( $this->render_icon( $attributes ), $allowed_svg ); ?>
+				<?php echo $label_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+			</a>
+		</div>
+		<?php
+		return (string) ob_get_clean();
+	}
+
+	/**
+	 * Render the block as a dropdown navigation.
+	 *
+	 * @param array  $attributes        Block attributes.
+	 * @param array  $classes_and_styles Classes and styles from block attributes.
+	 * @param string $aria_label        Pre-computed aria-label attribute string.
+	 * @param string $label_markup      Pre-computed label HTML markup.
+	 *
+	 * @return string Rendered block output.
+	 */
+	private function render_dropdown( $attributes, $classes_and_styles, $aria_label, $label_markup ) {
+		$allowed_svg = $this->get_allowed_svg();
+
+		$context = array(
+			'isDropdownOpen' => false,
+			'showAbove'      => false,
+			'alignRight'     => false,
+		);
+
+		$menu_items    = wc_get_account_menu_items();
+		$dropdown_html = $this->render_dropdown_menu( $menu_items );
+
+		ob_start();
+		?>
+		<div
+			class="wp-block-woocommerce-customer-account wc-block-customer-account--has-dropdown <?php echo esc_attr( $classes_and_styles['classes'] ); ?>"
+			style="<?php echo esc_attr( $classes_and_styles['styles'] ); ?>"
+			data-wp-interactive="woocommerce/customer-account/private"
+			<?php echo wp_interactivity_data_wp_context( $context ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+			data-wp-class--wc-block-customer-account--align-right="context.alignRight"
+			data-wp-class--wc-block-customer-account--is-dropdown-open="context.isDropdownOpen"
+			data-wp-class--wc-block-customer-account--show-above="context.showAbove"
+			data-wp-on--focusout="actions.handleFocusOut"
+			data-wp-on-document--click="actions.handleDocumentClick"
+			data-wp-on-document--keydown="actions.handleKeydown"
+		>
+			<button
+				type="button"
+				class="wc-block-customer-account__toggle"
+				<?php echo $aria_label; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+				aria-haspopup="true"
+				data-wp-bind--aria-expanded="context.isDropdownOpen"
+				data-wp-on--click="actions.toggleDropdown"
+			>
+				<?php echo wp_kses( $this->render_icon( $attributes ), $allowed_svg ); ?>
+				<?php echo $label_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+				<?php echo $this->render_caret_icon(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+			</button>
+			<nav
+				class="wc-block-customer-account__dropdown"
+				aria-label="<?php echo esc_attr__( 'Account navigation', 'woocommerce' ); ?>"
+				data-wp-bind--hidden="!context.isDropdownOpen"
+			>
+				<?php echo $dropdown_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
+			</nav>
+		</div>
+		<?php
+		return (string) ob_get_clean();
+	}
+
+	/**
+	 * Render the dropdown menu content with three sections.
+	 *
+	 * @param array $menu_items Account menu items from wc_get_account_menu_items().
+	 *
+	 * @return string Rendered dropdown menu HTML.
+	 */
+	private function render_dropdown_menu( $menu_items ) {
+		$sections = array();
+
+		if ( isset( $menu_items['dashboard'] ) ) {
+			$sections[] = $this->render_section( array( 'dashboard' => $menu_items['dashboard'] ) );
+		}
+
+		$nav_items = array_diff_key(
+			$menu_items,
+			array_flip( array( 'dashboard', 'customer-logout' ) )
+		);
+		if ( ! empty( $nav_items ) ) {
+			$sections[] = $this->render_section( $nav_items );
+		}
+
+		if ( isset( $menu_items['customer-logout'] ) ) {
+			$sections[] = $this->render_section( array( 'customer-logout' => $menu_items['customer-logout'] ) );
+		}
+
+		return implode( '<div class="wc-block-customer-account__dropdown-divider"></div>', $sections );
+	}
+
+	/**
+	 * Render a dropdown section wrapping one or more menu items.
+	 *
+	 * @param array $items Associative array of endpoint => label pairs.
+	 *
+	 * @return string Rendered section HTML.
+	 */
+	private function render_section( $items ) {
+		$output = '<div class="wc-block-customer-account__dropdown-section">';
+		foreach ( $items as $endpoint => $label ) {
+			$output .= $this->render_menu_item( $endpoint, $label );
+		}
+		$output .= '</div>';
+		return $output;
+	}
+
+	/**
+	 * Render a single dropdown menu item.
+	 *
+	 * @param string $endpoint The account endpoint key.
+	 * @param string $label    The menu item label.
+	 *
+	 * @return string Rendered menu item HTML.
+	 */
+	private function render_menu_item( $endpoint, $label ) {
+		$url = wc_get_account_endpoint_url( $endpoint );
+		return '<a href="' . esc_url( $url ) . '" class="wc-block-customer-account__dropdown-item">'
+			. esc_html( $label )
+			. '</a>';
+	}
+
+	/**
+	 * Render the caret/chevron icon for the dropdown toggle.
+	 *
+	 * @return string SVG markup for the caret icon.
+	 */
+	private function render_caret_icon() {
+		return '<svg class="wc-block-customer-account__caret" width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg">
+			<path d="M1 1L5 5L9 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+		</svg>';
+	}

-		$allowed_svg = array(
+	/**
+	 * Get the allowed SVG tags and attributes for wp_kses.
+	 *
+	 * @return array Allowed SVG elements and attributes.
+	 */
+	private function get_allowed_svg() {
+		return array(
 			'svg'    => array(
 				'class'   => true,
 				'xmlns'   => true,
@@ -147,17 +329,6 @@ class CustomerAccount extends AbstractBlock {
 				'fill'         => true,
 			),
 		);
-
-		// Only provide aria-label if the display style is icon only.
-		$aria_label = self::ICON_ONLY === $attributes['displayStyle'] ? 'aria-label="' . esc_attr( $this->render_label() ) . '"' : '';
-
-		$label_markup = self::ICON_ONLY === $attributes['displayStyle'] ? '' : '<span class="label">' . wp_kses( $this->render_label(), array() ) . '</span>';
-
-		return "<div class='wp-block-woocommerce-customer-account " . esc_attr( $classes_and_styles['classes'] ) . "' style='" . esc_attr( $classes_and_styles['styles'] ) . "'>
-			<a " . $aria_label . " href='" . esc_attr( $account_link ) . "'>
-				" . wp_kses( $this->render_icon( $attributes ), $allowed_svg ) . $label_markup . '
-			</a>
-		</div>';
 	}

 	/**
@@ -220,15 +391,4 @@ class CustomerAccount extends AbstractBlock {
 			? __( 'My Account', 'woocommerce' )
 			: __( 'Login', 'woocommerce' );
 	}
-
-	/**
-	 * Get the frontend script handle for this block type.
-	 *
-	 * @param string $key Data to get, or default to everything.
-	 *
-	 * @return null This block has no frontend script.
-	 */
-	protected function get_block_type_script( $key = null ) {
-		return null;
-	}
 }