Commit 988906a8ed5 for woocommerce

commit 988906a8ed58490e0e2a8a5d071e7afd8c098af3
Author: Pavel Dohnal <pavel.dohnal@automattic.com>
Date:   Thu Apr 30 15:10:10 2026 +0200

    Add RTL support to email renderer (#64500)

    * Add RTL direction support to email renderer

    * Add changelog entry for email renderer RTL support

    * Fix email renderer RTL compatibility issues

    * Refine RTL renderer test assertions

    * Fix RTL quote borders and HTML attributes

    * Use render context for email attributes

    * Restore email render context after rendering

diff --git a/packages/php/email-editor/changelog/add-email-renderer-rtl-direction b/packages/php/email-editor/changelog/add-email-renderer-rtl-direction
new file mode 100644
index 00000000000..4c514a817a0
--- /dev/null
+++ b/packages/php/email-editor/changelog/add-email-renderer-rtl-direction
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Add RTL direction support to the PHP email renderer.
diff --git a/packages/php/email-editor/docs/rendering.md b/packages/php/email-editor/docs/rendering.md
index b1edd184bb3..703bc2f80cb 100644
--- a/packages/php/email-editor/docs/rendering.md
+++ b/packages/php/email-editor/docs/rendering.md
@@ -11,6 +11,7 @@ The email rendering system includes **Core Blocks Integration** that provides de
 -   [Renderer Classes](#renderer-classes)
     -   [Renderer](#renderer)
     -   [Content_Renderer](#content_renderer)
+-   [Rendering Direction](#rendering-direction)
 -   [Core Blocks Integration](#core-blocks-integration)
 -   [Table Wrapper Helper](#table-wrapper-helper)
 -   [Styles Helper](#styles-helper)
@@ -184,6 +185,26 @@ $html        = $result['html'];
 $styles      = $result['styles'];
 ```

+## Rendering Direction
+
+Full email rendering resolves text direction once per render and shares it with the template shell, preprocessors, and block renderers.
+
+Integrations can pass an optional boolean `is_rtl` value through the `woocommerce_email_editor_rendering_email_context` filter:
+
+```php
+add_filter(
+    'woocommerce_email_editor_rendering_email_context',
+    function ( array $context ): array {
+        $context['is_rtl'] = true;
+        return $context;
+    }
+);
+```
+
+When `is_rtl` is explicitly `true`, the rendered email uses RTL direction. When it is explicitly `false`, the rendered email uses LTR direction even if the `$language` argument is an RTL language. Non-boolean values are ignored.
+
+When `is_rtl` is absent, `Renderer::render()` falls back to the `$language` argument using a conservative RTL primary-language allow-list, then defaults to LTR when the language is empty or not recognized. Direct `Content_Renderer` callers do not have a language argument, so they use explicit filtered `is_rtl` when present and otherwise default to LTR.
+
 ## Core Blocks Integration

 The package provides specialized renderers for the most commonly used WordPress core blocks, with plans to eventually cover all core blocks. These individual block renderers are located in the [packages/php/email-editor/src/Integrations/Core/Renderer/Blocks](https://github.com/woocommerce/woocommerce/tree/trunk/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks) directory.
@@ -259,20 +280,20 @@ $table_html = Table_Wrapper_Helper::render_table_wrapper(

 ```html
 <table
-    border="0"
-    cellpadding="0"
-    cellspacing="0"
-    role="presentation"
-    width="100%"
-    style="max-width: 600px;"
+	border="0"
+	cellpadding="0"
+	cellspacing="0"
+	role="presentation"
+	width="100%"
+	style="max-width: 600px;"
 >
-    <tbody>
-        <tr style="background-color: #f0f0f0;">
-            <td align="center" style="padding: 20px;">
-                <p>Email content here</p>
-            </td>
-        </tr>
-    </tbody>
+	<tbody>
+		<tr style="background-color: #f0f0f0;">
+			<td align="center" style="padding: 20px;">
+				<p>Email content here</p>
+			</td>
+		</tr>
+	</tbody>
 </table>
 ```

diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Layout/class-flex-layout-renderer.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Layout/class-flex-layout-renderer.php
index 639f47341b8..632c58b023f 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Layout/class-flex-layout-renderer.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Layout/class-flex-layout-renderer.php
@@ -28,7 +28,7 @@ class Flex_Layout_Renderer {
 		$flex_gap_number = Styles_Helper::parse_value( $flex_gap );

 		$margin_top = $parsed_block['email_attrs']['margin-top'] ?? '0px';
-		$justify    = $parsed_block['attrs']['layout']['justifyContent'] ?? 'left';
+		$justify    = $rendering_context->resolve_text_align( $parsed_block['attrs']['layout']['justifyContent'] ?? null );
 		$styles     = wp_style_engine_get_styles( $parsed_block['attrs']['style'] ?? array() )['css'] ?? '';
 		$styles    .= 'margin-top: ' . $margin_top . ';';
 		$styles    .= 'text-align: ' . $justify;
@@ -49,7 +49,7 @@ class Flex_Layout_Renderer {
 				$styles['width'] = $block['email_attrs']['layout_width'];
 			}
 			if ( $key > 0 ) {
-				$styles['padding-left'] = $flex_gap;
+				$styles[ 'padding-' . $rendering_context->get_start_side() ] = $flex_gap;
 			}
 			$output_html .= '<td class="layout-flex-item" style="' . esc_attr( \WP_Style_Engine::compile_css( $styles, '' ) ) . '">' . render_block( $block ) . '</td>';
 		}
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php
index a322d16cc5a..626bd5285ec 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-blocks-width-preprocessor.php
@@ -9,7 +9,6 @@ declare(strict_types = 1);
 namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;

 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preset_Variable_Resolver;
-
 /**
  * This class sets the width of the blocks based on the layout width or column count.
  * The final width in pixels is stored in the email_attrs array because we would like to avoid changing the original attributes.
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
index d6a81965ad8..62cb7e6dcdb 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/class-spacing-preprocessor.php
@@ -9,12 +9,13 @@ declare(strict_types = 1);
 namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;

 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preset_Variable_Resolver;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;

 /**
  * This preprocessor is responsible for setting default spacing values for blocks.
  * In the early development phase, we are setting only margin-top for blocks that are not first or last in the columns block.
  */
-class Spacing_Preprocessor implements Preprocessor {
+class Spacing_Preprocessor implements Context_Aware_Preprocessor {
 	/**
 	 * Cached post-content block names to avoid repeated apply_filters calls.
 	 *
@@ -31,10 +32,33 @@ class Spacing_Preprocessor implements Preprocessor {
 	 * @return array
 	 */
 	public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
+		return $this->preprocess_with_context( $parsed_blocks, $layout, $styles );
+	}
+
+	/**
+	 * Preprocesses the parsed blocks with rendering context.
+	 *
+	 * @param array                  $parsed_blocks Parsed blocks.
+	 * @param array                  $layout Layout.
+	 * @param array                  $styles Styles.
+	 * @param Rendering_Context|null $rendering_context Rendering context.
+	 * @return array
+	 */
+	public function preprocess_with_context( array $parsed_blocks, array $layout, array $styles, ?Rendering_Context $rendering_context = null ): array {
 		$root_padding      = $this->get_root_padding( $styles );
 		$container_padding = $styles['__container_padding'] ?? array();
 		$variables_map     = $styles['__variables_map'] ?? array();
-		$parsed_blocks     = $this->add_block_gaps( $parsed_blocks, $styles['spacing']['blockGap'] ?? '', null, $root_padding, false, $container_padding, $variables_map );
+		$gap_padding_side  = $rendering_context && $rendering_context->is_rtl() ? 'padding-right' : 'padding-left';
+		$parsed_blocks     = $this->add_block_gaps(
+			$parsed_blocks,
+			$styles['spacing']['blockGap'] ?? '',
+			null,
+			$root_padding,
+			false,
+			$container_padding,
+			$variables_map,
+			$gap_padding_side
+		);
 		return $parsed_blocks;
 	}

@@ -114,9 +138,10 @@ class Spacing_Preprocessor implements Preprocessor {
 	 * @param bool       $apply_root_padding Whether this block should receive root padding (delegated by parent container).
 	 * @param array      $container_padding Container horizontal padding with 'left' and 'right' keys.
 	 * @param array      $variables_map Map of CSS variable names to resolved values for preset resolution.
+	 * @param string     $gap_padding_side Physical padding side for generated column gaps.
 	 * @return array
 	 */
-	private function add_block_gaps( array $parsed_blocks, string $gap = '', $parent_block = null, array $root_padding = array(), bool $apply_root_padding = false, array $container_padding = array(), array $variables_map = array() ): array {
+	private function add_block_gaps( array $parsed_blocks, string $gap = '', $parent_block = null, array $root_padding = array(), bool $apply_root_padding = false, array $container_padding = array(), array $variables_map = array(), string $gap_padding_side = 'padding-left' ): array {
 		foreach ( $parsed_blocks as $key => $block ) {
 			$block_name        = $block['blockName'] ?? '';
 			$parent_block_name = $parent_block['blockName'] ?? '';
@@ -132,11 +157,11 @@ class Spacing_Preprocessor implements Preprocessor {
 				$block['email_attrs']['margin-top'] = $gap;
 			}

-			// Handle horizontal gap for columns: apply padding-left to column children (except the first).
+			// Handle horizontal gap for columns: apply physical padding to column children (except the first).
 			if ( 'core/columns' === $parent_block_name && 0 !== $key && null !== $parent_block ) {
 				$columns_gap = $this->get_columns_block_gap( $parent_block, $gap );
 				if ( $columns_gap ) {
-					$block['email_attrs']['padding-left'] = $columns_gap;
+					$block['email_attrs'][ $gap_padding_side ] = $columns_gap;
 				}
 			}

@@ -210,7 +235,7 @@ class Spacing_Preprocessor implements Preprocessor {
 				unset( $block['email_attrs']['root-padding-left'], $block['email_attrs']['root-padding-right'] );
 			}

-			$block['innerBlocks']  = $this->add_block_gaps( $block['innerBlocks'] ?? array(), $gap, $block, $root_padding, $children_apply, $children_container_pad, $variables_map );
+			$block['innerBlocks']  = $this->add_block_gaps( $block['innerBlocks'] ?? array(), $gap, $block, $root_padding, $children_apply, $children_container_pad, $variables_map, $gap_padding_side );
 			$parsed_blocks[ $key ] = $block;
 		}

diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/interface-context-aware-preprocessor.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/interface-context-aware-preprocessor.php
new file mode 100644
index 00000000000..a42e2c5a86a
--- /dev/null
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/Preprocessors/interface-context-aware-preprocessor.php
@@ -0,0 +1,27 @@
+<?php
+/**
+ * This file is part of the WooCommerce Email Editor package
+ *
+ * @package Automattic\WooCommerce\EmailEditor
+ */
+
+declare(strict_types = 1);
+namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors;
+
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
+
+/**
+ * Optional interface for preprocessors that need rendering context.
+ */
+interface Context_Aware_Preprocessor extends Preprocessor {
+	/**
+	 * Method to preprocess the content before rendering with context.
+	 *
+	 * @param array                                                                                                               $parsed_blocks Parsed blocks of the email.
+	 * @param array{contentSize: string, wideSize?: string, allowEditing?: bool, allowCustomContentAndWideSize?: bool}            $layout Layout of the email.
+	 * @param array{spacing: array{padding: array{bottom: string, left?: string, right?: string, top: string}, blockGap: string}} $styles Styles of the email.
+	 * @param Rendering_Context|null                                                                                              $rendering_context Rendering context.
+	 * @return array
+	 */
+	public function preprocess_with_context( array $parsed_blocks, array $layout, array $styles, ?Rendering_Context $rendering_context = null ): array;
+}
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php
index a334324799c..cc190c8018b 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-content-renderer.php
@@ -125,6 +125,13 @@ class Content_Renderer {
 	 */
 	private Css_Inliner $css_inliner;

+	/**
+	 * Render-scoped context shared across all blocks in the current content render.
+	 *
+	 * @var Rendering_Context|null
+	 */
+	private ?Rendering_Context $rendering_context = null;
+
 	/**
 	 * Content_Renderer constructor.
 	 *
@@ -171,6 +178,35 @@ class Content_Renderer {
 		}
 	}

+	/**
+	 * Set a rendering context resolved by the full email renderer.
+	 *
+	 * @param Rendering_Context $rendering_context Rendering context.
+	 * @return void
+	 */
+	public function set_rendering_context( Rendering_Context $rendering_context ): void {
+		$this->rendering_context = $rendering_context;
+	}
+
+	/**
+	 * Get the current rendering context without creating a fallback context.
+	 *
+	 * @return Rendering_Context|null
+	 */
+	public function get_current_rendering_context(): ?Rendering_Context {
+		return $this->rendering_context;
+	}
+
+	/**
+	 * Restore a previously active rendering context.
+	 *
+	 * @param Rendering_Context|null $rendering_context Rendering context.
+	 * @return void
+	 */
+	public function restore_rendering_context( ?Rendering_Context $rendering_context ): void {
+		$this->rendering_context = $rendering_context;
+	}
+
 	/**
 	 * Render the content with inlined CSS styles.
 	 *
@@ -196,6 +232,10 @@ class Content_Renderer {
 	 * @return array{html: string, styles: string} Rendered HTML and collected CSS.
 	 */
 	public function render_without_css_inline( WP_Post $post, WP_Block_Template $template ): array {
+		if ( null === $this->rendering_context ) {
+			$this->rendering_context = $this->create_rendering_context( null, $post, $template );
+		}
+
 		$this->set_template_globals( $post, $template );
 		$this->initialize();
 		try {
@@ -260,7 +300,7 @@ class Content_Renderer {
 			}
 		}

-		$result = $this->process_manager->preprocess( $parsed_blocks, $layout, $styles );
+		$result = $this->process_manager->preprocess( $parsed_blocks, $layout, $styles, $this->get_rendering_context() );

 		// After the first pass: find the post-content block's width and container padding.
 		if ( null === $this->post_content_width ) {
@@ -347,29 +387,7 @@ class Content_Renderer {
 	 * @return string
 	 */
 	public function render_block( string $block_content, array $parsed_block ): string {
-		/**
-		 * Filter the email-specific context data passed to block renderers.
-		 *
-		 * This allows email sending systems to provide context data such as user ID,
-		 * email address, order information, etc., that can be used by blocks during rendering.
-		 *
-		 * Blocks that need cart product information can derive it from the user_id or recipient_email
-		 * using CartCheckoutUtils::get_cart_product_ids_for_user().
-		 *
-		 * @since 1.9.0
-		 *
-		 * @param array $email_context {
-		 *     Email-specific context data.
-		 *
-		 *     @type int    $user_id         The ID of the user receiving the email.
-		 *     @type string $recipient_email The recipient's email address.
-		 *     @type int    $order_id        The order ID (for order-related emails).
-		 *     @type string $email_type      The type of email being rendered.
-		 * }
-		 */
-		$email_context = apply_filters( 'woocommerce_email_editor_rendering_email_context', array() );
-
-		$context = new Rendering_Context( $this->theme_controller->get_theme(), $email_context );
+		$context = $this->get_rendering_context();

 		$block_type = $this->block_type_registry->get_registered( $parsed_block['blockName'] );
 		$result     = null;
@@ -439,7 +457,7 @@ class Content_Renderer {
 		}

 		$table_attrs = array(
-			'align' => 'left',
+			'align' => $this->get_rendering_context()->get_default_text_align(),
 			'width' => '100%',
 		);

@@ -508,6 +526,7 @@ class Content_Renderer {

 		$this->post_content_width = null;
 		$this->container_padding  = array();
+		$this->rendering_context  = null;

 		// Restore the original core/post-content render callback.
 		// Note: We always restore it, even if it was null originally.
@@ -526,6 +545,58 @@ class Content_Renderer {
 		$post                         = $this->backup_post;  // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restoring of the post.
 	}

+	/**
+	 * Get the current rendering context, lazily creating one for direct render_block() usage.
+	 *
+	 * @return Rendering_Context
+	 */
+	private function get_rendering_context(): Rendering_Context {
+		if ( null === $this->rendering_context ) {
+			$this->rendering_context = $this->create_rendering_context();
+		}
+		return $this->rendering_context;
+	}
+
+	/**
+	 * Create a rendering context from filtered email context.
+	 *
+	 * @param string|null            $language Optional email language.
+	 * @param WP_Post|null           $post Optional email post.
+	 * @param WP_Block_Template|null $template Optional block template.
+	 * @return Rendering_Context
+	 */
+	public function create_rendering_context( ?string $language = null, ?WP_Post $post = null, ?WP_Block_Template $template = null ): Rendering_Context {
+		/**
+		 * Filter the email-specific context data passed to block renderers.
+		 *
+		 * This allows email sending systems to provide context data such as user ID,
+		 * email address, order information, etc., that can be used by blocks during rendering.
+		 *
+		 * Blocks that need cart product information can derive it from the user_id or recipient_email
+		 * using CartCheckoutUtils::get_cart_product_ids_for_user().
+		 *
+		 * @since 1.9.0
+		 *
+		 * @param array $email_context {
+		 *     Email-specific context data.
+		 *
+		 *     @type int    $user_id         The ID of the user receiving the email.
+		 *     @type string $recipient_email The recipient's email address.
+		 *     @type int    $order_id        The order ID (for order-related emails).
+		 *     @type string $email_type      The type of email being rendered.
+		 *     @type bool   $is_rtl          Optional. Whether this email render should use RTL direction.
+		 * }
+		 * @param WP_Post|null           $post     Email post being rendered.
+		 * @param WP_Block_Template|null $template Block template being rendered.
+		 */
+		$email_context = apply_filters( 'woocommerce_email_editor_rendering_email_context', array(), $post, $template );
+		if ( ! is_array( $email_context ) ) {
+			$email_context = array();
+		}
+
+		return new Rendering_Context( $this->theme_controller->get_theme(), $email_context, $language );
+	}
+
 	/**
 	 * Collects CSS for the rendered content without inlining it.
 	 *
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php
index 699ceb0655e..974e8aff686 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-process-manager.php
@@ -13,6 +13,7 @@ use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Postproce
 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Postprocessors\Variables_Postprocessor;
 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Blocks_Width_Preprocessor;
 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Cleanup_Preprocessor;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Context_Aware_Preprocessor;
 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Preprocessor;
 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Spacing_Preprocessor;
 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Preprocessors\Typography_Preprocessor;
@@ -78,11 +79,14 @@ class Process_Manager {
 	 * @param array                                                                                                               $parsed_blocks Parsed blocks.
 	 * @param array{contentSize: string, wideSize?: string, allowEditing?: bool, allowCustomContentAndWideSize?: bool}            $layout Layout.
 	 * @param array{spacing: array{padding: array{bottom: string, left?: string, right?: string, top: string}, blockGap: string}} $styles Styles.
+	 * @param Rendering_Context|null                                                                                              $rendering_context Rendering context.
 	 * @return array
 	 */
-	public function preprocess( array $parsed_blocks, array $layout, array $styles ): array {
+	public function preprocess( array $parsed_blocks, array $layout, array $styles, ?Rendering_Context $rendering_context = null ): array {
 		foreach ( $this->preprocessors as $preprocessor ) {
-			$parsed_blocks = $preprocessor->preprocess( $parsed_blocks, $layout, $styles );
+			$parsed_blocks = $preprocessor instanceof Context_Aware_Preprocessor
+				? $preprocessor->preprocess_with_context( $parsed_blocks, $layout, $styles, $rendering_context )
+				: $preprocessor->preprocess( $parsed_blocks, $layout, $styles );
 		}
 		return $parsed_blocks;
 	}
diff --git a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-rendering-context.php b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-rendering-context.php
index c1f78b0fcb3..c041fb154a1 100644
--- a/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-rendering-context.php
+++ b/packages/php/email-editor/src/Engine/Renderer/ContentRenderer/class-rendering-context.php
@@ -35,15 +35,24 @@ class Rendering_Context {
 	 */
 	private array $email_context;

+	/**
+	 * Email language used for deterministic direction fallback.
+	 *
+	 * @var string|null
+	 */
+	private ?string $language;
+
 	/**
 	 * Rendering_Context constructor.
 	 *
 	 * @param WP_Theme_JSON        $theme_json Theme Json used in the email.
 	 * @param array<string, mixed> $email_context Email-specific context data.
+	 * @param string|null          $language Email language.
 	 */
-	public function __construct( WP_Theme_JSON $theme_json, array $email_context = array() ) {
+	public function __construct( WP_Theme_JSON $theme_json, array $email_context = array(), ?string $language = null ) {
 		$this->theme_json    = $theme_json;
 		$this->email_context = $email_context;
+		$this->language      = $language;
 	}

 	/**
@@ -131,6 +140,15 @@ class Rendering_Context {
 		return $this->email_context;
 	}

+	/**
+	 * Get the email language.
+	 *
+	 * @return string|null
+	 */
+	public function get_language(): ?string {
+		return $this->language;
+	}
+
 	/**
 	 * Get the user ID from the email context.
 	 *
@@ -162,4 +180,123 @@ class Rendering_Context {
 	public function get( string $key, $default_value = null ) {
 		return $this->email_context[ $key ] ?? $default_value;
 	}
+
+	/**
+	 * Whether this render should use right-to-left direction.
+	 *
+	 * @return bool
+	 */
+	public function is_rtl(): bool {
+		if ( isset( $this->email_context['is_rtl'] ) && is_bool( $this->email_context['is_rtl'] ) ) {
+			return $this->email_context['is_rtl'];
+		}
+
+		$primary_language = $this->get_primary_language_subtag( $this->language );
+		if ( null === $primary_language ) {
+			return false;
+		}
+
+		return in_array(
+			$primary_language,
+			array(
+				'ar',
+				'arc',
+				'azb',
+				'ckb',
+				'dv',
+				'fa',
+				'he',
+				'ku',
+				'nqo',
+				'ps',
+				'sd',
+				'ug',
+				'ur',
+				'yi',
+			),
+			true
+		);
+	}
+
+	/**
+	 * Get the HTML/CSS text direction.
+	 *
+	 * @return string
+	 */
+	public function get_text_direction(): string {
+		return $this->is_rtl() ? 'rtl' : 'ltr';
+	}
+
+	/**
+	 * Get the default physical text alignment for this render.
+	 *
+	 * @return string
+	 */
+	public function get_default_text_align(): string {
+		return $this->is_rtl() ? 'right' : 'left';
+	}
+
+	/**
+	 * Get the physical start side.
+	 *
+	 * @return string
+	 */
+	public function get_start_side(): string {
+		return $this->is_rtl() ? 'right' : 'left';
+	}
+
+	/**
+	 * Get the physical end side.
+	 *
+	 * @return string
+	 */
+	public function get_end_side(): string {
+		return $this->is_rtl() ? 'left' : 'right';
+	}
+
+	/**
+	 * Sanitize a text alignment value.
+	 *
+	 * @param mixed $alignment Alignment candidate.
+	 * @return string|null
+	 */
+	public function sanitize_text_align( $alignment ): ?string {
+		if ( ! is_string( $alignment ) ) {
+			return null;
+		}
+		return in_array( $alignment, array( 'left', 'center', 'right' ), true ) ? $alignment : null;
+	}
+
+	/**
+	 * Resolve a text alignment value with direction-aware default.
+	 *
+	 * @param mixed $alignment Alignment candidate.
+	 * @return string
+	 */
+	public function resolve_text_align( $alignment ): string {
+		return $this->sanitize_text_align( $alignment ) ?? $this->get_default_text_align();
+	}
+
+	/**
+	 * Get the primary language subtag from a locale/language value.
+	 *
+	 * @param string|null $language Language or locale.
+	 * @return string|null
+	 */
+	private function get_primary_language_subtag( ?string $language ): ?string {
+		if ( null === $language || '' === trim( $language ) ) {
+			return null;
+		}
+
+		$language = strtolower( str_replace( '_', '-', trim( $language ) ) );
+		$parts    = explode( '-', $language );
+		$primary  = $parts[0] ?? '';
+		$length   = strlen( $primary );
+
+		if ( $length < 2 || $length > 3 ) {
+			return null;
+		}
+
+		return strspn( $primary, 'abcdefghijklmnopqrstuvwxyz' ) === $length ? $primary : null;
+	}
 }
diff --git a/packages/php/email-editor/src/Engine/Renderer/class-renderer.php b/packages/php/email-editor/src/Engine/Renderer/class-renderer.php
index d3baa51225b..0cc03956e86 100644
--- a/packages/php/email-editor/src/Engine/Renderer/class-renderer.php
+++ b/packages/php/email-editor/src/Engine/Renderer/class-renderer.php
@@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer;

 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer;
 use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Process_Manager;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
 use Automattic\WooCommerce\EmailEditor\Engine\Templates\Templates;
 use Automattic\WooCommerce\EmailEditor\Engine\Theme_Controller;
 use Automattic\WooCommerce\EmailEditor\Engine\PersonalizationTags\Personalization_Tags_Registry;
@@ -109,57 +110,74 @@ class Renderer {
 	 * @param string   $template_slug Optional block template slug used for cases when email doesn't have associated template.
 	 * @return array
 	 */
-	public function render( \WP_Post $post, string $subject, string $pre_header, string $language, string $meta_robots = '', string $template_slug = '' ): array { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
+	public function render( \WP_Post $post, string $subject, string $pre_header, string $language, string $meta_robots = '', string $template_slug = '' ): array {
 		if ( ! $template_slug ) {
 			$template_slug = get_page_template_slug( $post ) ? get_page_template_slug( $post ) : 'email-general';
 		}
 		/** @var \WP_Block_Template $template */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- used for phpstan
 		$template = $this->templates->get_block_template( $template_slug );

-		$email_styles   = $this->theme_controller->get_styles();
-		$content_result = $this->content_renderer->render_without_css_inline( $post, $template );
-		$template_html  = $content_result['html'];
-		$content_styles = $content_result['styles'];
-		$layout         = $this->theme_controller->get_layout_settings();
-
-		ob_start();
-		include self::TEMPLATE_FILE;
-		$rendered_template = (string) ob_get_clean();
-
-		$template_styles  =
-		WP_Style_Engine::compile_css(
-			array(
-				'background-color' => $email_styles['color']['background'] ?? 'inherit',
-				'color'            => $email_styles['color']['text'] ?? 'inherit',
-				'padding-top'      => $email_styles['spacing']['padding']['top'] ?? '0px',
-				'padding-bottom'   => $email_styles['spacing']['padding']['bottom'] ?? '0px',
-				'font-family'      => $email_styles['typography']['fontFamily'] ?? 'inherit',
-				'line-height'      => $email_styles['typography']['lineHeight'] ?? '1.5',
-				'font-size'        => $email_styles['typography']['fontSize'] ?? 'inherit',
-			),
-			'body, .email_layout_wrapper'
-		);
-		$template_styles .= '.email_layout_wrapper { box-sizing: border-box;}';
-		$template_styles .= file_get_contents( __DIR__ . '/' . self::TEMPLATE_STYLES_FILE );
-		$template_styles  = wp_strip_all_tags( (string) apply_filters( 'woocommerce_email_renderer_styles', $template_styles, $post ) );
-
-		// Single CSS inlining pass: combine content and template styles, then inline all at once.
-		$all_styles        = '<style>' . $template_styles . $content_styles . '</style>';
-		$rendered_template = $this->inline_css_styles( $all_styles . $rendered_template );
-
-		// Postprocess after CSS inlining (border normalization, CSS variable replacement, etc.).
-		$rendered_template = $this->process_manager->postprocess( $rendered_template );
-
-		// This is a workaround to support link :hover in some clients. Ideally we would remove the ability to set :hover
-		// however this is not possible using the color panel from Gutenberg.
-		if ( isset( $email_styles['elements']['link'][':hover']['color']['text'] ) ) {
-			$rendered_template = str_replace( '<!-- Forced Styles -->', '<style>a:hover { color: ' . esc_attr( $email_styles['elements']['link'][':hover']['color']['text'] ) . ' !important; }</style>', $rendered_template );
+		$previous_rendering_context = $this->content_renderer->get_current_rendering_context();
+		try {
+			$rendering_context = $this->content_renderer->create_rendering_context( $language, $post, $template );
+			$this->content_renderer->set_rendering_context( $rendering_context );
+			$email_styles   = $this->theme_controller->get_styles();
+			$content_result = $this->content_renderer->render_without_css_inline( $post, $template );
+			$template_html  = $content_result['html'];
+			$content_styles = $content_result['styles'];
+			$layout         = $this->theme_controller->get_layout_settings();
+
+			ob_start();
+			include self::TEMPLATE_FILE;
+			$rendered_template = (string) ob_get_clean();
+
+			$template_styles  =
+			WP_Style_Engine::compile_css(
+				array(
+					'background-color' => $email_styles['color']['background'] ?? 'inherit',
+					'color'            => $email_styles['color']['text'] ?? 'inherit',
+					'padding-top'      => $email_styles['spacing']['padding']['top'] ?? '0px',
+					'padding-bottom'   => $email_styles['spacing']['padding']['bottom'] ?? '0px',
+					'font-family'      => $email_styles['typography']['fontFamily'] ?? 'inherit',
+					'line-height'      => $email_styles['typography']['lineHeight'] ?? '1.5',
+					'font-size'        => $email_styles['typography']['fontSize'] ?? 'inherit',
+					'direction'        => $rendering_context->get_text_direction(),
+					'text-align'       => $rendering_context->get_default_text_align(),
+				),
+				'body, .email_layout_wrapper'
+			);
+			$template_styles .= WP_Style_Engine::compile_css(
+				array(
+					'direction'  => $rendering_context->get_text_direction(),
+					'text-align' => $rendering_context->get_default_text_align(),
+				),
+				'.email_content_wrapper, .email_preheader'
+			);
+			$template_styles .= '.email_layout_wrapper { box-sizing: border-box;}';
+			$template_styles .= file_get_contents( __DIR__ . '/' . self::TEMPLATE_STYLES_FILE );
+			$template_styles  = wp_strip_all_tags( (string) apply_filters( 'woocommerce_email_renderer_styles', $template_styles, $post ) );
+
+			// Single CSS inlining pass: combine content and template styles, then inline all at once.
+			$all_styles        = '<style>' . $template_styles . $content_styles . '</style>';
+			$rendered_template = $this->inline_css_styles( $all_styles . $rendered_template );
+
+			// Postprocess after CSS inlining (border normalization, CSS variable replacement, etc.).
+			$rendered_template = $this->process_manager->postprocess( $rendered_template );
+			$rendered_template = $this->apply_html_attributes( $rendered_template, $rendering_context );
+
+			// This is a workaround to support link :hover in some clients. Ideally we would remove the ability to set :hover
+			// however this is not possible using the color panel from Gutenberg.
+			if ( isset( $email_styles['elements']['link'][':hover']['color']['text'] ) ) {
+				$rendered_template = str_replace( '<!-- Forced Styles -->', '<style>a:hover { color: ' . esc_attr( $email_styles['elements']['link'][':hover']['color']['text'] ) . ' !important; }</style>', $rendered_template );
+			}
+
+			return array(
+				'html' => $rendered_template,
+				'text' => $this->render_text_version( $rendered_template ),
+			);
+		} finally {
+			$this->content_renderer->restore_rendering_context( $previous_rendering_context );
 		}
-
-		return array(
-			'html' => $rendered_template,
-			'text' => $this->render_text_version( $rendered_template ),
-		);
 	}

 	/**
@@ -172,6 +190,28 @@ class Renderer {
 		return $this->css_inliner->from_html( $template )->inline_css()->render();
 	}

+	/**
+	 * Apply document-level language and direction attributes after CSS inlining.
+	 *
+	 * @param string            $template HTML template.
+	 * @param Rendering_Context $rendering_context Rendering context.
+	 * @return string
+	 */
+	private function apply_html_attributes( string $template, Rendering_Context $rendering_context ): string {
+		$processor = new \WP_HTML_Tag_Processor( $template );
+		if ( ! $processor->next_tag( array( 'tag_name' => 'html' ) ) ) {
+			return $template;
+		}
+
+		$language = $rendering_context->get_language();
+		if ( $language ) {
+			$processor->set_attribute( 'lang', str_replace( '_', '-', $language ) );
+		}
+		$processor->set_attribute( 'dir', $rendering_context->get_text_direction() );
+
+		return $processor->get_updated_html();
+	}
+
 	/**
 	 * Renders the text version of the email template.
 	 *
diff --git a/packages/php/email-editor/src/Engine/Renderer/template-canvas.css b/packages/php/email-editor/src/Engine/Renderer/template-canvas.css
index 6a77cf3adfd..10dc560ea8e 100644
--- a/packages/php/email-editor/src/Engine/Renderer/template-canvas.css
+++ b/packages/php/email-editor/src/Engine/Renderer/template-canvas.css
@@ -20,13 +20,10 @@ a {
 }

 .email_content_wrapper {
-	direction: ltr;
 	font-size: inherit;
-	text-align: left;
 }

 .email_footer {
-	direction: ltr;
 	text-align: center;
 }

@@ -77,6 +74,7 @@ a {
 	display: block !important;
 	padding-bottom: 8px !important; /* Half of the flex gap between blocks */
 	padding-left: 0 !important;
+	padding-right: 0 !important;
 	width: 100% !important;
 	}

diff --git a/packages/php/email-editor/src/Engine/Renderer/template-canvas.php b/packages/php/email-editor/src/Engine/Renderer/template-canvas.php
index 263149dae2c..6483920a395 100644
--- a/packages/php/email-editor/src/Engine/Renderer/template-canvas.php
+++ b/packages/php/email-editor/src/Engine/Renderer/template-canvas.php
@@ -19,9 +19,13 @@ declare(strict_types = 1);
  * @var string $template_html The email template HTML content
  * @var string $meta_robots Meta robots tag content
  * @var array{contentSize: string} $layout Layout configuration
+ * @var Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context $rendering_context Rendering context
  */
+$language_attributes = get_language_attributes();
+$language_attributes = trim( (string) preg_replace( '/\s?dir=(["\']).*?\1/i', '', $language_attributes ) );
+$language_attributes = trim( $language_attributes . ' dir="' . esc_attr( $rendering_context->get_text_direction() ) . '"' );
 ?><!DOCTYPE html>
-<html <?php language_attributes(); ?>>
+<html <?php echo $language_attributes; ?>>
 <head>
 	<title><?php echo esc_html( $subject ); ?></title>
 	<meta charset="<?php bloginfo( 'charset' ); ?>" />
@@ -32,7 +36,7 @@ declare(strict_types = 1);
 	<?php echo $meta_robots; ?>
 	<!-- Forced Styles -->
 </head>
-<body>
+<body dir="<?php echo esc_attr( $rendering_context->get_text_direction() ); ?>">
 	<!--[if mso | IE]><table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" width="<?php echo esc_attr( $layout['contentSize'] ); ?>" style="width:<?php echo esc_attr( $layout['contentSize'] ); ?>"><tr><td><![endif]-->
 	<div class="email_layout_wrapper" style="max-width: <?php echo esc_attr( $layout['contentSize'] ); ?>">
 		<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-abstract-block-renderer.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-abstract-block-renderer.php
index a6671ac2a3f..9a35e35f507 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-abstract-block-renderer.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-abstract-block-renderer.php
@@ -19,6 +19,13 @@ use WP_Style_Engine;
  * Shared functionality for block renderers.
  */
 abstract class Abstract_Block_Renderer implements Block_Renderer {
+	/**
+	 * Rendering context for calls using the legacy add_spacer() signature.
+	 *
+	 * @var Rendering_Context|null
+	 */
+	protected ?Rendering_Context $current_rendering_context = null;
+
 	/**
 	 * Wrapper for wp_style_engine_get_styles which ensures all values are returned.
 	 *
@@ -78,7 +85,7 @@ abstract class Abstract_Block_Renderer implements Block_Renderer {
 		$gap_style = WP_Style_Engine::compile_css( $margin_top_attrs, '' ) ?? '';

 		$table_attrs = array(
-			'align' => 'left',
+			'align' => $this->current_rendering_context ? $this->current_rendering_context->get_default_text_align() : 'left',
 			'width' => '100%',
 			'style' => $gap_style,
 		);
@@ -92,6 +99,25 @@ abstract class Abstract_Block_Renderer implements Block_Renderer {
 		return Table_Wrapper_Helper::render_outlook_table_wrapper( $div_content, $table_attrs );
 	}

+	/**
+	 * Add a spacer around the block with rendering context.
+	 *
+	 * @param string                 $content The block content.
+	 * @param array                  $email_attrs The email attributes.
+	 * @param Rendering_Context|null $rendering_context Rendering context.
+	 * @return string
+	 */
+	protected function add_spacer_with_context( $content, $email_attrs, ?Rendering_Context $rendering_context = null ): string {
+		$previous_context                = $this->current_rendering_context;
+		$this->current_rendering_context = $rendering_context;
+
+		try {
+			return $this->add_spacer( $content, $email_attrs );
+		} finally {
+			$this->current_rendering_context = $previous_context;
+		}
+	}
+
 	/**
 	 * Render the block.
 	 *
@@ -101,9 +127,10 @@ abstract class Abstract_Block_Renderer implements Block_Renderer {
 	 * @return string
 	 */
 	public function render( string $block_content, array $parsed_block, Rendering_Context $rendering_context ): string {
-		return $this->add_spacer(
+		return $this->add_spacer_with_context(
 			$this->render_content( $block_content, $parsed_block, $rendering_context ),
-			$parsed_block['email_attrs'] ?? array()
+			$parsed_block['email_attrs'] ?? array(),
+			$rendering_context
 		);
 	}

diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-audio.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-audio.php
index b8289d49916..73bbdaf3d07 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-audio.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-audio.php
@@ -51,7 +51,7 @@ class Audio extends Abstract_Block_Renderer {
 			return '';
 		}

-		return $this->add_spacer( $rendered_content, $parsed_block['email_attrs'] ?? array() );
+		return $this->add_spacer_with_context( $rendered_content, $parsed_block['email_attrs'] ?? array(), $rendering_context );
 	}
 	/**
 	 * Renders the audio block content as an audio player for email.
@@ -119,23 +119,28 @@ class Audio extends Abstract_Block_Renderer {
 		$border_color     = '#AAA';
 		$icon_size        = '18px';
 		$font_size        = '14px';
+		$start_side       = $rendering_context->get_start_side();
+		$end_side         = $rendering_context->get_end_side();

 		// Generate the icon content.
 		$icon_content = sprintf(
-			'<a href="%1$s" rel="noopener nofollow" target="_blank" style="padding: 0.25em; padding-left: 17px; display: inline-block; vertical-align: middle;"><img height="%2$s" src="%3$s" style="display:block;margin-right:0;vertical-align:middle;" width="%2$s" alt="%4$s"></a>',
+			'<a href="%1$s" rel="noopener nofollow" target="_blank" style="padding: 0.25em; padding-%5$s: 17px; display: inline-block; vertical-align: middle;"><img height="%2$s" src="%3$s" style="display:block;margin-%6$s:0;vertical-align:middle;" width="%2$s" alt="%4$s"></a>',
 			$audio_url,
 			esc_attr( $icon_size ),
 			esc_url( $icon_image ),
 			// translators: %s is the audio player icon.
-			sprintf( __( '%s icon', 'woocommerce' ), __( 'Audio', 'woocommerce' ) )
+			sprintf( __( '%s icon', 'woocommerce' ), __( 'Audio', 'woocommerce' ) ),
+			esc_attr( $start_side ),
+			esc_attr( $end_side )
 		);
 		$icon_content = Table_Wrapper_Helper::render_table_cell( $icon_content, array( 'style' => sprintf( 'vertical-align:middle;font-size:%s;', $font_size ) ) );

 		// Generate the label content.
 		$label_content    = sprintf(
-			'<a href="%1$s" rel="noopener nofollow" target="_blank" style="text-decoration:none; padding: 0.25em; padding-right: 17px; display: inline-block;"><span style="margin-left:.5em;margin-right:.5em;font-weight:bold"> %2$s </span></a>',
+			'<a href="%1$s" rel="noopener nofollow" target="_blank" style="text-decoration:none; padding: 0.25em; padding-%3$s: 17px; display: inline-block;"><span style="margin-left:.5em;margin-right:.5em;font-weight:bold"> %2$s </span></a>',
 			$audio_url,
-			esc_html( $label )
+			esc_html( $label ),
+			esc_attr( $end_side )
 		);
 		$label_cell_style = sprintf(
 			'vertical-align:middle;font-size:%s;',
@@ -154,7 +159,7 @@ class Audio extends Abstract_Block_Renderer {
 		);

 		$main_table_attrs = array(
-			'align' => 'left',
+			'align' => $rendering_context->get_default_text_align(),
 			'style' => $main_table_styles,
 		);

@@ -173,12 +178,12 @@ class Audio extends Abstract_Block_Renderer {
 		);

 		$cell_attrs = array(
-			'style' => 'min-width: 100%; vertical-align: middle; word-break: break-word; text-align: left;',
+			'style' => 'min-width: 100%; vertical-align: middle; word-break: break-word; text-align: ' . $rendering_context->get_default_text_align() . ';',
 		);

 		$main_wrapper = Table_Wrapper_Helper::render_table_wrapper( $main_table, $table_attrs, $cell_attrs );

-		return Table_Wrapper_Helper::render_outlook_table_wrapper( $main_wrapper, array( 'align' => 'left' ) );
+		return Table_Wrapper_Helper::render_outlook_table_wrapper( $main_wrapper, array( 'align' => $rendering_context->get_default_text_align() ) );
 	}

 	/**
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-buttons.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-buttons.php
index 49c644b62e2..a63c1e2b22f 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-buttons.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-buttons.php
@@ -49,7 +49,7 @@ class Buttons extends Abstract_Block_Renderer {
 		$content     = $this->render_content( $block_content, $parsed_block, $rendering_context );
 		$email_attrs = $parsed_block['email_attrs'] ?? array();
 		unset( $email_attrs['margin-top'] );
-		return $this->add_spacer( $content, $email_attrs );
+		return $this->add_spacer_with_context( $content, $email_attrs, $rendering_context );
 	}

 	/**
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php
index 6a71b96e29d..221c7776f07 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-column.php
@@ -67,7 +67,7 @@ class Column extends Abstract_Block_Renderer {
 		$is_stretched = empty( $block_attributes['verticalAlignment'] ) || 'stretch' === $block_attributes['verticalAlignment'];

 		$padding_styles = Styles_Helper::get_block_styles( $block_attributes, $rendering_context, array( 'padding' ) );
-		$padding_styles = Styles_Helper::extend_block_styles( $padding_styles, array( 'text-align' => 'left' ) );
+		$padding_styles = Styles_Helper::extend_block_styles( $padding_styles, array( 'text-align' => $rendering_context->get_default_text_align() ) );

 		$cell_styles = Styles_Helper::get_block_styles( $block_attributes, $rendering_context, array( 'border', 'background', 'background-color', 'color' ) );
 		$cell_styles = Styles_Helper::extend_block_styles(
@@ -103,16 +103,17 @@ class Column extends Abstract_Block_Renderer {
 		);

 		$inner_cell_attrs = array(
-			'align' => 'left',
+			'align' => $rendering_context->get_default_text_align(),
 			'style' => $padding_styles['css'],
 		);

 		$inner_table = Table_Wrapper_Helper::render_table_wrapper( '{column_content}', $inner_table_attrs, $inner_cell_attrs );

-		// Apply padding-left from email_attrs (set by Spacing_Preprocessor for columns blockGap).
-		$padding_left = $parsed_block['email_attrs']['padding-left'] ?? null;
-		if ( $padding_left ) {
-			$gap_padding_styles = wp_style_engine_get_styles( array( 'spacing' => array( 'padding' => array( 'left' => $padding_left ) ) ) );
+		// Apply physical side padding from email_attrs (set by Spacing_Preprocessor for columns blockGap).
+		$gap_padding_side  = $rendering_context->get_start_side();
+		$gap_padding_value = $parsed_block['email_attrs'][ 'padding-' . $gap_padding_side ] ?? null;
+		if ( $gap_padding_value ) {
+			$gap_padding_styles = wp_style_engine_get_styles( array( 'spacing' => array( 'padding' => array( $gap_padding_side => $gap_padding_value ) ) ) );
 			$wrapper_styles     = Styles_Helper::extend_block_styles( $wrapper_styles, $gap_padding_styles['declarations'] ?? array() );
 		}

diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php
index 95e4cdc860e..7ee99158208 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-columns.php
@@ -19,7 +19,7 @@ use Automattic\WooCommerce\EmailEditor\Integrations\Utils\Styles_Helper;
 class Columns extends Abstract_Block_Renderer {
 	/**
 	 * Renders the block content.
-	 * BlockGap spacing is handled by Spacing_Preprocessor which sets padding-left on column children.
+	 * BlockGap spacing is handled by Spacing_Preprocessor which sets physical padding on column children.
 	 *
 	 * @param string            $block_content Block content.
 	 * @param array             $parsed_block Parsed block.
@@ -58,7 +58,7 @@ class Columns extends Abstract_Block_Renderer {
 			array(
 				'width'           => '100%',
 				'border-collapse' => 'separate',
-				'text-align'      => 'left',
+				'text-align'      => $rendering_context->get_default_text_align(),
 				'background-size' => $columns_styles['declarations']['background-size'] ?? 'cover',
 			)
 		);
@@ -82,7 +82,7 @@ class Columns extends Abstract_Block_Renderer {
 				array(
 					'width'           => '100%',
 					'border-collapse' => 'separate',
-					'text-align'      => 'left',
+					'text-align'      => $rendering_context->get_default_text_align(),
 				)
 			);

diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-embed.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-embed.php
index 14870625bcc..4c8ee64ffb7 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-embed.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-embed.php
@@ -437,9 +437,10 @@ class Embed extends Abstract_Block_Renderer {
 		);

 		// Wrap with spacer if we have email attributes.
-		return $this->add_spacer(
+		return $this->add_spacer_with_context(
 			$link_html,
-			$parsed_block['email_attrs'] ?? array()
+			$parsed_block['email_attrs'] ?? array(),
+			$rendering_context
 		);
 	}

@@ -923,7 +924,7 @@ class Embed extends Abstract_Block_Renderer {
 			$content_parts .= sprintf(
 				'<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="margin-top: 16px;">'
 				. '<tr>'
-				. '<td style="vertical-align: middle; padding-right: 6px;">'
+				. '<td style="vertical-align: middle; padding-' . esc_attr( $rendering_context->get_end_side() ) . ': 6px;">'
 				. '<img src="%s" width="16" height="16" alt="" style="display: block; border-radius: 2px;" />'
 				. '</td>'
 				. '<td style="vertical-align: middle;">%s</td>'
@@ -949,14 +950,15 @@ class Embed extends Abstract_Block_Renderer {
 		$outlook_wrapped = Table_Wrapper_Helper::render_outlook_table_wrapper(
 			$card_html,
 			array(
-				'align' => 'left',
+				'align' => $rendering_context->get_default_text_align(),
 				'width' => '100%',
 			)
 		);

-		return $this->add_spacer(
+		return $this->add_spacer_with_context(
 			$outlook_wrapped,
-			$parsed_block['email_attrs'] ?? array()
+			$parsed_block['email_attrs'] ?? array(),
+			$rendering_context
 		);
 	}

@@ -997,14 +999,15 @@ class Embed extends Abstract_Block_Renderer {
 		$outlook_wrapped = Table_Wrapper_Helper::render_outlook_table_wrapper(
 			$card_html,
 			array(
-				'align' => 'left',
+				'align' => $rendering_context->get_default_text_align(),
 				'width' => '100%',
 			)
 		);

-		return $this->add_spacer(
+		return $this->add_spacer_with_context(
 			$outlook_wrapped,
-			$parsed_block['email_attrs'] ?? array()
+			$parsed_block['email_attrs'] ?? array(),
+			$rendering_context
 		);
 	}
 }
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-fallback.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-fallback.php
index 87860d02d0b..8d01bb2714d 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-fallback.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-fallback.php
@@ -37,7 +37,9 @@ class Fallback extends Abstract_Block_Renderer {
 			'width' => '100%',
 		);

-		$align = $block_attrs['textAlign'] ?? $block_attrs['align'] ?? 'left';
+		$align = $rendering_context->sanitize_text_align( $block_attrs['textAlign'] ?? null )
+			?? $rendering_context->sanitize_text_align( $block_attrs['align'] ?? null )
+			?? $rendering_context->get_default_text_align();

 		$cell_attrs = array(
 			'align' => $align,
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-gallery.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-gallery.php
index c424a3e12a4..6d3ccb5e179 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-gallery.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-gallery.php
@@ -163,7 +163,7 @@ class Gallery extends Abstract_Block_Renderer {
 			array(
 				'width'           => '100%',
 				'border-collapse' => 'collapse',
-				'text-align'      => 'left',
+				'text-align'      => $rendering_context->get_default_text_align(),
 			)
 		);

@@ -171,7 +171,7 @@ class Gallery extends Abstract_Block_Renderer {
 		$table_attrs = array(
 			'class' => 'email-block-gallery ' . Html_Processing_Helper::clean_css_classes( $original_wrapper_classname ),
 			'style' => $block_styles['css'],
-			'align' => 'left',
+			'align' => $rendering_context->get_default_text_align(),
 			'width' => '100%',
 		);

diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php
index 5adf601a91d..d0b515dbcae 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-image.php
@@ -245,7 +245,7 @@ class Image extends Abstract_Block_Renderer {

 		$align  = $parsed_block['attrs']['align'] ?? '';
 		$styles = array(
-			'text-align' => $align ? 'center' : 'left',
+			'text-align' => $align ? 'center' : $rendering_context->get_default_text_align(),
 		);

 		$styles['font-size'] = $parsed_block['email_attrs']['font-size'] ?? $theme_data['styles']['typography']['fontSize'];
@@ -298,11 +298,11 @@ class Image extends Abstract_Block_Renderer {
 		}

 		$styles['width'] = '100%';
-		$align           = $parsed_block['attrs']['align'] ?? 'left';
+		$align           = $parsed_block['attrs']['align'] ?? $rendering_context->get_default_text_align();

 		// Map block alignment to valid HTML/CSS alignment values.
 		// "full" and "wide" are not valid text-align or table align values.
-		$css_align = in_array( $align, array( 'full', 'wide' ), true ) ? 'center' : $align;
+		$css_align = in_array( $align, array( 'full', 'wide' ), true ) ? 'center' : $rendering_context->resolve_text_align( $align );

 		$table_attrs = array(
 			'style' => \WP_Style_Engine::compile_css( $styles, '' ),
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php
index ae414ce2cab..26b39c79d7f 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-list-block.php
@@ -32,7 +32,7 @@ class List_Block extends Abstract_Block_Renderer {
 		$content     = $this->render_content( $block_content, $parsed_block, $rendering_context );
 		$email_attrs = $parsed_block['email_attrs'] ?? array();
 		unset( $email_attrs['margin-top'] );
-		return $this->add_spacer( $content, $email_attrs );
+		return $this->add_spacer_with_context( $content, $email_attrs, $rendering_context );
 	}

 	/**
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-media-text.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-media-text.php
index 247fc73c91f..818d341db3f 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-media-text.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-media-text.php
@@ -79,7 +79,7 @@ class Media_Text extends Abstract_Block_Renderer {
 		$original_wrapper_classname = ( new Dom_Document_Helper( $block_content ) )->get_attribute_value_by_tag_name( 'div', 'class' ) ?? '';

 		// Get layout attributes.
-		$media_position     = $block_attrs['mediaPosition'] ?? 'left';
+		$media_position     = $block_attrs['mediaPosition'] ?? $rendering_context->get_start_side();
 		$vertical_alignment = $this->get_vertical_alignment_from_attributes( $block_attrs );
 		$media_width        = $this->get_media_width_from_attributes( $block_attrs );
 		$text_width         = 100 - $media_width; // Text takes the remaining width.
@@ -96,7 +96,7 @@ class Media_Text extends Abstract_Block_Renderer {
 			array(
 				'width'           => '100%',
 				'border-collapse' => 'collapse',
-				'text-align'      => 'left',
+				'text-align'      => $rendering_context->get_default_text_align(),
 			)
 		);

@@ -104,7 +104,7 @@ class Media_Text extends Abstract_Block_Renderer {
 		$table_attrs = array(
 			'class' => 'email-block-media-text ' . $original_wrapper_classname,
 			'style' => $block_styles['css'],
-			'align' => 'left',
+			'align' => $rendering_context->get_default_text_align(),
 			'width' => '100%',
 		);

diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-quote.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-quote.php
index d9396fce3f2..33eadc746eb 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-quote.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-quote.php
@@ -41,11 +41,69 @@ class Quote extends Abstract_Block_Renderer {

 		return str_replace(
 			array( '{quote_content}', '{citation_content}' ),
-			array( $this->get_inner_content( $block_content ), $citation_content ),
+			array( $this->get_quote_content( $block_content, $parsed_block, $rendering_context ), $citation_content ),
 			$this->get_block_wrapper( $block_content, $parsed_block, $rendering_context )
 		);
 	}

+	/**
+	 * Get quote content with direction-aware default border overrides.
+	 *
+	 * @param string            $block_content Block content.
+	 * @param array             $parsed_block Parsed block.
+	 * @param Rendering_Context $rendering_context Rendering context.
+	 * @return string
+	 */
+	private function get_quote_content( string $block_content, array $parsed_block, Rendering_Context $rendering_context ): string {
+		$quote_content = $this->get_inner_content( $block_content );
+
+		$original_classname = ( new Dom_Document_Helper( $block_content ) )->get_attribute_value_by_tag_name( 'blockquote', 'class' ) ?? '';
+		$block_attributes   = wp_parse_args(
+			$parsed_block['attrs'] ?? array(),
+			array(
+				'style'       => array(),
+				'borderColor' => '',
+			)
+		);
+
+		if ( ! $rendering_context->is_rtl() || $this->has_authored_border( $block_attributes ) ) {
+			return $quote_content;
+		}
+
+		$authored_alignment = $this->get_authored_alignment( $block_attributes, $original_classname );
+		if ( 'left' === $authored_alignment ) {
+			return $quote_content;
+		}
+
+		$processor = new \WP_HTML_Tag_Processor( $quote_content );
+		if ( ! $processor->next_tag( array( 'tag_name' => 'blockquote' ) ) ) {
+			return $quote_content;
+		}
+
+		$style  = $processor->get_attribute( 'style' );
+		$style  = is_string( $style ) ? rtrim( $style, ';' ) . ';' : '';
+		$style .= \WP_Style_Engine::compile_css(
+			'center' === $authored_alignment
+				? array(
+					'border-left-style'  => 'none',
+					'border-left-width'  => '0',
+					'border-right-style' => 'none',
+					'border-right-width' => '0',
+				)
+				: array(
+					'border-left-style'  => 'none',
+					'border-left-width'  => '0',
+					'border-right-color' => 'currentColor',
+					'border-right-style' => 'solid',
+					'border-right-width' => '1px',
+				),
+			''
+		);
+		$processor->set_attribute( 'style', $style );
+
+		return $processor->get_updated_html();
+	}
+
 	/**
 	 * Returns the citation content with a wrapper.
 	 *
@@ -65,13 +123,14 @@ class Quote extends Abstract_Block_Renderer {
 		$citation_styles = Styles_Helper::get_block_styles( $parsed_block['attrs'], $rendering_context, array( 'text-align' ) );
 		$citation_styles = Styles_Helper::extend_block_styles( $citation_styles, array( 'margin' => "{$margin_top} 0px 0px 0px" ) );

-		return $this->add_spacer(
+		return $this->add_spacer_with_context(
 			sprintf(
 				'<p style="%2$s"><cite class="email-block-quote-citation" style="display: block; margin: 0;">%1$s</cite></p>',
 				$citation_content,
 				$citation_styles['css'],
 			),
-			$parsed_block['email_attrs'] ?? array()
+			$parsed_block['email_attrs'] ?? array(),
+			$rendering_context
 		);
 	}

@@ -96,6 +155,28 @@ class Quote extends Abstract_Block_Renderer {

 		// Layout, background, borders need to be on the outer table element.
 		$table_styles = Styles_Helper::get_block_styles( $block_attributes, $rendering_context, array( 'border', 'background', 'background-color', 'color', 'text-align' ) );
+		if ( $rendering_context->is_rtl() && ! $this->has_authored_border( $block_attributes ) ) {
+			$authored_alignment = $this->get_authored_alignment( $block_attributes, $original_classname );
+			$border_width       = array(
+				'left'   => '0 0 0 1px',
+				'center' => '0',
+				'right'  => '0 1px 0 0',
+			)[ $authored_alignment ?? 'right' ];
+			$table_styles       = Styles_Helper::extend_block_styles(
+				$table_styles,
+				array_filter(
+					array(
+						'border-color'  => 'currentColor',
+						'border-style'  => 'solid',
+						'border-width'  => $border_width,
+						'border-inline' => 'center' === $authored_alignment ? '0' : null,
+					),
+					static function ( $value ) {
+						return null !== $value;
+					}
+				)
+			);
+		}
 		$table_styles = Styles_Helper::extend_block_styles(
 			$table_styles,
 			array(
@@ -121,4 +202,43 @@ class Quote extends Abstract_Block_Renderer {

 		return Table_Wrapper_Helper::render_table_wrapper( '{quote_content}{citation_content}', $table_attrs, $cell_attrs );
 	}
+
+	/**
+	 * Get explicit quote alignment when authored.
+	 *
+	 * @param array  $block_attributes Block attributes.
+	 * @param string $original_classname Original quote classes.
+	 * @return string|null
+	 */
+	private function get_authored_alignment( array $block_attributes, string $original_classname ): ?string {
+		foreach ( array( 'textAlign', 'align' ) as $attribute_name ) {
+			$alignment = $block_attributes[ $attribute_name ] ?? null;
+			if ( in_array( $alignment, array( 'left', 'center', 'right' ), true ) ) {
+				return $alignment;
+			}
+		}
+
+		foreach ( wp_parse_list( $original_classname ) as $class_name ) {
+			if ( 0 !== strpos( $class_name, 'has-text-align-' ) ) {
+				continue;
+			}
+
+			$alignment = substr( $class_name, strlen( 'has-text-align-' ) );
+			if ( in_array( $alignment, array( 'left', 'center', 'right' ), true ) ) {
+				return $alignment;
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * Check whether quote border was explicitly authored.
+	 *
+	 * @param array $block_attributes Block attributes.
+	 * @return bool
+	 */
+	private function has_authored_border( array $block_attributes ): bool {
+		return ! empty( $block_attributes['style']['border'] ) || ! empty( $block_attributes['borderColor'] );
+	}
 }
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-social-links.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-social-links.php
index 2ade38cd96f..03310586059 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-social-links.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-social-links.php
@@ -51,7 +51,7 @@ class Social_Links extends Abstract_Block_Renderer {
 		return str_replace(
 			'{social_links_content}',
 			$content,
-			$this->get_block_wrapper( $block_content, $parsed_block )
+			$this->get_block_wrapper( $block_content, $parsed_block, $rendering_context )
 		);
 	}

@@ -212,12 +212,13 @@ class Social_Links extends Abstract_Block_Renderer {
 	/**
 	 * Gets the block wrapper.
 	 *
-	 * @param string $block_content The block content.
-	 * @param array  $parsed_block The parsed block.
+	 * @param string            $block_content The block content.
+	 * @param array             $parsed_block The parsed block.
+	 * @param Rendering_Context $rendering_context Rendering context.
 	 * @return string The block wrapper HTML.
 	 */
-	private function get_block_wrapper( $block_content, $parsed_block ) {
-		$content = $this->adjust_block_content( $block_content, $parsed_block );
+	private function get_block_wrapper( $block_content, $parsed_block, Rendering_Context $rendering_context ) {
+		$content = $this->adjust_block_content( $block_content, $parsed_block, $rendering_context );

 		$table_styles    = $content['table_styles'];
 		$classes         = $content['classes'];
@@ -250,17 +251,17 @@ class Social_Links extends Abstract_Block_Renderer {
 	 * Adjusts the block content.
 	 * Returns css classes and styles compatible with email clients.
 	 *
-	 * @param string $block_content The block content.
-	 * @param array  $parsed_block The parsed block.
+	 * @param string            $block_content The block content.
+	 * @param array             $parsed_block The parsed block.
+	 * @param Rendering_Context $rendering_context Rendering context.
 	 * @return array The adjusted block content.
 	 */
-	private function adjust_block_content( $block_content, $parsed_block ) {
+	private function adjust_block_content( $block_content, $parsed_block, Rendering_Context $rendering_context ) {
 		$block_content    = $this->adjust_style_attribute( $block_content );
 		$block_attributes = wp_parse_args(
 			$parsed_block['attrs'] ?? array(),
 			array(
-				'textAlign' => 'left',
-				'style'     => array(),
+				'style' => array(),
 			)
 		);
 		$html             = new \WP_HTML_Tag_Processor( $block_content );
@@ -293,11 +294,11 @@ class Social_Links extends Abstract_Block_Renderer {
 			'word-break'     => 'break-word',
 		);

-		$styles['text-align'] = 'left';
+		$styles['text-align'] = $rendering_context->get_default_text_align();
 		if ( ! empty( $parsed_block['attrs']['textAlign'] ) ) { // in this case, textAlign needs to be one of 'left', 'center', 'right'.
-			$styles['text-align'] = $parsed_block['attrs']['textAlign'];
-		} elseif ( in_array( $parsed_block['attrs']['align'] ?? null, array( 'left', 'center', 'right' ), true ) ) {
-			$styles['text-align'] = $parsed_block['attrs']['align'];
+			$styles['text-align'] = $rendering_context->resolve_text_align( $parsed_block['attrs']['textAlign'] );
+		} elseif ( null !== $rendering_context->sanitize_text_align( $parsed_block['attrs']['align'] ?? null ) ) {
+			$styles['text-align'] = $rendering_context->resolve_text_align( $parsed_block['attrs']['align'] );
 		}

 		$compiled_styles = $this->compile_css( $block_styles['declarations'], $styles );
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-table.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-table.php
index 03d7b2578db..8398f83f7d4 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-table.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-table.php
@@ -50,8 +50,7 @@ class Table extends Abstract_Block_Renderer {
 		$block_attributes = wp_parse_args(
 			$parsed_block['attrs'] ?? array(),
 			array(
-				'textAlign' => 'left',
-				'style'     => array(),
+				'style' => array(),
 			)
 		);

@@ -95,14 +94,14 @@ class Table extends Abstract_Block_Renderer {
 			$additional_styles['color'] = Html_Processing_Helper::sanitize_color( $color );
 		}

-		$additional_styles['text-align'] = 'left';
+		$additional_styles['text-align'] = $rendering_context->get_default_text_align();
 		if ( ! empty( $parsed_block['attrs']['textAlign'] ) ) { // In this case, textAlign needs to be one of 'left', 'center', 'right'.
-			$text_align = $parsed_block['attrs']['textAlign'];
-			if ( in_array( $text_align, self::VALID_TEXT_ALIGNMENTS, true ) ) {
+			$text_align = $rendering_context->sanitize_text_align( $parsed_block['attrs']['textAlign'] );
+			if ( null !== $text_align ) {
 				$additional_styles['text-align'] = $text_align;
 			}
-		} elseif ( in_array( $parsed_block['attrs']['align'] ?? null, self::VALID_TEXT_ALIGNMENTS, true ) ) {
-			$additional_styles['text-align'] = $parsed_block['attrs']['align'];
+		} elseif ( null !== $rendering_context->sanitize_text_align( $parsed_block['attrs']['align'] ?? null ) ) {
+			$additional_styles['text-align'] = $rendering_context->resolve_text_align( $parsed_block['attrs']['align'] );
 		}

 		$table_styles = Styles_Helper::extend_block_styles( $table_styles, $additional_styles );
@@ -227,7 +226,7 @@ class Table extends Abstract_Block_Renderer {
 				$border_style   = $this->get_custom_border_style( $parsed_block );

 				// Extract cell-specific text alignment.
-				$cell_text_align = $this->get_cell_text_alignment( $html );
+				$cell_text_align = $this->get_cell_text_alignment( $html, $rendering_context );

 				$email_cell_styles = "vertical-align: top; border: {$border_width} {$border_style} {$border_color}; padding: 8px; text-align: {$cell_text_align};";

@@ -345,9 +344,10 @@ class Table extends Abstract_Block_Renderer {
 	 * Get text alignment for a table cell.
 	 *
 	 * @param \WP_HTML_Tag_Processor $html HTML tag processor.
+	 * @param Rendering_Context      $rendering_context Rendering context.
 	 * @return string Text alignment value (left, center, right).
 	 */
-	private function get_cell_text_alignment( \WP_HTML_Tag_Processor $html ): string {
+	private function get_cell_text_alignment( \WP_HTML_Tag_Processor $html, Rendering_Context $rendering_context ): string {
 		// Check for data-align attribute first.
 		$data_align = $html->get_attribute( 'data-align' );
 		if ( $data_align && in_array( $data_align, self::VALID_TEXT_ALIGNMENTS, true ) ) {
@@ -366,8 +366,7 @@ class Table extends Abstract_Block_Renderer {
 			return 'left';
 		}

-		// Default to left alignment.
-		return 'left';
+		return $rendering_context->get_default_text_align();
 	}

 	/**
diff --git a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php
index c2c98c03c4b..6e0f44ebf28 100644
--- a/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php
+++ b/packages/php/email-editor/src/Integrations/Core/Renderer/Blocks/class-text.php
@@ -34,8 +34,7 @@ class Text extends Abstract_Block_Renderer {
 		$block_attributes     = wp_parse_args(
 			$parsed_block['attrs'] ?? array(),
 			array(
-				'textAlign' => 'left',
-				'style'     => array(),
+				'style' => array(),
 			)
 		);
 		$html                 = new \WP_HTML_Tag_Processor( $block_content );
@@ -76,11 +75,11 @@ class Text extends Abstract_Block_Renderer {
 			$additional_styles['color'] = $parsed_block['email_attrs']['color'] ?? $email_styles['color']['text'] ?? '#000000'; // Fallback for the text color.
 		}

-		$additional_styles['text-align'] = 'left';
+		$additional_styles['text-align'] = $rendering_context->get_default_text_align();
 		if ( ! empty( $parsed_block['attrs']['textAlign'] ) ) { // in this case, textAlign needs to be one of 'left', 'center', 'right'.
-			$additional_styles['text-align'] = $parsed_block['attrs']['textAlign'];
-		} elseif ( in_array( $parsed_block['attrs']['align'] ?? null, array( 'left', 'center', 'right' ), true ) ) {
-			$additional_styles['text-align'] = $parsed_block['attrs']['align'];
+			$additional_styles['text-align'] = $rendering_context->resolve_text_align( $parsed_block['attrs']['textAlign'] );
+		} elseif ( null !== $rendering_context->sanitize_text_align( $parsed_block['attrs']['align'] ?? null ) ) {
+			$additional_styles['text-align'] = $rendering_context->resolve_text_align( $parsed_block['attrs']['align'] );
 		} elseif ( null !== $alignment_from_class ) {
 			$additional_styles['text-align'] = $alignment_from_class;
 		}
diff --git a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-button.php b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-button.php
index cf3a588fc17..d8ceceffa71 100644
--- a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-button.php
+++ b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-button.php
@@ -88,11 +88,11 @@ class Product_Button extends Abstract_Product_Block_Renderer {
 			}
 		}

-		$block_attributes = array_replace_recursive(
+		$block_attributes              = array_replace_recursive(
 			array(
 				'textColor'       => '#ffffff',
 				'backgroundColor' => '#000000',
-				'textAlign'       => 'left',
+				'textAlign'       => $rendering_context->get_default_text_align(),
 				'width'           => '',
 				'style'           => array(
 					'typography' => array(
@@ -109,6 +109,7 @@ class Product_Button extends Abstract_Product_Block_Renderer {
 			),
 			$parsed_block['attrs'] ?? array()
 		);
+		$block_attributes['textAlign'] = $rendering_context->resolve_text_align( $block_attributes['textAlign'] ?? null );

 		$wrapper_styles = $this->get_wrapper_styles( $block_attributes, $rendering_context );
 		$button_styles  = $this->get_button_styles( $block_attributes, $rendering_context );
diff --git a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-collection.php b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-collection.php
index 393165d8a42..e17fb9d2a73 100644
--- a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-collection.php
+++ b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-collection.php
@@ -121,9 +121,10 @@ class Product_Collection extends Abstract_Product_Block_Renderer {
 				if ( $index > 0 && ! isset( $email_attrs['margin-top'] ) ) {
 					$email_attrs['margin-top'] = $block_gap;
 				}
-				$content .= $this->add_spacer(
+				$content .= $this->add_spacer_with_context(
 					$this->render_product_content( $product, $inner_block, $collection_type ),
-					$email_attrs
+					$email_attrs,
+					$rendering_context
 				);
 				++$index;
 			}
@@ -132,9 +133,10 @@ class Product_Collection extends Abstract_Product_Block_Renderer {

 		// Two-column layout using HTML tables for email compatibility.
 		// Wrap with add_spacer to match single-column spacing behavior.
-		return $this->add_spacer(
+		return $this->add_spacer_with_context(
 			$this->render_two_column_grid( $products, $inner_block, $collection_type, $rendering_context, $block_gap ),
-			$inner_block['email_attrs'] ?? array()
+			$inner_block['email_attrs'] ?? array(),
+			$rendering_context
 		);
 	}

@@ -173,7 +175,7 @@ class Product_Collection extends Abstract_Product_Block_Renderer {

 			foreach ( $row_products as $col_index => $product ) {
 				$cell_style  = 'width: 50%; vertical-align: top; padding: 0;';
-				$cell_style .= 0 === $col_index ? ' padding-right: 10px;' : ' padding-left: 10px;';
+				$cell_style .= 0 === $col_index ? ' padding-' . $rendering_context->get_end_side() . ': 10px;' : ' padding-' . $rendering_context->get_start_side() . ': 10px;';

 				$content .= sprintf(
 					'<td style="%s">%s</td>',
@@ -184,7 +186,7 @@ class Product_Collection extends Abstract_Product_Block_Renderer {

 			// If odd number of products, add empty cell to complete the row.
 			if ( 1 === count( $row_products ) ) {
-				$content .= '<td style="width: 50%; vertical-align: top; padding: 0; padding-left: 10px;"></td>';
+				$content .= '<td style="width: 50%; vertical-align: top; padding: 0; padding-' . esc_attr( $rendering_context->get_start_side() ) . ': 10px;"></td>';
 			}

 			$content .= '</tr>';
diff --git a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-image.php b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-image.php
index 7dc847f1af0..49ff30e3cfe 100644
--- a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-image.php
+++ b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-image.php
@@ -69,7 +69,7 @@ class Product_Image extends Abstract_Product_Block_Renderer {
 	private function process_inner_blocks( array $parsed_block, \WC_Product $product, Rendering_Context $rendering_context ): array {
 		$badges          = '';
 		$other_content   = '';
-		$badge_alignment = 'left';
+		$badge_alignment = $rendering_context->get_default_text_align();

 		if ( ! empty( $parsed_block['innerBlocks'] ) ) {
 			foreach ( $parsed_block['innerBlocks'] as $inner_block ) {
@@ -78,7 +78,7 @@ class Product_Image extends Abstract_Product_Block_Renderer {

 				if ( 'woocommerce/product-sale-badge' === $inner_block['blockName'] ) {
 					$badges         .= $this->render_overlay_badge( $inner_block, $product, $rendering_context );
-					$badge_alignment = $inner_block['attrs']['align'] ?? 'left';
+					$badge_alignment = $rendering_context->resolve_text_align( $inner_block['attrs']['align'] ?? null );
 				} else {
 					$other_content .= render_block( $inner_block );
 				}
@@ -436,7 +436,7 @@ class Product_Image extends Abstract_Product_Block_Renderer {

 		// Map block alignment to valid HTML/CSS alignment values.
 		// "full" is not a valid text-align or table align value.
-		$css_align = $is_full ? 'center' : ( $align ? $align : 'left' );
+		$css_align = $is_full ? 'center' : $rendering_context->resolve_text_align( $align ? $align : null );

 		$wrapper_styles = array(
 			'border-collapse' => 'separate',
diff --git a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-price.php b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-price.php
index 272c4a83134..9196d03268b 100644
--- a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-price.php
+++ b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-price.php
@@ -34,7 +34,7 @@ class Product_Price extends Abstract_Product_Block_Renderer {

 		$price_content = $this->generate_price_html( $product, $attributes, $rendering_context );

-		return $this->apply_email_wrapper( $price_content, $parsed_block );
+		return $this->apply_email_wrapper( $price_content, $parsed_block, $rendering_context );
 	}

 	/**
@@ -190,12 +190,13 @@ class Product_Price extends Abstract_Product_Block_Renderer {
 	/**
 	 * Apply email-compatible table wrapper.
 	 *
-	 * @param string $price_html Price HTML.
-	 * @param array  $parsed_block Parsed block.
+	 * @param string            $price_html Price HTML.
+	 * @param array             $parsed_block Parsed block.
+	 * @param Rendering_Context $rendering_context Rendering context.
 	 * @return string
 	 */
-	private function apply_email_wrapper( string $price_html, array $parsed_block ): string {
-		$align = $parsed_block['attrs']['textAlign'] ?? 'left';
+	private function apply_email_wrapper( string $price_html, array $parsed_block, Rendering_Context $rendering_context ): string {
+		$align = $rendering_context->resolve_text_align( $parsed_block['attrs']['textAlign'] ?? null );

 		$wrapper_styles = array(
 			'border-collapse' => 'collapse',
diff --git a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-sale-badge.php b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-sale-badge.php
index c3631f759f5..436a2ed579b 100644
--- a/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-sale-badge.php
+++ b/packages/php/email-editor/src/Integrations/WooCommerce/Renderer/Blocks/class-product-sale-badge.php
@@ -49,7 +49,7 @@ class Product_Sale_Badge extends Abstract_Product_Block_Renderer {
 		$sale_text = apply_filters( 'woocommerce_sale_badge_text', __( 'Sale', 'woocommerce' ), $product );

 		$badge_html = $this->build_badge_html( $sale_text, $attributes, $rendering_context );
-		return $this->apply_email_wrapper( $badge_html, $parsed_block );
+		return $this->apply_email_wrapper( $badge_html, $parsed_block, $rendering_context );
 	}

 	/**
@@ -61,7 +61,7 @@ class Product_Sale_Badge extends Abstract_Product_Block_Renderer {
 	 * @return string
 	 */
 	private function build_badge_html( string $sale_text, array $attributes, Rendering_Context $rendering_context ): string {
-		$align = $attributes['align'] ?? 'left';
+		$align = $rendering_context->resolve_text_align( $attributes['align'] ?? null );

 		$position_style = $this->get_position_style( $align );

@@ -137,12 +137,13 @@ class Product_Sale_Badge extends Abstract_Product_Block_Renderer {
 	/**
 	 * Apply email-compatible table wrapper.
 	 *
-	 * @param string $badge_html Badge HTML.
-	 * @param array  $parsed_block Parsed block.
+	 * @param string            $badge_html Badge HTML.
+	 * @param array             $parsed_block Parsed block.
+	 * @param Rendering_Context $rendering_context Rendering context.
 	 * @return string
 	 */
-	private function apply_email_wrapper( string $badge_html, array $parsed_block ): string {
-		$align = $parsed_block['attrs']['align'] ?? 'left';
+	private function apply_email_wrapper( string $badge_html, array $parsed_block, Rendering_Context $rendering_context ): string {
+		$align = $rendering_context->resolve_text_align( $parsed_block['attrs']['align'] ?? null );

 		$wrapper_styles = array(
 			'border-collapse' => 'collapse',
diff --git a/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Content_Renderer_Test.php b/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Content_Renderer_Test.php
index f5eee42d9f1..1589740fbdf 100644
--- a/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Content_Renderer_Test.php
+++ b/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Content_Renderer_Test.php
@@ -90,6 +90,118 @@ class Content_Renderer_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'Hello!', $result['html'] );
 	}

+	/**
+	 * Test render_without_css_inline applies email context once per content render.
+	 */
+	public function testRenderWithoutCssInlineAppliesEmailContextOnce(): void {
+		$email_post_id = $this->factory->post->create(
+			array(
+				'post_content' => '<!-- wp:test/context-block /--><!-- wp:test/context-block /-->',
+			)
+		);
+		$this->assertIsInt( $email_post_id );
+		$email_post = get_post( $email_post_id );
+		$this->assertInstanceOf( \WP_Post::class, $email_post );
+
+		$seen_contexts = array();
+		register_block_type(
+			'test/context-block',
+			array(
+				'render_email_callback' => function ( $block_content, $parsed_block, Rendering_Context $context ) use ( &$seen_contexts ) {
+					$seen_contexts[] = array(
+						'direction' => $context->get_text_direction(),
+						'custom'    => $context->get( 'custom_key' ),
+					);
+					return '<p>' . esc_html( $context->get_text_direction() ) . '</p>';
+				},
+			)
+		);
+		$filter_calls   = 0;
+		$context_filter = function () use ( &$filter_calls ) {
+			++$filter_calls;
+			return array(
+				'is_rtl'     => true,
+				'custom_key' => 'preserved',
+			);
+		};
+		add_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+
+		try {
+			$template          = new \WP_Block_Template();
+			$template->id      = 'template-id';
+			$template->content = '<!-- wp:post-content /-->';
+			$this->renderer->render_without_css_inline( $email_post, $template );
+		} finally {
+			remove_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+			\WP_Block_Type_Registry::get_instance()->unregister( 'test/context-block' );
+		}
+
+		$this->assertSame( 1, $filter_calls );
+		$this->assertCount( 2, $seen_contexts );
+		$this->assertSame(
+			array(
+				array(
+					'direction' => 'rtl',
+					'custom'    => 'preserved',
+				),
+				array(
+					'direction' => 'rtl',
+					'custom'    => 'preserved',
+				),
+			),
+			$seen_contexts
+		);
+	}
+
+	/**
+	 * Test render applies email context once per content render.
+	 */
+	public function testRenderAppliesEmailContextOnce(): void {
+		$filter_calls   = 0;
+		$context_filter = function () use ( &$filter_calls ) {
+			++$filter_calls;
+			return array( 'is_rtl' => true );
+		};
+		add_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+
+		try {
+			$template          = new \WP_Block_Template();
+			$template->id      = 'template-id';
+			$template->content = '<!-- wp:post-content /-->';
+			$this->renderer->render( $this->email_post, $template );
+		} finally {
+			remove_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+		}
+
+		$this->assertSame( 1, $filter_calls );
+	}
+
+	/**
+	 * Test render_without_css_inline passes current post and template to email context filter.
+	 */
+	public function testRenderWithoutCssInlinePassesPostAndTemplateToContextFilter(): void {
+		$template          = new \WP_Block_Template();
+		$template->id      = 'template-id';
+		$template->content = '<!-- wp:post-content /-->';
+
+		$filter_calls   = 0;
+		$context_filter = function ( array $email_context, ?\WP_Post $post, ?\WP_Block_Template $received_template ) use ( &$filter_calls, $template ): array {
+			++$filter_calls;
+			$this->assertSame( $this->email_post->ID, $post instanceof \WP_Post ? $post->ID : null );
+			$this->assertSame( $template, $received_template );
+			return $email_context;
+		};
+		add_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter, 10, 3 );
+
+		try {
+			$this->renderer->render_without_css_inline( $this->email_post, $template );
+		} finally {
+			remove_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+		}
+
+		$this->assertSame( 1, $filter_calls );
+	}
+
 	/**
 	 * Test it collects content styles without inlining them.
 	 */
diff --git a/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Layout/Flex_Layout_Renderer_Test.php b/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Layout/Flex_Layout_Renderer_Test.php
index 01857f96882..75943295df1 100644
--- a/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Layout/Flex_Layout_Renderer_Test.php
+++ b/packages/php/email-editor/tests/integration/Engine/Renderer/ContentRenderer/Layout/Flex_Layout_Renderer_Test.php
@@ -96,6 +96,34 @@ class Flex_Layout_Renderer_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'align="center"', $output );
 	}

+	/**
+	 * Test it uses RTL defaults when no justification is authored.
+	 */
+	public function testItUsesRtlDefaultJustificationAndGapSide(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$parsed_block     = array(
+			'innerBlocks' => array(
+				array(
+					'blockName' => 'dummy/block',
+					'innerHTML' => 'Dummy 1',
+				),
+				array(
+					'blockName' => 'dummy/block',
+					'innerHTML' => 'Dummy 2',
+				),
+			),
+			'email_attrs' => array(),
+		);
+
+		$output = $this->renderer->render_inner_blocks_in_layout( $parsed_block, $rtl_context );
+
+		$this->assertStringContainsString( 'text-align: right', $output );
+		$this->assertStringContainsString( 'align="right"', $output );
+		$this->assertStringContainsString( 'padding-right', $output );
+		$this->assertStringNotContainsString( 'padding-left', $output );
+	}
+
 	/**
 	 * Test it applies margin-top from email_attrs on the inner div for Gmail compatibility.
 	 */
diff --git a/packages/php/email-editor/tests/integration/Engine/Renderer/Renderer_Test.php b/packages/php/email-editor/tests/integration/Engine/Renderer/Renderer_Test.php
index 8103e7b32d0..0be742522c8 100644
--- a/packages/php/email-editor/tests/integration/Engine/Renderer/Renderer_Test.php
+++ b/packages/php/email-editor/tests/integration/Engine/Renderer/Renderer_Test.php
@@ -107,6 +107,131 @@ class Renderer_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'Hello!', $rendered['text'] );
 	}

+	/**
+	 * Test it renders LTR direction by default.
+	 */
+	public function testItRendersLtrDirectionByDefault(): void {
+		$rendered = $this->renderer->render( $this->email_post, 'Subject', '', 'en_US' );
+
+		$html_tag_position = strpos( $rendered['html'], '<html ' );
+		$head_tag_position = strpos( $rendered['html'], '<head>' );
+
+		$this->assertNotFalse( $html_tag_position );
+		$this->assertNotFalse( $head_tag_position );
+		$this->assertLessThan( $head_tag_position, $html_tag_position );
+		$html_opening_tag = substr( $rendered['html'], $html_tag_position, $head_tag_position - $html_tag_position );
+		$this->assertStringContainsString( 'lang="', $html_opening_tag );
+		$this->assertStringContainsString( 'dir="ltr"', $html_opening_tag );
+		$this->assertStringContainsString( 'dir="ltr"', $rendered['html'] );
+		$this->assertStringContainsString( 'direction: ltr', $rendered['html'] );
+		$this->assertStringContainsString( 'text-align: left', $rendered['html'] );
+	}
+
+	/**
+	 * Test it renders RTL direction from language fallback.
+	 */
+	public function testItRendersRtlDirectionFromLanguage(): void {
+		$rendered = $this->renderer->render( $this->email_post, 'Subject', '', 'ar_SA' );
+
+		$this->assertStringContainsString( 'lang="ar-SA"', $rendered['html'] );
+		$this->assertStringContainsString( 'dir="rtl"', $rendered['html'] );
+		$this->assertStringContainsString( 'direction: rtl', $rendered['html'] );
+		$this->assertStringContainsString( 'text-align: right', $rendered['html'] );
+	}
+
+	/**
+	 * Test explicit LTR context takes precedence over RTL language.
+	 */
+	public function testExplicitLtrContextTakesPrecedenceOverRtlLanguage(): void {
+		$context_filter = function () {
+			return array( 'is_rtl' => false );
+		};
+		add_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+
+		try {
+			$rendered = $this->renderer->render( $this->email_post, 'Subject', '', 'ar' );
+		} finally {
+			remove_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+		}
+
+		$this->assertStringContainsString( 'dir="ltr"', $rendered['html'] );
+		$this->assertStringContainsString( 'direction: ltr', $rendered['html'] );
+	}
+
+	/**
+	 * Test explicit RTL context takes precedence over LTR language.
+	 */
+	public function testExplicitRtlContextTakesPrecedenceOverLtrLanguage(): void {
+		$context_filter = function () {
+			return array( 'is_rtl' => true );
+		};
+		add_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+
+		try {
+			$rendered = $this->renderer->render( $this->email_post, 'Subject', '', 'en_US' );
+		} finally {
+			remove_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+		}
+
+		$this->assertStringContainsString( 'dir="rtl"', $rendered['html'] );
+		$this->assertStringContainsString( 'direction: rtl', $rendered['html'] );
+	}
+
+	/**
+	 * Test it applies the rendering email context filter once per full render.
+	 */
+	public function testItAppliesRenderingContextFilterOncePerRender(): void {
+		$filter_calls   = 0;
+		$context_filter = function () use ( &$filter_calls ) {
+			++$filter_calls;
+			return array( 'is_rtl' => true );
+		};
+		add_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+
+		try {
+			$rendered = $this->renderer->render( $this->email_post, 'Subject', '', 'en_US' );
+		} finally {
+			remove_filter( 'woocommerce_email_editor_rendering_email_context', $context_filter );
+		}
+
+		$this->assertSame( 1, $filter_calls );
+		$this->assertStringContainsString( 'dir="rtl"', $rendered['html'] );
+	}
+
+	/**
+	 * Test render restores a previously active rendering context.
+	 */
+	public function testRenderRestoresPreviousRenderingContext(): void {
+		$content_renderer_property = new \ReflectionProperty( Renderer::class, 'content_renderer' );
+		$content_renderer_property->setAccessible( true );
+		$content_renderer = $content_renderer_property->getValue( $this->renderer );
+		$this->assertInstanceOf( \Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Content_Renderer::class, $content_renderer );
+
+		$previous_context = new \Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context(
+			$this->createMock( \WP_Theme_JSON::class ),
+			array( 'is_rtl' => true ),
+			'ar_SA'
+		);
+		$content_renderer->set_rendering_context( $previous_context );
+
+		try {
+			$this->renderer->render( $this->email_post, 'Subject', '', 'en_US' );
+			$this->assertSame( $previous_context, $content_renderer->get_current_rendering_context() );
+		} finally {
+			$content_renderer->restore_rendering_context( null );
+		}
+	}
+
+	/**
+	 * Test base template CSS resets both physical flex padding sides on mobile.
+	 */
+	public function testTemplateCssResetsBothFlexGapSides(): void {
+		$template_styles = (string) file_get_contents( __DIR__ . '/../../../../src/Engine/Renderer/template-canvas.css' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local fixture file.
+
+		$this->assertStringContainsString( 'padding-left: 0 !important;', $template_styles );
+		$this->assertStringContainsString( 'padding-right: 0 !important;', $template_styles );
+	}
+
 	/**
 	 * Test it inlines styles.
 	 */
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Buttons_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Buttons_Test.php
index 1bfd5b16e61..64a1a158658 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Buttons_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Buttons_Test.php
@@ -69,6 +69,34 @@ class Buttons_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringNotContainsString( 'email-block-layout" style="margin-top', $rendered );
 	}

+	/**
+	 * Test outer spacer uses RTL alignment.
+	 */
+	public function testItUsesRtlOuterSpacerAlignment(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$parsed_block     = array(
+			'blockName'   => 'core/buttons',
+			'attrs'       => array(),
+			'innerBlocks' => array(
+				array(
+					'blockName' => 'dummy/block',
+					'innerHTML' => 'Click me',
+				),
+			),
+			'email_attrs' => array(),
+		);
+
+		$rendered = $this->buttons_renderer->render( '', $parsed_block, $rtl_context );
+
+		$right_aligned_table_position = strpos( $rendered, 'align="right"' );
+		$layout_class_position        = strpos( $rendered, 'email-block-layout' );
+
+		$this->assertNotFalse( $right_aligned_table_position );
+		$this->assertNotFalse( $layout_class_position );
+		$this->assertGreaterThan( $right_aligned_table_position, $layout_class_position );
+	}
+
 	/**
 	 * Render a dummy block.
 	 *
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Core_Renderers_Rtl_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Core_Renderers_Rtl_Test.php
new file mode 100644
index 00000000000..7df1307a1e7
--- /dev/null
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Core_Renderers_Rtl_Test.php
@@ -0,0 +1,242 @@
+<?php
+/**
+ * This file is part of the WooCommerce Email Editor package.
+ *
+ * @package Automattic\WooCommerce\EmailEditor
+ */
+
+declare( strict_types = 1 );
+namespace Automattic\WooCommerce\EmailEditor\Integrations\Core\Renderer\Blocks;
+
+use Automattic\WooCommerce\EmailEditor\Engine\Email_Editor;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
+use Automattic\WooCommerce\EmailEditor\Engine\Theme_Controller;
+
+/**
+ * Integration tests for RTL defaults in core renderers.
+ */
+class Core_Renderers_Rtl_Test extends \Email_Editor_Integration_Test_Case {
+	/**
+	 * RTL rendering context.
+	 *
+	 * @var Rendering_Context
+	 */
+	private Rendering_Context $rtl_context;
+
+	/**
+	 * Set up before each test.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		$this->di_container->get( Email_Editor::class )->initialize();
+		$theme_controller  = $this->di_container->get( Theme_Controller::class );
+		$this->rtl_context = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+	}
+
+	/**
+	 * Test fallback renderer uses RTL default alignment and sanitizes invalid values.
+	 */
+	public function testFallbackUsesRtlDefaultAlignment(): void {
+		$renderer = new Fallback();
+		$rendered = $renderer->render(
+			'<div>Fallback</div>',
+			array(
+				'blockName' => 'test/unknown',
+				'attrs'     => array( 'align' => 'start' ),
+			),
+			$this->rtl_context
+		);
+
+		$this->assertStringContainsString( 'align="right"', $rendered );
+	}
+
+	/**
+	 * Test columns and column renderers use RTL default alignment and gap side.
+	 */
+	public function testColumnsAndColumnUseRtlDefaults(): void {
+		$columns = new Columns();
+		$column  = new Column();
+
+		$columns_rendered = $columns->render(
+			'<div class="wp-block-columns"><div class="wp-block-column">Column</div></div>',
+			array(
+				'blockName'   => 'core/columns',
+				'attrs'       => array(),
+				'email_attrs' => array(),
+			),
+			$this->rtl_context
+		);
+		$column_rendered  = $column->render(
+			'<div class="wp-block-column">Column</div>',
+			array(
+				'blockName'   => 'core/column',
+				'attrs'       => array(),
+				'email_attrs' => array( 'padding-right' => '24px' ),
+			),
+			$this->rtl_context
+		);
+
+		$this->assertStringContainsString( 'text-align:right;', $columns_rendered );
+		$this->assertStringContainsString( 'align="right"', $column_rendered );
+		$this->assertStringContainsString( 'padding-right:24px;', $column_rendered );
+	}
+
+	/**
+	 * Test media-text keeps explicit position and uses RTL alignment defaults.
+	 */
+	public function testMediaTextUsesRtlAlignmentAndPreservesExplicitPosition(): void {
+		$renderer = new Media_Text();
+		$content  = '<div class="wp-block-media-text"><figure class="wp-block-media-text__media"><img src="https://example.com/image.jpg" alt=""></figure></div>';
+		$block    = array(
+			'blockName'   => 'core/media-text',
+			'attrs'       => array( 'mediaPosition' => 'left' ),
+			'innerHTML'   => $content,
+			'innerBlocks' => array(
+				array(
+					'blockName'    => 'core/paragraph',
+					'attrs'        => array(),
+					'innerHTML'    => '<p>Text</p>',
+					'innerContent' => array( '<p>Text</p>' ),
+				),
+			),
+			'email_attrs' => array(),
+		);
+
+		$rendered = $renderer->render( $content, $block, $this->rtl_context );
+
+		$media_position = strpos( $rendered, 'https://example.com/image.jpg' );
+		$text_position  = strpos( $rendered, '<p>Text</p>' );
+
+		$this->assertNotFalse( $media_position );
+		$this->assertNotFalse( $text_position );
+		$this->assertLessThan( $text_position, $media_position );
+		$this->assertStringContainsString( 'text-align:right;', $rendered );
+		$this->assertStringContainsString( 'align="right"', $rendered );
+	}
+
+	/**
+	 * Test image renderer uses RTL default alignment.
+	 */
+	public function testImageUsesRtlDefaultAlignment(): void {
+		$renderer = new Image();
+		$content  = '<figure class="wp-block-image"><img src="https://example.com/image.jpg" alt=""></figure>';
+		$rendered = $renderer->render(
+			$content,
+			array(
+				'blockName'   => 'core/image',
+				'attrs'       => array( 'width' => '100px' ),
+				'email_attrs' => array( 'width' => '600px' ),
+			),
+			$this->rtl_context
+		);
+
+		$this->assertStringContainsString( 'align="right"', $rendered );
+	}
+
+	/**
+	 * Test social links renderer uses RTL default alignment.
+	 */
+	public function testSocialLinksUseRtlDefaultAlignment(): void {
+		$renderer = new Social_Links();
+		$content  = '<ul class="wp-block-social-links"></ul>';
+		$rendered = $renderer->render(
+			$content,
+			array(
+				'blockName'   => 'core/social-links',
+				'attrs'       => array(),
+				'innerBlocks' => array(
+					array(
+						'blockName' => 'core/social-link',
+						'attrs'     => array(
+							'service' => 'wordpress',
+							'url'     => 'https://example.com',
+						),
+					),
+				),
+			),
+			$this->rtl_context
+		);
+
+		$this->assertStringContainsString( 'align="right"', $rendered );
+	}
+
+	/**
+	 * Test audio renderer uses RTL physical sides.
+	 */
+	public function testAudioUsesRtlPhysicalSides(): void {
+		$renderer = new Audio();
+		$content  = '<figure class="wp-block-audio"><audio controls src="data:audio/mpeg;base64,AAAA"></audio></figure>';
+		$rendered = $renderer->render(
+			$content,
+			array(
+				'blockName' => 'core/audio',
+				'attrs'     => array( 'src' => 'data:audio/mpeg;base64,AAAA' ),
+			),
+			$this->rtl_context
+		);
+
+		$this->assertStringContainsString( 'align="right"', $rendered );
+		$this->assertStringContainsString( 'padding-right: 17px', $rendered );
+		$this->assertStringContainsString( 'padding-left: 17px', $rendered );
+		$this->assertOuterSpacerAligned( $rendered, 'right' );
+	}
+
+	/**
+	 * Test gallery renderer uses RTL default wrapper alignment.
+	 */
+	public function testGalleryUsesRtlDefaultAlignment(): void {
+		$renderer = new Gallery();
+		$content  = '<figure class="wp-block-gallery"><figure class="wp-block-image"><img src="https://example.com/image.jpg" alt=""></figure></figure>';
+		$rendered = $renderer->render(
+			$content,
+			array(
+				'blockName'   => 'core/gallery',
+				'attrs'       => array(),
+				'innerBlocks' => array(
+					array(
+						'blockName' => 'core/image',
+						'innerHTML' => '<figure><img src="https://example.com/image.jpg" alt=""></figure>',
+					),
+				),
+			),
+			$this->rtl_context
+		);
+
+		$this->assertStringContainsString( 'text-align:right;', $rendered );
+		$this->assertStringContainsString( 'align="right"', $rendered );
+	}
+
+	/**
+	 * Test embed fallback uses RTL wrapper alignment.
+	 */
+	public function testEmbedFallbackUsesRtlAlignment(): void {
+		$renderer = new Embed();
+		$content  = '<figure class="wp-block-embed"><div class="wp-block-embed__wrapper">https://example.com</div></figure>';
+		$rendered = $renderer->render(
+			$content,
+			array(
+				'blockName' => 'core/embed',
+				'attrs'     => array( 'url' => 'https://example.com' ),
+			),
+			$this->rtl_context
+		);
+
+		$this->assertStringContainsString( 'align="right"', $rendered );
+		$this->assertOuterSpacerAligned( $rendered, 'right' );
+	}
+
+	/**
+	 * Assert that the outer spacer wrapper uses the expected alignment.
+	 *
+	 * @param string $rendered Rendered HTML.
+	 * @param string $alignment Expected alignment.
+	 */
+	private function assertOuterSpacerAligned( string $rendered, string $alignment ): void {
+		$alignment_position = strpos( $rendered, 'align="' . $alignment . '"' );
+		$layout_position    = strpos( $rendered, 'email-block-layout' );
+
+		$this->assertNotFalse( $alignment_position );
+		$this->assertNotFalse( $layout_position );
+		$this->assertLessThan( $layout_position, $alignment_position );
+	}
+}
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/List_Block_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/List_Block_Test.php
index 86cea83b0b6..b388eb98dde 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/List_Block_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/List_Block_Test.php
@@ -114,6 +114,23 @@ class List_Block_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringNotContainsString( 'email-block-layout" style="margin-top', $rendered );
 	}

+	/**
+	 * Test outer spacer uses RTL alignment.
+	 */
+	public function testItUsesRtlOuterSpacerAlignment(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+
+		$rendered = $this->list_renderer->render( '<ul><li>Item 1</li></ul>', $this->parsed_list, $rtl_context );
+
+		$right_aligned_table_position = strpos( $rendered, 'align="right"' );
+		$layout_class_position        = strpos( $rendered, 'email-block-layout' );
+
+		$this->assertNotFalse( $right_aligned_table_position );
+		$this->assertNotFalse( $layout_class_position );
+		$this->assertGreaterThan( $right_aligned_table_position, $layout_class_position );
+	}
+
 	/**
 	 * Test it preserves custom set colors
 	 */
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php
index 8962fb082a6..f48bc024238 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Paragraph_Test.php
@@ -74,6 +74,34 @@ class Paragraph_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'align="left"', $rendered ); // Check the default align.
 	}

+	/**
+	 * Test it uses RTL default alignment when alignment is absent.
+	 */
+	public function testItRendersContentWithRtlDefaultAlignment(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+
+		$rendered = $this->paragraph_renderer->render( '<p>Lorem Ipsum</p>', $this->parsed_paragraph, $rtl_context );
+
+		$this->assertStringContainsString( 'text-align:right;', $rendered );
+		$this->assertStringContainsString( 'align="right"', $rendered );
+	}
+
+	/**
+	 * Test it preserves explicit left alignment in RTL.
+	 */
+	public function testItPreservesExplicitLeftAlignmentInRtl(): void {
+		$theme_controller                   = $this->di_container->get( Theme_Controller::class );
+		$rtl_context                        = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$parsed_paragraph                   = $this->parsed_paragraph;
+		$parsed_paragraph['attrs']['align'] = 'left';
+
+		$rendered = $this->paragraph_renderer->render( '<p>Lorem Ipsum</p>', $parsed_paragraph, $rtl_context );
+
+		$this->assertStringContainsString( 'text-align:left;', $rendered );
+		$this->assertStringContainsString( 'align="left"', $rendered );
+	}
+
 	/**
 	 * Test it renders content with padding
 	 */
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Quote_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Quote_Test.php
index eb30c6f4d93..77aebcc9ec4 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Quote_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Quote_Test.php
@@ -76,6 +76,70 @@ class Quote_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'Quote content', $rendered );
 	}

+	/**
+	 * Test it moves default quote border to the right in RTL.
+	 */
+	public function testItUsesRightDefaultBorderInRtl(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$content          = '<blockquote class="wp-block-quote" style="border-color: currentColor; border-width: 0 0 0 1px; border-left-style: solid;"><p>Quote content</p></blockquote>';
+
+		$rendered = $this->quote_renderer->render( $content, $this->parsed_quote, $rtl_context );
+
+		$this->assertStringContainsString( 'border-width:0 1px 0 0;', $rendered );
+		$this->assertStringContainsString( 'border-style:solid;', $rendered );
+		$this->assertStringContainsString( 'border-left-style:none;', $rendered );
+		$this->assertStringContainsString( 'border-left-width:0;', $rendered );
+		$this->assertStringContainsString( 'border-right-style:solid;', $rendered );
+	}
+
+	/**
+	 * Test it preserves authored quote borders in RTL.
+	 */
+	public function testItPreservesAuthoredQuoteBorderInRtl(): void {
+		$theme_controller      = $this->di_container->get( Theme_Controller::class );
+		$rtl_context           = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$parsed_quote          = $this->parsed_quote;
+		$parsed_quote['attrs'] = array(
+			'style' => array(
+				'border' => array(
+					'width' => '0 0 0 2px',
+					'style' => 'dashed',
+				),
+			),
+		);
+
+		$rendered = $this->quote_renderer->render( '<p>Quote content</p>', $parsed_quote, $rtl_context );
+
+		$this->assertStringContainsString( 'border-width:0 0 0 2px;', $rendered );
+		$this->assertStringContainsString( 'border-style:dashed;', $rendered );
+		$this->assertStringNotContainsString( 'border-width:0 1px 0 0;', $rendered );
+	}
+
+	/**
+	 * Test it preserves explicit authored quote alignment in RTL.
+	 */
+	public function testItPreservesAuthoredQuoteAlignmentInRtl(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$expected_borders = array(
+			'left'   => 'border-width:0 0 0 1px;',
+			'center' => 'border-width:0;',
+			'right'  => 'border-width:0 1px 0 0;',
+		);
+
+		foreach ( array( 'left', 'center', 'right' ) as $alignment ) {
+			$parsed_quote                       = $this->parsed_quote;
+			$parsed_quote['attrs']['textAlign'] = $alignment;
+			$content                            = '<blockquote class="wp-block-quote has-text-align-' . $alignment . '"></blockquote>';
+			$rendered                           = $this->quote_renderer->render( $content, $parsed_quote, $rtl_context );
+
+			$this->assertStringContainsString( 'text-align:' . $alignment . ';', $rendered );
+			$this->assertStringContainsString( 'has-text-align-' . $alignment, $rendered );
+			$this->assertStringContainsString( $expected_borders[ $alignment ], $rendered );
+		}
+	}
+
 	/**
 	 * Test it contains quote styles
 	 */
diff --git a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Table_Test.php b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Table_Test.php
index db2e1b0b4d8..27fe1ea6d4f 100644
--- a/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Table_Test.php
+++ b/packages/php/email-editor/tests/integration/Integrations/Core/Renderer/Blocks/Table_Test.php
@@ -127,6 +127,39 @@ class Table_Test extends \Email_Editor_Integration_Test_Case {
 		$this->assertStringContainsString( 'Cell 4', $rendered );
 	}

+	/**
+	 * Test it uses RTL default wrapper and cell alignment when alignment is absent.
+	 */
+	public function testItRendersTableWithRtlDefaultAlignment(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$parsed_table     = $this->parsed_table;
+		unset( $parsed_table['attrs']['textAlign'] );
+		$parsed_table['innerHTML'] = $this->table_content;
+
+		$rendered = $this->table_renderer->render( $this->table_content, $parsed_table, $rtl_context );
+
+		$this->assertStringContainsString( 'text-align:right;', $rendered );
+		$this->assertStringContainsString( 'align="right"', $rendered );
+	}
+
+	/**
+	 * Test it preserves explicit table cell alignment in RTL.
+	 */
+	public function testItPreservesExplicitCellAlignmentInRtl(): void {
+		$theme_controller = $this->di_container->get( Theme_Controller::class );
+		$rtl_context      = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+		$parsed_table     = $this->parsed_table;
+		unset( $parsed_table['attrs']['textAlign'] );
+		$content                   = '<table><tbody><tr><td data-align="left">Cell</td><td class="has-text-align-center">Cell 2</td></tr></tbody></table>';
+		$parsed_table['innerHTML'] = $content;
+
+		$rendered = $this->table_renderer->render( $content, $parsed_table, $rtl_context );
+
+		$this->assertStringContainsString( 'text-align: left', $rendered );
+		$this->assertStringContainsString( 'text-align: center', $rendered );
+	}
+
 	/**
 	 * Test it extracts table from figure wrapper
 	 */
diff --git a/packages/php/email-editor/tests/integration/Integrations/WooCommerce/Renderer/Blocks/Product_Renderers_Rtl_Test.php b/packages/php/email-editor/tests/integration/Integrations/WooCommerce/Renderer/Blocks/Product_Renderers_Rtl_Test.php
new file mode 100644
index 00000000000..e8e9852d4f2
--- /dev/null
+++ b/packages/php/email-editor/tests/integration/Integrations/WooCommerce/Renderer/Blocks/Product_Renderers_Rtl_Test.php
@@ -0,0 +1,254 @@
+<?php
+/**
+ * This file is part of the WooCommerce Email Editor package.
+ *
+ * @package Automattic\WooCommerce\EmailEditor
+ */
+
+declare( strict_types = 1 );
+namespace Automattic\WooCommerce\EmailEditor\Integrations\WooCommerce\Renderer\Blocks;
+
+use Automattic\WooCommerce\EmailEditor\Engine\Email_Editor;
+use Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context;
+use Automattic\WooCommerce\EmailEditor\Engine\Theme_Controller;
+
+/**
+ * Integration tests for WooCommerce product renderer RTL defaults.
+ */
+class Product_Renderers_Rtl_Test extends \Email_Editor_Integration_Test_Case {
+	/**
+	 * Product used in renderer tests.
+	 *
+	 * @var \WC_Product_Simple|null
+	 */
+	private $product = null;
+
+	/**
+	 * LTR rendering context.
+	 *
+	 * @var Rendering_Context
+	 */
+	private Rendering_Context $ltr_context;
+
+	/**
+	 * RTL rendering context.
+	 *
+	 * @var Rendering_Context
+	 */
+	private Rendering_Context $rtl_context;
+
+	/**
+	 * Set up before each test.
+	 */
+	public function setUp(): void {
+		parent::setUp();
+		$this->di_container->get( Email_Editor::class )->initialize();
+
+		$theme_controller  = $this->di_container->get( Theme_Controller::class );
+		$this->ltr_context = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => false ) );
+		$this->rtl_context = new Rendering_Context( $theme_controller->get_theme(), array( 'is_rtl' => true ) );
+
+		if ( ! class_exists( '\WC_Product_Simple' ) ) {
+			return;
+		}
+
+		$this->product = new \WC_Product_Simple();
+		$this->product->set_name( 'RTL Test Product' );
+		$this->product->set_regular_price( '10' );
+		$this->product->set_sale_price( '5' );
+		$this->product->set_price( '5' );
+		$this->product->save();
+	}
+
+	/**
+	 * Tear down after each test.
+	 */
+	public function tearDown(): void {
+		if ( $this->product ) {
+			$this->product->delete( true );
+		}
+		parent::tearDown();
+	}
+
+	/**
+	 * Test product button defaults to RTL alignment and preserves explicit alignment.
+	 */
+	public function testProductButtonRtlAlignment(): void {
+		$this->skip_if_woocommerce_is_unavailable();
+
+		$renderer = new Product_Button();
+		$block    = $this->get_product_block( 'woocommerce/product-button' );
+
+		$ltr                         = $renderer->render( '', $block, $this->ltr_context );
+		$rtl                         = $renderer->render( '', $block, $this->rtl_context );
+		$block['attrs']['textAlign'] = 'left';
+		$explicit_left               = $renderer->render( '', $block, $this->rtl_context );
+
+		$this->assertStringContainsString( 'align="left"', $ltr );
+		$this->assertStringContainsString( 'align="right"', $rtl );
+		$this->assertStringContainsString( 'align="left"', $explicit_left );
+	}
+
+	/**
+	 * Test product price defaults to RTL alignment and preserves explicit alignment.
+	 */
+	public function testProductPriceRtlAlignment(): void {
+		$this->skip_if_woocommerce_is_unavailable();
+
+		$renderer = new Product_Price();
+		$block    = $this->get_product_block( 'woocommerce/product-price' );
+
+		$ltr                         = $renderer->render( '', $block, $this->ltr_context );
+		$rtl                         = $renderer->render( '', $block, $this->rtl_context );
+		$block['attrs']['textAlign'] = 'left';
+		$explicit_left               = $renderer->render( '', $block, $this->rtl_context );
+
+		$this->assertStringContainsString( 'text-align:left', $ltr );
+		$this->assertStringContainsString( 'text-align:right', $rtl );
+		$this->assertStringContainsString( 'text-align:left', $explicit_left );
+	}
+
+	/**
+	 * Test product sale badge defaults to RTL alignment and preserves explicit alignment.
+	 */
+	public function testProductSaleBadgeRtlAlignment(): void {
+		$this->skip_if_woocommerce_is_unavailable();
+
+		$renderer = new Product_Sale_Badge();
+		$block    = $this->get_product_block( 'woocommerce/product-sale-badge' );
+
+		$ltr                     = $renderer->render( '', $block, $this->ltr_context );
+		$rtl                     = $renderer->render( '', $block, $this->rtl_context );
+		$block['attrs']['align'] = 'left';
+		$explicit_left           = $renderer->render( '', $block, $this->rtl_context );
+
+		$this->assertStringContainsString( 'text-align:left', $ltr );
+		$this->assertStringContainsString( 'text-align:right', $rtl );
+		$this->assertStringContainsString( 'text-align:left', $explicit_left );
+	}
+
+	/**
+	 * Test product image defaults to RTL alignment and preserves explicit alignment.
+	 */
+	public function testProductImageRtlAlignment(): void {
+		$this->skip_if_woocommerce_is_unavailable();
+
+		$renderer = new Product_Image();
+		$block    = $this->get_product_block( 'woocommerce/product-image' );
+
+		$ltr                     = $renderer->render( '', $block, $this->ltr_context );
+		$rtl                     = $renderer->render( '', $block, $this->rtl_context );
+		$block['attrs']['align'] = 'left';
+		$explicit_left           = $renderer->render( '', $block, $this->rtl_context );
+
+		$this->assertStringContainsString( 'align="left"', $ltr );
+		$this->assertStringContainsString( 'align="right"', $rtl );
+		$this->assertStringContainsString( 'align="left"', $explicit_left );
+	}
+
+	/**
+	 * Test product collection two-column gaps use direction-aware physical sides.
+	 */
+	public function testProductCollectionRtlColumnGapSide(): void {
+		$renderer = new Product_Collection();
+		$method   = new \ReflectionMethod( $renderer, 'render_two_column_grid' );
+		$method->setAccessible( true );
+		$template_block = array( 'innerBlocks' => array() );
+
+		$ltr = $method->invoke( $renderer, array( null, null ), $template_block, 'test', $this->ltr_context );
+		$rtl = $method->invoke( $renderer, array( null, null ), $template_block, 'test', $this->rtl_context );
+		$this->assertIsString( $ltr );
+		$this->assertIsString( $rtl );
+
+		$ltr_cell_styles = $this->get_first_two_cell_styles( $ltr );
+		$rtl_cell_styles = $this->get_first_two_cell_styles( $rtl );
+
+		$this->assertStringContainsString( 'padding-right: 10px', $ltr_cell_styles[0] );
+		$this->assertStringNotContainsString( 'padding-left: 10px', $ltr_cell_styles[0] );
+		$this->assertStringContainsString( 'padding-left: 10px', $ltr_cell_styles[1] );
+		$this->assertStringNotContainsString( 'padding-right: 10px', $ltr_cell_styles[1] );
+		$this->assertStringContainsString( 'padding-left: 10px', $rtl_cell_styles[0] );
+		$this->assertStringNotContainsString( 'padding-right: 10px', $rtl_cell_styles[0] );
+		$this->assertStringContainsString( 'padding-right: 10px', $rtl_cell_styles[1] );
+		$this->assertStringNotContainsString( 'padding-left: 10px', $rtl_cell_styles[1] );
+	}
+
+	/**
+	 * Test product collection outer spacers use RTL alignment.
+	 */
+	public function testProductCollectionRtlOuterSpacerAlignment(): void {
+		$renderer = new Product_Collection();
+		$method   = new \ReflectionMethod( $renderer, 'render_product_grid' );
+		$method->setAccessible( true );
+		$template_block = array(
+			'email_attrs' => array(),
+			'innerBlocks' => array(),
+		);
+
+		$single_column = $method->invoke( $renderer, array( null ), $template_block, 'test', 1, $this->rtl_context );
+		$two_column    = $method->invoke( $renderer, array( null, null ), $template_block, 'test', 2, $this->rtl_context );
+		$this->assertIsString( $single_column );
+		$this->assertIsString( $two_column );
+
+		$this->assert_outer_spacer_alignment( $single_column );
+		$this->assert_outer_spacer_alignment( $two_column );
+	}
+
+	/**
+	 * Assert the outer spacer table uses RTL alignment.
+	 *
+	 * @param string $rendered Rendered HTML.
+	 */
+	private function assert_outer_spacer_alignment( string $rendered ): void {
+		$right_aligned_table_position = strpos( $rendered, 'align="right"' );
+		$layout_class_position        = strpos( $rendered, 'email-block-layout' );
+
+		$this->assertNotFalse( $right_aligned_table_position );
+		$this->assertNotFalse( $layout_class_position );
+		$this->assertGreaterThan( $right_aligned_table_position, $layout_class_position );
+	}
+
+	/**
+	 * Get parsed product block.
+	 *
+	 * @param string $block_name Block name.
+	 * @return array
+	 * @throws \RuntimeException When the product fixture is unavailable.
+	 */
+	private function get_product_block( string $block_name ): array {
+		if ( ! $this->product instanceof \WC_Product_Simple ) {
+			throw new \RuntimeException( 'Product fixture is unavailable.' );
+		}
+
+		return array(
+			'blockName'   => $block_name,
+			'attrs'       => array(),
+			'context'     => array(
+				'postId' => $this->product->get_id(),
+			),
+			'email_attrs' => array(),
+			'innerBlocks' => array(),
+		);
+	}
+
+	/**
+	 * Get the first two TD style attributes from rendered grid HTML.
+	 *
+	 * @param string $html Rendered HTML.
+	 * @return string[]
+	 */
+	private function get_first_two_cell_styles( string $html ): array {
+		preg_match_all( '/<td style="([^"]*)"/', $html, $matches );
+		$this->assertGreaterThanOrEqual( 2, count( $matches[1] ) );
+		return array_slice( $matches[1], 0, 2 );
+	}
+
+	/**
+	 * Skip product-specific renderer tests when WooCommerce is not loaded.
+	 */
+	private function skip_if_woocommerce_is_unavailable(): void {
+		if ( ! $this->product ) {
+			$this->markTestSkipped( 'WooCommerce product classes are not loaded in this package test environment.' );
+		}
+	}
+}
diff --git a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
index 4f97afa039e..c291b811e9c 100644
--- a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
+++ b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Preprocessors/Spacing_Preprocessor_Test.php
@@ -262,6 +262,49 @@ class Spacing_Preprocessor_Test extends \Email_Editor_Unit_Test {
 		$this->assertEquals( '10px', $second_column['email_attrs']['padding-left'] );
 	}

+	/**
+	 * Test it adds padding-right to column blocks in RTL renders.
+	 */
+	public function testItAddsPaddingRightToColumnsInRtl(): void {
+		$theme_json = $this->createMock( \WP_Theme_JSON::class );
+		$context    = new \Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer\Rendering_Context(
+			$theme_json,
+			array( 'is_rtl' => true )
+		);
+		$blocks     = array(
+			array(
+				'blockName'   => 'core/columns',
+				'attrs'       => array(
+					'style' => array(
+						'spacing' => array(
+							'blockGap' => array(
+								'left' => '30px',
+							),
+						),
+					),
+				),
+				'innerBlocks' => array(
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+					array(
+						'blockName'   => 'core/column',
+						'attrs'       => array(),
+						'innerBlocks' => array(),
+					),
+				),
+			),
+		);
+
+		$result        = $this->preprocessor->preprocess_with_context( $blocks, $this->layout, $this->styles, $context );
+		$second_column = $result[0]['innerBlocks'][1];
+
+		$this->assertArrayNotHasKey( 'padding-left', $second_column['email_attrs'] );
+		$this->assertSame( '30px', $second_column['email_attrs']['padding-right'] );
+	}
+
 	/**
 	 * Test it skips root padding for core/post-content but applies it to its children
 	 */
diff --git a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Process_Manager_Test.php b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Process_Manager_Test.php
index 1daeb63642e..f763b09cc98 100644
--- a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Process_Manager_Test.php
+++ b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Process_Manager_Test.php
@@ -51,7 +51,8 @@ class Process_Manager_Test extends \Email_Editor_Unit_Test {
 		$typography->expects( $this->once() )->method( 'preprocess' )->willReturn( array() );

 		$spacing = $this->createMock( Spacing_Preprocessor::class );
-		$spacing->expects( $this->once() )->method( 'preprocess' )->willReturn( array() );
+		$spacing->expects( $this->once() )->method( 'preprocess_with_context' )->willReturn( array() );
+		$spacing->expects( $this->never() )->method( 'preprocess' );

 		$quote_text_align = $this->createMock( Quote_Preprocessor::class );
 		$quote_text_align->expects( $this->once() )->method( 'preprocess' )->willReturn( array() );
diff --git a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Rendering_Context_Test.php b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Rendering_Context_Test.php
index bad6cb98bd8..e94b8dcf6e4 100644
--- a/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Rendering_Context_Test.php
+++ b/packages/php/email-editor/tests/unit/Engine/Renderer/ContentRenderer/Rendering_Context_Test.php
@@ -12,6 +12,27 @@ namespace Automattic\WooCommerce\EmailEditor\Engine\Renderer\ContentRenderer;
  * Unit test for Rendering_Context class.
  */
 class Rendering_Context_Test extends \Email_Editor_Unit_Test {
+	/**
+	 * Create a theme JSON mock.
+	 *
+	 * @return \WP_Theme_JSON
+	 */
+	private function create_theme_json(): \WP_Theme_JSON {
+		$theme_json = $this->createMock( \WP_Theme_JSON::class );
+		$theme_json->method( 'get_data' )->willReturn( array( 'styles' => array() ) );
+		$theme_json->method( 'get_settings' )->willReturn(
+			array(
+				'color' => array(
+					'palette' => array(
+						'theme'   => array(),
+						'default' => array(),
+					),
+				),
+			)
+		);
+		return $theme_json;
+	}
+
 	/**
 	 * Test it returns correct layout width without padding.
 	 */
@@ -143,4 +164,60 @@ class Rendering_Context_Test extends \Email_Editor_Unit_Test {
 		$this->assertSame( 'default', $context->get( 'non_existent_key', 'default' ) );
 		$this->assertSame( array(), $context->get_email_context() );
 	}
+
+	/**
+	 * Test it resolves explicit RTL context before language fallback.
+	 */
+	public function testItResolvesExplicitRtlContextBeforeLanguage(): void {
+		$theme_json = $this->create_theme_json();
+
+		$ltr_context = new Rendering_Context( $theme_json, array( 'is_rtl' => false ), 'ar_SA' );
+		$rtl_context = new Rendering_Context( $theme_json, array( 'is_rtl' => true ), 'en_US' );
+
+		$this->assertFalse( $ltr_context->is_rtl() );
+		$this->assertSame( 'ltr', $ltr_context->get_text_direction() );
+		$this->assertSame( 'left', $ltr_context->get_default_text_align() );
+		$this->assertTrue( $rtl_context->is_rtl() );
+		$this->assertSame( 'rtl', $rtl_context->get_text_direction() );
+		$this->assertSame( 'right', $rtl_context->get_default_text_align() );
+	}
+
+	/**
+	 * Test it resolves direction from language when explicit context is absent.
+	 */
+	public function testItResolvesDirectionFromLanguage(): void {
+		$theme_json = $this->create_theme_json();
+
+		$this->assertTrue( ( new Rendering_Context( $theme_json, array(), 'he_IL' ) )->is_rtl() );
+		$this->assertTrue( ( new Rendering_Context( $theme_json, array(), 'fa-IR' ) )->is_rtl() );
+		$this->assertTrue( ( new Rendering_Context( $theme_json, array(), 'ckb_IR' ) )->is_rtl() );
+		$this->assertFalse( ( new Rendering_Context( $theme_json, array(), 'en_US' ) )->is_rtl() );
+		$this->assertFalse( ( new Rendering_Context( $theme_json, array(), 'unknown' ) )->is_rtl() );
+	}
+
+	/**
+	 * Test it ignores malformed RTL context values.
+	 */
+	public function testItIgnoresMalformedRtlContextValues(): void {
+		$context = new Rendering_Context( $this->create_theme_json(), array( 'is_rtl' => 'true' ), 'ar' );
+
+		$this->assertTrue( $context->is_rtl() );
+		$this->assertSame( 'right', $context->get_start_side() );
+		$this->assertSame( 'left', $context->get_end_side() );
+	}
+
+	/**
+	 * Test it sanitizes and resolves text alignment values.
+	 */
+	public function testItSanitizesAndResolvesTextAlignment(): void {
+		$context = new Rendering_Context( $this->create_theme_json(), array( 'is_rtl' => true ) );
+
+		$this->assertSame( 'left', $context->sanitize_text_align( 'left' ) );
+		$this->assertSame( 'center', $context->sanitize_text_align( 'center' ) );
+		$this->assertSame( 'right', $context->sanitize_text_align( 'right' ) );
+		$this->assertNull( $context->sanitize_text_align( 'start' ) );
+		$this->assertNull( $context->sanitize_text_align( '<script>' ) );
+		$this->assertSame( 'right', $context->resolve_text_align( null ) );
+		$this->assertSame( 'center', $context->resolve_text_align( 'center' ) );
+	}
 }