diff --git a/mu-plugins/blocks/ratings-bars/index.php b/mu-plugins/blocks/ratings-bars/index.php
new file mode 100644
index 00000000..e9329933
--- /dev/null
+++ b/mu-plugins/blocks/ratings-bars/index.php
@@ -0,0 +1,20 @@
+context['postId'];
+if ( ! $current_post_id ) {
+ return;
+}
+
+/**
+ * Get the ratings data via filter, so that individual sites can provide
+ * this regardless of rating data format.
+ *
+ * @param array $data {
+ * Array of ratings data.
+ *
+ * The return value should use the following format.
+ *
+ * @type int $ratingsCount The total of ratings, must match sum of all
+ * values in ratings.
+ * @type int[] $ratings Rating count. The array must have 5 items, ex:
+ * [1 => count of 1-star, …, 5 => count of 5-star].
+ * @type int $rating The average rating on a scale of 0 - 100.
+ * @type string $supportUrl URL to support forum.
+ * }
+ * @param int $current_post_id The ID of the current post.
+ */
+$data = apply_filters( 'wporg_ratings_data', array(), $current_post_id );
+
+$defaults = array(
+ 'ratingsCount' => 0,
+ 'ratings' => [],
+ 'supportUrl' => '',
+);
+
+$data = wp_parse_args( $data, $defaults );
+
+if ( empty( $data['ratings'] ) || empty( $data['ratingsCount'] ) ) {
+ return;
+}
+
+?>
+
diff --git a/mu-plugins/blocks/ratings-bars/src/block.json b/mu-plugins/blocks/ratings-bars/src/block.json
new file mode 100644
index 00000000..a5ea5393
--- /dev/null
+++ b/mu-plugins/blocks/ratings-bars/src/block.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/ratings-bars",
+ "version": "0.1.0",
+ "title": "Ratings (bars)",
+ "category": "design",
+ "icon": "",
+ "description": "The breakdown of ratings displayed as bars for each rating value.",
+ "textdomain": "wporg",
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "render": "file:../render.php"
+}
diff --git a/mu-plugins/blocks/ratings-bars/src/index.js b/mu-plugins/blocks/ratings-bars/src/index.js
new file mode 100644
index 00000000..4f621505
--- /dev/null
+++ b/mu-plugins/blocks/ratings-bars/src/index.js
@@ -0,0 +1,25 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+import ServerSideRender from '@wordpress/server-side-render';
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * Internal dependencies
+ */
+import metadata from './block.json';
+import './style.scss';
+
+function Edit( { attributes, name } ) {
+ return (
+
+
+
+ );
+}
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/mu-plugins/blocks/ratings-bars/src/style.scss b/mu-plugins/blocks/ratings-bars/src/style.scss
new file mode 100644
index 00000000..d066b8eb
--- /dev/null
+++ b/mu-plugins/blocks/ratings-bars/src/style.scss
@@ -0,0 +1,65 @@
+.wp-block-wporg-ratings-bars {
+ list-style: none;
+ padding-inline-start: unset;
+}
+
+.wporg-ratings-bars__bar {
+ a {
+ margin-bottom: 4px;
+ display: flex;
+ align-items: center;
+ gap: var(--wp--preset--spacing--10);
+ text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ &:last-child a {
+ margin-bottom: 0;
+ }
+}
+
+.wporg-ratings-bars__bar-label {
+ flex-basis: 4em;
+ flex-shrink: 0;
+}
+
+.wporg-ratings-bars__bar-count {
+ flex-basis: 2em;
+ flex-shrink: 0;
+ text-align: right;
+}
+
+.wporg-ratings-bars__bar-background {
+ display: inline-block;
+ background-color: var(--wp--preset--color--light-grey-2);
+ position: relative;
+ width: 100%;
+ height: var(--wp--preset--spacing--20);
+}
+
+.wporg-ratings-bars__bar-foreground {
+ position: absolute;
+ inset: 0;
+ right: auto;
+ background-color: var(--wp--custom--wporg-ratings-stars--color--fill, #e26f56);
+}
+
+@supports (grid-template-columns: subgrid) {
+ .wp-block-wporg-ratings-bars {
+ display: grid;
+ gap: 4px var(--wp--preset--spacing--10);
+ grid-template-columns: auto 1fr auto;
+
+ .wporg-ratings-bars__bar,
+ .wporg-ratings-bars__bar a {
+ display: grid;
+ grid-column: span 3;
+ grid-template-columns: subgrid;
+ margin-bottom: unset;
+ gap: unset;
+ }
+ }
+}
diff --git a/mu-plugins/blocks/ratings-stars/index.php b/mu-plugins/blocks/ratings-stars/index.php
new file mode 100644
index 00000000..0a2be3f1
--- /dev/null
+++ b/mu-plugins/blocks/ratings-stars/index.php
@@ -0,0 +1,20 @@
+context['postId'];
+if ( ! $current_post_id ) {
+ return;
+}
+
+/** This filter is documented in mu-plugins/blocks/ratings-bars/render.php */
+$data = apply_filters( 'wporg_ratings_data', array(), $current_post_id );
+
+$defaults = array(
+ 'rating' => 0,
+);
+
+$data = wp_parse_args( $data, $defaults );
+
+if ( empty( $data['rating'] ) ) {
+ return;
+}
+
+?>
+>
+
+
+
+
+
+
+
+
+
';
+ } else if ( $i + 0.5 === $display_rating ) {
+ echo '
';
+ } else {
+ echo '
';
+ }
+ }
+ ?>
+
+
+
+ ' . $display_rating . '' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ );
+ ?>
+
+
+
diff --git a/mu-plugins/blocks/ratings-stars/src/block.json b/mu-plugins/blocks/ratings-stars/src/block.json
new file mode 100644
index 00000000..7b991bca
--- /dev/null
+++ b/mu-plugins/blocks/ratings-stars/src/block.json
@@ -0,0 +1,18 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "wporg/ratings-stars",
+ "version": "0.1.0",
+ "title": "Ratings (stars)",
+ "category": "design",
+ "icon": "",
+ "description": "The average rating displayed as stars.",
+ "textdomain": "wporg",
+ "supports": {
+ "html": false
+ },
+ "usesContext": [ "postId" ],
+ "editorScript": "file:./index.js",
+ "style": "file:./style-index.css",
+ "render": "file:../render.php"
+}
diff --git a/mu-plugins/blocks/ratings-stars/src/index.js b/mu-plugins/blocks/ratings-stars/src/index.js
new file mode 100644
index 00000000..4f621505
--- /dev/null
+++ b/mu-plugins/blocks/ratings-stars/src/index.js
@@ -0,0 +1,25 @@
+/**
+ * WordPress dependencies
+ */
+import { registerBlockType } from '@wordpress/blocks';
+import ServerSideRender from '@wordpress/server-side-render';
+import { useBlockProps } from '@wordpress/block-editor';
+
+/**
+ * Internal dependencies
+ */
+import metadata from './block.json';
+import './style.scss';
+
+function Edit( { attributes, name } ) {
+ return (
+
+
+
+ );
+}
+
+registerBlockType( metadata.name, {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/mu-plugins/blocks/ratings-stars/src/style.scss b/mu-plugins/blocks/ratings-stars/src/style.scss
new file mode 100644
index 00000000..6e4e7bc8
--- /dev/null
+++ b/mu-plugins/blocks/ratings-stars/src/style.scss
@@ -0,0 +1,29 @@
+.wp-block-wporg-ratings-stars {
+ display: flex;
+ align-items: center;
+}
+
+.wporg-ratings-stars__icons {
+ display: inline-flex;
+
+ svg {
+ height: 32px;
+ width: 32px;
+ margin-inline-start: -6px;
+ fill: var(--wp--custom--wporg-ratings-stars--color--fill, #e26f56);
+ }
+
+ // Flip the half-star for RTL views.
+ .rtl & .is-star-half {
+ transform: rotateY(-180deg);
+ }
+}
+
+.wporg-ratings-stars__label {
+ font-size: var(--wp--preset--font-size--small);
+ color: var(--wp--preset--color--charcoal-4);
+
+ .wporg-ratings-stars__icons + & {
+ margin-inline-start: 0.5em;
+ }
+}
diff --git a/mu-plugins/loader.php b/mu-plugins/loader.php
index 486a31bc..b628c3c1 100644
--- a/mu-plugins/loader.php
+++ b/mu-plugins/loader.php
@@ -40,6 +40,8 @@
require_once __DIR__ . '/blocks/query-filter/index.php';
require_once __DIR__ . '/blocks/query-has-results/index.php';
require_once __DIR__ . '/blocks/query-total/index.php';
+require_once __DIR__ . '/blocks/ratings-bars/index.php';
+require_once __DIR__ . '/blocks/ratings-stars/index.php';
require_once __DIR__ . '/blocks/sidebar-container/index.php';
require_once __DIR__ . '/blocks/screenshot-preview/block.php';
require_once __DIR__ . '/blocks/screenshot-preview-block/block.php';