Index: wp-admin/includes/theme.php
===================================================================
--- wp-admin/includes/theme.php	(revision 60707)
+++ wp-admin/includes/theme.php	(working copy)
@@ -138,6 +138,157 @@
 }
 
 /**
+ * Downloads a theme as a ZIP archive.
+ *
+ * @since 6.0.0
+ *
+ * @global WP_Filesystem_Base $wp_filesystem WordPress filesystem subclass.
+ *
+ * @param string $stylesheet Stylesheet of the theme to download.
+ * @return bool|WP_Error True on success, WP_Error on failure.
+ */
+function download_theme( $stylesheet ) {
+	global $wp_filesystem;
+
+	if ( empty( $stylesheet ) ) {
+		return new WP_Error( 'empty_stylesheet', __( 'Theme stylesheet is empty.' ) );
+	}
+
+	$theme = wp_get_theme( $stylesheet );
+
+	if ( ! $theme->exists() ) {
+		return new WP_Error( 'theme_not_found', __( 'Theme not found.' ) );
+	}
+
+	// Initialize filesystem.
+	if ( empty( $wp_filesystem ) ) {
+		require_once ABSPATH . '/wp-admin/includes/file.php';
+		WP_Filesystem();
+	}
+
+	$theme_root = get_theme_root( $stylesheet );
+	$theme_dir  = $theme_root . '/' . $stylesheet;
+
+	if ( ! $wp_filesystem->is_dir( $theme_dir ) ) {
+		return new WP_Error( 'theme_dir_not_found', __( 'Theme directory not found.' ) );
+	}
+
+	// Create temporary directory for ZIP file.
+	$upload_dir = wp_upload_dir();
+	$temp_dir   = $upload_dir['basedir'] . '/theme-downloads';
+
+	if ( ! $wp_filesystem->is_dir( $temp_dir ) ) {
+		$wp_filesystem->mkdir( $temp_dir, FS_CHMOD_DIR );
+	}
+
+	$zip_filename = sanitize_file_name( $stylesheet . '.zip' );
+	$zip_path     = $temp_dir . '/' . $zip_filename;
+
+	// Remove existing ZIP file if it exists.
+	if ( $wp_filesystem->exists( $zip_path ) ) {
+		$wp_filesystem->delete( $zip_path );
+	}
+
+	// Get all theme files recursively.
+	$files = list_files( $theme_dir );
+
+	if ( empty( $files ) ) {
+		return new WP_Error( 'no_files', __( 'No files found in theme directory.' ) );
+	}
+
+	// Create ZIP archive.
+	if ( class_exists( 'ZipArchive' ) ) {
+		$zip = new ZipArchive();
+		if ( $zip->open( $zip_path, ZipArchive::CREATE | ZipArchive::OVERWRITE ) !== true ) {
+			return new WP_Error( 'zip_create_failed', __( 'Could not create ZIP archive.' ) );
+		}
+
+		foreach ( $files as $file ) {
+			if ( ! is_dir( $file ) ) {
+				$relative_path = str_replace( $theme_dir . DIRECTORY_SEPARATOR, '', $file );
+				$relative_path = str_replace( '\\', '/', $relative_path ); // Normalize path separators.
+
+				// Skip hidden files and directories.
+				$path_parts = explode( '/', $relative_path );
+				$skip_file  = false;
+				foreach ( $path_parts as $part ) {
+					if ( strpos( $part, '.' ) === 0 && $part !== '.' && $part !== '..' ) {
+						$skip_file = true;
+						break;
+					}
+				}
+
+				if ( ! $skip_file ) {
+					$zip->addFile( $file, $relative_path );
+				}
+			}
+		}
+
+		$zip->close();
+	} else {
+		// Fallback to PclZip if ZipArchive is not available.
+		require_once ABSPATH . 'wp-admin/includes/class-pclzip.php';
+		$archive = new PclZip( $zip_path );
+
+		$file_list = array();
+		foreach ( $files as $file ) {
+			if ( ! is_dir( $file ) ) {
+				$relative_path = str_replace( $theme_dir . DIRECTORY_SEPARATOR, '', $file );
+				$relative_path = str_replace( '\\', '/', $relative_path ); // Normalize path separators.
+
+				// Skip hidden files and directories.
+				$path_parts = explode( '/', $relative_path );
+				$skip_file  = false;
+				foreach ( $path_parts as $part ) {
+					if ( strpos( $part, '.' ) === 0 && $part !== '.' && $part !== '..' ) {
+						$skip_file = true;
+						break;
+					}
+				}
+
+				if ( ! $skip_file ) {
+					$file_list[] = array(
+						PCLZIP_ATT_FILE_NAME => $file,
+						PCLZIP_ATT_FILE_NEW_FULL_NAME => $relative_path,
+					);
+				}
+			}
+		}
+
+		if ( empty( $file_list ) || $archive->create( $file_list ) === 0 ) {
+			return new WP_Error( 'zip_create_failed', __( 'Could not create ZIP archive.' ) );
+		}
+	}
+
+	// Send ZIP file to browser.
+	if ( ! file_exists( $zip_path ) ) {
+		return new WP_Error( 'zip_not_created', __( 'ZIP file was not created.' ) );
+	}
+
+	// Clean output buffer.
+	if ( ob_get_level() ) {
+		ob_end_clean();
+	}
+
+	// Set headers for download.
+	header( 'Content-Type: application/zip' );
+	header( 'Content-Disposition: attachment; filename="' . $zip_filename . '"' );
+	header( 'Content-Length: ' . filesize( $zip_path ) );
+	header( 'Cache-Control: no-cache, must-revalidate' );
+	header( 'Expires: Sat, 26 Jul 1997 05:00:00 GMT' );
+
+	// Read and output file.
+	readfile( $zip_path );
+
+	// Clean up temporary file.
+	if ( file_exists( $zip_path ) ) {
+		@unlink( $zip_path );
+	}
+
+	exit;
+}
+
+/**
  * Gets the page templates available in this theme.
  *
  * @since 1.5.0
@@ -791,6 +942,7 @@
 				'activate'   => current_user_can( 'switch_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=activate&amp;stylesheet=' . $encoded_slug ), 'switch-theme_' . $slug ) : null,
 				'customize'  => $customize_action,
 				'delete'     => ( ! is_multisite() && current_user_can( 'delete_themes' ) ) ? wp_nonce_url( admin_url( 'themes.php?action=delete&amp;stylesheet=' . $encoded_slug ), 'delete-theme_' . $slug ) : null,
+				'download'   => current_user_can( 'switch_themes' ) ? wp_nonce_url( admin_url( 'themes.php?action=download&amp;stylesheet=' . $encoded_slug ), 'download-theme_' . $slug ) : null,
 				'autoupdate' => wp_is_auto_update_enabled_for_type( 'theme' ) && ! is_multisite() && current_user_can( 'update_themes' )
 					? wp_nonce_url( admin_url( 'themes.php?action=' . $auto_update_action . '&amp;stylesheet=' . $encoded_slug ), 'updates' )
 					: null,
Index: wp-admin/themes.php
===================================================================
--- wp-admin/themes.php	(revision 60707)
+++ wp-admin/themes.php	(working copy)
@@ -120,6 +120,33 @@
 		wp_redirect( admin_url( 'themes.php?disabled-auto-update=true' ) );
 
 		exit;
+	} elseif ( 'download' === $_GET['action'] ) {
+		check_admin_referer( 'download-theme_' . $_GET['stylesheet'] );
+		$theme = wp_get_theme( $_GET['stylesheet'] );
+
+		if ( ! $theme->exists() || ! $theme->is_allowed() ) {
+			wp_die(
+				'<h1>' . __( 'An error occurred.' ) . '</h1>' .
+				'<p>' . __( 'The requested theme does not exist.' ) . '</p>',
+				403
+			);
+		}
+
+		if ( ! current_user_can( 'switch_themes' ) ) {
+			wp_die(
+				'<h1>' . __( 'You need a higher level of permission.' ) . '</h1>' .
+				'<p>' . __( 'Sorry, you are not allowed to download themes.' ) . '</p>',
+				403
+			);
+		}
+
+		$result = download_theme( $_GET['stylesheet'] );
+
+		if ( is_wp_error( $result ) ) {
+			wp_die( $result );
+		}
+
+		// download_theme() handles the file output and exit.
 	}
 }
 
@@ -655,6 +682,17 @@
 			<?php } ?>
 		<?php } ?>
 
+		<?php if ( ! empty( $theme['actions']['download'] ) ) { ?>
+			<?php
+			/* translators: %s: Theme name. */
+			$download_aria_label = sprintf( _x( 'Download %s', 'theme' ), $theme['name'] );
+			?>
+			<a class="button download-theme"
+				href="<?php echo esc_url( $theme['actions']['download'] ); ?>"
+				aria-label="<?php echo esc_attr( $download_aria_label ); ?>"
+			><?php _e( 'Download' ); ?></a>
+		<?php } ?>
+
 		</div>
 	</div>
 </div>
@@ -1043,6 +1081,17 @@
 					><?php _e( 'Live Preview' ); ?></a>
 				<# } #>
 			<# } #>
+
+			<# if ( data.actions.download ) { #>
+				<?php
+				/* translators: %s: Theme name. */
+				$download_aria_label = sprintf( _x( 'Download %s', 'theme' ), '{{ data.name }}' );
+				?>
+				<a class="button download-theme"
+					href="{{{ data.actions.download }}}"
+					aria-label="<?php echo esc_attr( $download_aria_label ); ?>"
+				><?php _e( 'Download' ); ?></a>
+			<# } #>
 		</div>
 	</div>
 </script>
@@ -1309,6 +1358,17 @@
 					aria-label="<?php echo esc_attr( $aria_label ); ?>"
 				><?php _e( 'Delete' ); ?></a>
 			<# } #>
+
+			<# if ( data.actions.download ) { #>
+				<?php
+				/* translators: %s: Theme name. */
+				$download_aria_label = sprintf( _x( 'Download %s', 'theme' ), '{{ data.name }}' );
+				?>
+				<a class="button download-theme"
+					href="{{{ data.actions.download }}}"
+					aria-label="<?php echo esc_attr( $download_aria_label ); ?>"
+				><?php _e( 'Download' ); ?></a>
+			<# } #>
 		</div>
 	</div>
 </script>
