diff --git a/core/modules/comment/src/Plugin/views/row/Rss.php b/core/modules/comment/src/Plugin/views/row/Rss.php
index 5be3a680a4f3e30fd08137df72bc60f37775ee81..78f9083280fee0d04b03b30e3f20650a15e3884f 100644
--- a/core/modules/comment/src/Plugin/views/row/Rss.php
+++ b/core/modules/comment/src/Plugin/views/row/Rss.php
@@ -2,21 +2,22 @@
 
 namespace Drupal\comment\Plugin\views\row;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Plugin\views\row\RssPluginBase;
 
 /**
  * Plugin which formats the comments as RSS items.
- *
- * @ViewsRow(
- *   id = "comment_rss",
- *   title = @Translation("Comment"),
- *   help = @Translation("Display the comment as RSS."),
- *   theme = "views_view_row_rss",
- *   register_theme = FALSE,
- *   base = {"comment_field_data"},
- *   display_types = {"feed"}
- * )
  */
+#[ViewsRow(
+  id: "comment_rss",
+  title: new TranslatableMarkup("Comment"),
+  help: new TranslatableMarkup("Display the comment as RSS."),
+  theme: "views_view_row_rss",
+  register_theme: FALSE,
+  base: ["comment_field_data"],
+  display_types: ["feed"]
+)]
 class Rss extends RssPluginBase {
 
   /**
diff --git a/core/modules/node/src/Plugin/views/row/NodeRow.php b/core/modules/node/src/Plugin/views/row/NodeRow.php
index 7f061bae5cc8d55855c9124d88658de41fa1f960..082db0752d547fe1dd3868704d01725f5b71e33b 100644
--- a/core/modules/node/src/Plugin/views/row/NodeRow.php
+++ b/core/modules/node/src/Plugin/views/row/NodeRow.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\node\Plugin\views\row;
 
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Plugin\views\row\EntityRow;
 
 /**
@@ -10,11 +11,8 @@
  * Most of the code on this object is in the theme function.
  *
  * @ingroup views_row_plugins
- *
- * @ViewsRow(
- *   id = "entity:node",
- * )
  */
+#[ViewsRow("entity:node")]
 class NodeRow extends EntityRow {
 
   /**
diff --git a/core/modules/node/src/Plugin/views/row/Rss.php b/core/modules/node/src/Plugin/views/row/Rss.php
index 4a74d4da167faca8a3ecb2b6325a5e0278154e70..3189a47f441e7abe6ac34ea6e21563417281019e 100644
--- a/core/modules/node/src/Plugin/views/row/Rss.php
+++ b/core/modules/node/src/Plugin/views/row/Rss.php
@@ -2,21 +2,22 @@
 
 namespace Drupal\node\Plugin\views\row;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Plugin\views\row\RssPluginBase;
 
 /**
  * Performs a node_view on the resulting object and formats it as an RSS item.
- *
- * @ViewsRow(
- *   id = "node_rss",
- *   title = @Translation("Content"),
- *   help = @Translation("Display the content with standard node view."),
- *   theme = "views_view_row_rss",
- *   register_theme = FALSE,
- *   base = {"node_field_data"},
- *   display_types = {"feed"}
- * )
  */
+#[ViewsRow(
+  id: "node_rss",
+  title: new TranslatableMarkup("Content"),
+  help: new TranslatableMarkup("Display the content with standard node view."),
+  theme: "views_view_row_rss",
+  register_theme: FALSE,
+  base: ["node_field_data"],
+  display_types: ["feed"]
+)]
 class Rss extends RssPluginBase {
 
   /**
diff --git a/core/modules/rest/src/Plugin/views/row/DataEntityRow.php b/core/modules/rest/src/Plugin/views/row/DataEntityRow.php
index f583687a82eb60552f8cb3623c89b03ee1141639..e77ba33c7d8aed3bdcac9d99df351eb02bdd38eb 100644
--- a/core/modules/rest/src/Plugin/views/row/DataEntityRow.php
+++ b/core/modules/rest/src/Plugin/views/row/DataEntityRow.php
@@ -5,6 +5,8 @@
 use Drupal\Core\Entity\EntityRepositoryInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
 use Drupal\views\Plugin\views\row\RowPluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -13,14 +15,13 @@
  * Plugin which displays entities as raw data.
  *
  * @ingroup views_row_plugins
- *
- * @ViewsRow(
- *   id = "data_entity",
- *   title = @Translation("Entity"),
- *   help = @Translation("Use entities as row data."),
- *   display_types = {"data"}
- * )
  */
+#[ViewsRow(
+  id: "data_entity",
+  title: new TranslatableMarkup("Entity"),
+  help: new TranslatableMarkup("Use entities as row data."),
+  display_types: ["data"]
+)]
 class DataEntityRow extends RowPluginBase {
 
   use EntityTranslationRenderTrait;
diff --git a/core/modules/rest/src/Plugin/views/row/DataFieldRow.php b/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
index b328e452e03e24ed154f71a67cde3a63e4b0bb8a..e0efc39d8dde7593c90184ae42bf5942a1298b01 100644
--- a/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
+++ b/core/modules/rest/src/Plugin/views/row/DataFieldRow.php
@@ -3,6 +3,8 @@
 namespace Drupal\rest\Plugin\views\row;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\row\RowPluginBase;
@@ -11,14 +13,13 @@
  * Plugin which displays fields as raw data.
  *
  * @ingroup views_row_plugins
- *
- * @ViewsRow(
- *   id = "data_field",
- *   title = @Translation("Fields"),
- *   help = @Translation("Use fields as row data."),
- *   display_types = {"data"}
- * )
  */
+#[ViewsRow(
+  id: "data_field",
+  title: new TranslatableMarkup("Fields"),
+  help: new TranslatableMarkup("Use fields as row data."),
+  display_types: ["data"]
+)]
 class DataFieldRow extends RowPluginBase {
 
   /**
diff --git a/core/modules/search/src/Plugin/views/row/SearchRow.php b/core/modules/search/src/Plugin/views/row/SearchRow.php
index 29e150d557ba22c97ab2e0b3ec33333efef783f8..1ea7dca8174aaa0c8a0feb1148e1bf7602ee03ad 100644
--- a/core/modules/search/src/Plugin/views/row/SearchRow.php
+++ b/core/modules/search/src/Plugin/views/row/SearchRow.php
@@ -3,17 +3,18 @@
 namespace Drupal\search\Plugin\views\row;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Plugin\views\row\RowPluginBase;
 
 /**
  * Row handler plugin for displaying search results.
- *
- * @ViewsRow(
- *   id = "search_view",
- *   title = @Translation("Search results"),
- *   help = @Translation("Provides a row plugin to display search results.")
- * )
  */
+#[ViewsRow(
+  id: "search_view",
+  title: new TranslatableMarkup("Search results"),
+  help: new TranslatableMarkup("Provides a row plugin to display search results.")
+)]
 class SearchRow extends RowPluginBase {
 
   /**
diff --git a/core/modules/user/src/Plugin/views/row/UserRow.php b/core/modules/user/src/Plugin/views/row/UserRow.php
index 38991493f11c808f91ba3d0e0114cdb4ca7bf336..751156b391569fccf6877a68229ca56832bfa6a2 100644
--- a/core/modules/user/src/Plugin/views/row/UserRow.php
+++ b/core/modules/user/src/Plugin/views/row/UserRow.php
@@ -2,17 +2,15 @@
 
 namespace Drupal\user\Plugin\views\row;
 
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Plugin\views\row\EntityRow;
 
 /**
  * A row plugin which renders a user.
  *
  * @ingroup views_row_plugins
- *
- * @ViewsRow(
- *   id = "entity:user",
- * )
  */
+#[ViewsRow("entity:user")]
 class UserRow extends EntityRow {
 
   /**
diff --git a/core/modules/views/src/Attribute/ViewsRow.php b/core/modules/views/src/Attribute/ViewsRow.php
new file mode 100644
index 0000000000000000000000000000000000000000..378188c6b23c97bdce2f9788dcaaae3e7b461c2d
--- /dev/null
+++ b/core/modules/views/src/Attribute/ViewsRow.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\views\Attribute;
+
+use Drupal\Component\Plugin\Attribute\Plugin;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Defines a ViewsRow attribute for plugin discovery.
+ *
+ * @see \Drupal\views\Plugin\views\style\StylePluginBase
+ *
+ * @ingroup views_row_plugins
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class ViewsRow extends Plugin {
+
+  /**
+   * Constructs an ViewsRow attribute.
+   *
+   * @param string $id
+   *   The plugin ID.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $title
+   *   (optional) The plugin title used in the views UI.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $short_title
+   *   (optional) The short title used in the views UI.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $help
+   *   (optional) A short help string; this is displayed in the views UI.
+   * @param string[] $display_types
+   *   (optional) The types of the display this plugin can be used with.
+   *   For example the Feed display defines the type 'feed', so only rss style
+   *   and row plugins can be used in the views UI.
+   * @param string[] $base
+   *   (optional) The base tables on which this style plugin can be used.
+   *   If no base table is specified the plugin can be used with all tables.
+   * @param string|null $theme
+   *   (optional) The theme function used to render the style output.
+   * @param bool $no_ui
+   *   (optional) Whether the plugin should be not selectable in the UI.
+   *   If set to TRUE, you can still use it via the API in config files.
+   *   Defaults to FALSE.
+   * @param bool $register_theme
+   *   (optional) Whether to register a theme function automatically. Defaults
+   *   to TRUE.
+   * @param class-string|null $deriver
+   *   (optional) The deriver class.
+   */
+  public function __construct(
+    public readonly string $id,
+    public readonly ?TranslatableMarkup $title = NULL,
+    public readonly ?TranslatableMarkup $short_title = NULL,
+    public readonly ?TranslatableMarkup $help = NULL,
+    public readonly array $display_types = [],
+    public readonly array $base = [],
+    public readonly ?string $theme = NULL,
+    public readonly bool $no_ui = FALSE,
+    public readonly bool $register_theme = TRUE,
+    public readonly ?string $deriver = NULL
+  ) {}
+
+}
diff --git a/core/modules/views/src/Plugin/views/row/EntityReference.php b/core/modules/views/src/Plugin/views/row/EntityReference.php
index 29661e3e08eab1dcafe7838aaa165517d46b7c57..222edef473526578220fc9491277b9ed07b7a5c3 100644
--- a/core/modules/views/src/Plugin/views/row/EntityReference.php
+++ b/core/modules/views/src/Plugin/views/row/EntityReference.php
@@ -3,21 +3,22 @@
 namespace Drupal\views\Plugin\views\row;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 
 /**
  * EntityReference row plugin.
  *
  * @ingroup views_row_plugins
- *
- * @ViewsRow(
- *   id = "entity_reference",
- *   title = @Translation("Entity Reference inline fields"),
- *   help = @Translation("Displays the fields with an optional template."),
- *   theme = "views_view_fields",
- *   register_theme = FALSE,
- *   display_types = {"entity_reference"}
- * )
  */
+#[ViewsRow(
+  id: "entity_reference",
+  title: new TranslatableMarkup("Entity Reference inline fields"),
+  help: new TranslatableMarkup("Displays the fields with an optional template."),
+  theme: "views_view_fields",
+  register_theme: FALSE,
+  display_types: ["entity_reference"]
+)]
 class EntityReference extends Fields {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/row/EntityRow.php b/core/modules/views/src/Plugin/views/row/EntityRow.php
index bbcdee8983aa2c326e1326fbf3b40f941bdf1e1b..210e92caa736da64e34ee56be1945c3f4a1cc5f4 100644
--- a/core/modules/views/src/Plugin/views/row/EntityRow.php
+++ b/core/modules/views/src/Plugin/views/row/EntityRow.php
@@ -7,19 +7,20 @@
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Entity\Render\EntityTranslationRenderTrait;
+use Drupal\views\Plugin\Derivative\ViewsEntityRow;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\ViewExecutable;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Generic entity row plugin to provide a common base for all entity types.
- *
- * @ViewsRow(
- *   id = "entity",
- *   deriver = "Drupal\views\Plugin\Derivative\ViewsEntityRow"
- * )
  */
+#[ViewsRow(
+  id: "entity",
+  deriver: ViewsEntityRow::class
+)]
 class EntityRow extends RowPluginBase {
   use EntityTranslationRenderTrait;
 
diff --git a/core/modules/views/src/Plugin/views/row/Fields.php b/core/modules/views/src/Plugin/views/row/Fields.php
index 647ddce89fc6678707229c0b18ee2da5883cb8ea..2687b34ab79adbaa811eb3c9d4953bd54400b8b3 100644
--- a/core/modules/views/src/Plugin/views/row/Fields.php
+++ b/core/modules/views/src/Plugin/views/row/Fields.php
@@ -3,6 +3,8 @@
 namespace Drupal\views\Plugin\views\row;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 
 /**
  * The basic 'fields' row plugin.
@@ -11,15 +13,14 @@
  * or not.
  *
  * @ingroup views_row_plugins
- *
- * @ViewsRow(
- *   id = "fields",
- *   title = @Translation("Fields"),
- *   help = @Translation("Displays the fields with an optional template."),
- *   theme = "views_view_fields",
- *   display_types = {"normal"}
- * )
  */
+#[ViewsRow(
+  id: "fields",
+  title: new TranslatableMarkup("Fields"),
+  help: new TranslatableMarkup("Displays the fields with an optional template."),
+  theme: "views_view_fields",
+  display_types: ["normal"]
+)]
 class Fields extends RowPluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/row/OpmlFields.php b/core/modules/views/src/Plugin/views/row/OpmlFields.php
index c5116f3fbd49452129e79bb16a05ca209360fbab..ca88dbc6d826b832d4c276c318b7696b407642d1 100644
--- a/core/modules/views/src/Plugin/views/row/OpmlFields.php
+++ b/core/modules/views/src/Plugin/views/row/OpmlFields.php
@@ -3,18 +3,19 @@
 namespace Drupal\views\Plugin\views\row;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 
 /**
  * Renders an OPML item based on fields.
- *
- * @ViewsRow(
- *   id = "opml_fields",
- *   title = @Translation("OPML fields"),
- *   help = @Translation("Display fields as OPML items."),
- *   theme = "views_view_row_opml",
- *   display_types = {"feed"}
- * )
  */
+#[ViewsRow(
+  id: "opml_fields",
+  title: new TranslatableMarkup("OPML fields"),
+  help: new TranslatableMarkup("Display fields as OPML items."),
+  theme: "views_view_row_opml",
+  display_types: ["feed"]
+)]
 class OpmlFields extends RowPluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/row/RowPluginBase.php b/core/modules/views/src/Plugin/views/row/RowPluginBase.php
index d3deb5b94e2a14596936d27b398326b21debefa6..445aa54e248931396417c84e8a11bd360b0369f2 100644
--- a/core/modules/views/src/Plugin/views/row/RowPluginBase.php
+++ b/core/modules/views/src/Plugin/views/row/RowPluginBase.php
@@ -21,7 +21,7 @@
  * more information.
  *
  * Row plugins extend \Drupal\views\Plugin\views\row\RowPluginBase. They must
- * be annotated with \Drupal\views\Annotation\ViewsRow annotation, and
+ * be attributed with \Drupal\views\Attribute\ViewsRow attribute, and
  * they must be in namespace directory Plugin\views\row.
  *
  * @ingroup views_plugins
diff --git a/core/modules/views/src/Plugin/views/row/RssFields.php b/core/modules/views/src/Plugin/views/row/RssFields.php
index 924cd7b6e2879c76be72d75de0abc299f7611e2a..64cb267b5c2853f2d8fd058aa0696dd7741a0afd 100644
--- a/core/modules/views/src/Plugin/views/row/RssFields.php
+++ b/core/modules/views/src/Plugin/views/row/RssFields.php
@@ -3,19 +3,20 @@
 namespace Drupal\views\Plugin\views\row;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
+use Drupal\views\Attribute\ViewsRow;
 
 /**
  * Renders an RSS item based on fields.
- *
- * @ViewsRow(
- *   id = "rss_fields",
- *   title = @Translation("Fields"),
- *   help = @Translation("Display fields as RSS items."),
- *   theme = "views_view_row_rss",
- *   display_types = {"feed"}
- * )
  */
+#[ViewsRow(
+  id: "rss_fields",
+  title: new TranslatableMarkup("Fields"),
+  help: new TranslatableMarkup("Display fields as RSS items."),
+  theme: "views_view_row_rss",
+  display_types: ["feed"]
+)]
 class RssFields extends RowPluginBase {
 
   /**
diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/row/RowTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/row/RowTest.php
index 6af493b1e778c63fb0ec69b1fd401f8786556e11..248abc13afd1c51cab0cd1edd58811c886ec66c9 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/row/RowTest.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/row/RowTest.php
@@ -3,21 +3,22 @@
 namespace Drupal\views_test_data\Plugin\views\row;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsRow;
 use Drupal\views\Plugin\views\row\RowPluginBase;
 
 /**
  * Provides a general test row plugin.
  *
  * @ingroup views_row_plugins
- *
- * @ViewsRow(
- *   id = "test_row",
- *   title = @Translation("Test row plugin"),
- *   help = @Translation("Provides a generic row test plugin."),
- *   theme = "views_view_row_test",
- *   display_types = {"normal", "test"}
- * )
  */
+#[ViewsRow(
+  id: "test_row",
+  title: new TranslatableMarkup("Test row plugin"),
+  help: new TranslatableMarkup("Provides a generic row test plugin."),
+  theme: "views_view_row_test",
+  display_types: ["normal", "test"]
+)]
 class RowTest extends RowPluginBase {
 
   /**