diff --git a/core/modules/rest/src/Plugin/views/style/Serializer.php b/core/modules/rest/src/Plugin/views/style/Serializer.php
index b2dc484b977f2c6e26ce4cb40e9f422b82ecc6bc..abdd42c0235e6d0075316f0721364df352561670 100644
--- a/core/modules/rest/src/Plugin/views/style/Serializer.php
+++ b/core/modules/rest/src/Plugin/views/style/Serializer.php
@@ -5,6 +5,8 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 use Drupal\views\Plugin\views\style\StylePluginBase;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Serializer\SerializerInterface;
@@ -13,14 +15,13 @@
  * The style plugin for serialized output formats.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "serializer",
- *   title = @Translation("Serializer"),
- *   help = @Translation("Serializes views row data using the Serializer component."),
- *   display_types = {"data"}
- * )
  */
+#[ViewsStyle(
+  id: "serializer",
+  title: new TranslatableMarkup("Serializer"),
+  help: new TranslatableMarkup("Serializes views row data using the Serializer component."),
+  display_types: ["data"],
+)]
 class Serializer extends StylePluginBase implements CacheableDependencyInterface {
 
   /**
diff --git a/core/modules/views/src/Attribute/ViewsStyle.php b/core/modules/views/src/Attribute/ViewsStyle.php
new file mode 100644
index 0000000000000000000000000000000000000000..36f6f67079f5420cf01380ea92eacf5e68e3c8a1
--- /dev/null
+++ b/core/modules/views/src/Attribute/ViewsStyle.php
@@ -0,0 +1,62 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\views\Attribute;
+
+use Drupal\Component\Plugin\Attribute\Plugin;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+
+/**
+ * Defines a views style plugins type attribute for plugin discovery.
+ *
+ * @see \Drupal\views\Plugin\views\style\StylePluginBase
+ *
+ * @ingroup views_style_plugins
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class ViewsStyle extends Plugin {
+
+  /**
+   * Constructs a ViewsStyle attribute.
+   *
+   * @param string $id
+   *   The plugin ID.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $title
+   *   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|null $theme
+   *   (optional) The theme function used to render the style output.
+   * @param string[] $display_types
+   *   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 access plugin can be used.
+   *   If no base table is specified the plugin can be used with all tables.
+   * @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 or not to register a theme function automatically.
+   * @param class-string|null $deriver
+   *   (optional) The deriver class.
+   */
+  public function __construct(
+    public readonly string $id,
+    public readonly TranslatableMarkup $title,
+    public readonly ?TranslatableMarkup $short_title = NULL,
+    public readonly ?TranslatableMarkup $help = NULL,
+    public readonly ?string $theme = NULL,
+    public readonly array $display_types = [],
+    public readonly array $base = [],
+    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/style/DefaultStyle.php b/core/modules/views/src/Plugin/views/style/DefaultStyle.php
index 7c6afea00e4f121286d56c6ce01d0909f6d014fe..cefd802436772086983f734ac6a1cfee6b0ce379 100644
--- a/core/modules/views/src/Plugin/views/style/DefaultStyle.php
+++ b/core/modules/views/src/Plugin/views/style/DefaultStyle.php
@@ -2,21 +2,23 @@
 
 namespace Drupal\views\Plugin\views\style;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
+
 /**
  * Unformatted style plugin to render rows.
  *
  * Row are rendered one after another with no decorations.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "default",
- *   title = @Translation("Unformatted list"),
- *   help = @Translation("Displays rows one after another."),
- *   theme = "views_view_unformatted",
- *   display_types = {"normal"}
- * )
  */
+#[ViewsStyle(
+  id: "default",
+  title: new TranslatableMarkup("Unformatted list"),
+  help: new TranslatableMarkup("Displays rows one after another."),
+  theme: "views_view_unformatted",
+  display_types: ["normal"],
+)]
 class DefaultStyle extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/DefaultSummary.php b/core/modules/views/src/Plugin/views/style/DefaultSummary.php
index 0b4325bcc93fc42ac0829fdcbfacdc9931fec24b..3df57c2e827c8b6593d450b26b4009f506869106 100644
--- a/core/modules/views/src/Plugin/views/style/DefaultSummary.php
+++ b/core/modules/views/src/Plugin/views/style/DefaultSummary.php
@@ -3,20 +3,21 @@
 namespace Drupal\views\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * The default style plugin for summaries.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "default_summary",
- *   title = @Translation("List"),
- *   help = @Translation("Displays the default summary as a list."),
- *   theme = "views_view_summary",
- *   display_types = {"summary"}
- * )
  */
+#[ViewsStyle(
+  id: "default_summary",
+  title: new TranslatableMarkup("List"),
+  help: new TranslatableMarkup("Displays the default summary as a list."),
+  theme: "views_view_summary",
+  display_types: ["summary"],
+)]
 class DefaultSummary extends StylePluginBase {
 
   protected function defineOptions() {
diff --git a/core/modules/views/src/Plugin/views/style/EntityReference.php b/core/modules/views/src/Plugin/views/style/EntityReference.php
index e07a0f1858998d61ce539d5a79ea3bf272dc290f..d847c5a9dc2e38b005a480e757df54da8a9d3ff8 100644
--- a/core/modules/views/src/Plugin/views/style/EntityReference.php
+++ b/core/modules/views/src/Plugin/views/style/EntityReference.php
@@ -4,21 +4,22 @@
 
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * EntityReference style plugin.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "entity_reference",
- *   title = @Translation("Entity Reference list"),
- *   help = @Translation("Returns results as a PHP array of labels and rendered rows."),
- *   theme = "views_view_unformatted",
- *   register_theme = FALSE,
- *   display_types = {"entity_reference"}
- * )
  */
+#[ViewsStyle(
+  id: "entity_reference",
+  title: new TranslatableMarkup("Entity Reference list"),
+  help: new TranslatableMarkup("Returns results as a PHP array of labels and rendered rows."),
+  theme: "views_view_unformatted",
+  register_theme: FALSE,
+  display_types: ["entity_reference"],
+)]
 class EntityReference extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/Grid.php b/core/modules/views/src/Plugin/views/style/Grid.php
index 8f834dd4b87fa96bf87ae4b5571da3f4d97a9af7..6e6df8532bf8d9d153fc402660d7cf2667897f14 100644
--- a/core/modules/views/src/Plugin/views/style/Grid.php
+++ b/core/modules/views/src/Plugin/views/style/Grid.php
@@ -4,20 +4,21 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * Style plugin to render each item in a grid cell.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "grid",
- *   title = @Translation("Grid"),
- *   help = @Translation("Displays rows in a grid."),
- *   theme = "views_view_grid",
- *   display_types = {"normal"}
- * )
  */
+#[ViewsStyle(
+  id: "grid",
+  title: new TranslatableMarkup("Grid"),
+  help: new TranslatableMarkup("Displays rows in a grid."),
+  theme: "views_view_grid",
+  display_types: ["normal"],
+)]
 class Grid extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/GridResponsive.php b/core/modules/views/src/Plugin/views/style/GridResponsive.php
index f9b9b820c54b58d5d84ed9714a74df6dd8d86fb6..15dad5fa0f69e8a19565391369981e58ce5cc19a 100644
--- a/core/modules/views/src/Plugin/views/style/GridResponsive.php
+++ b/core/modules/views/src/Plugin/views/style/GridResponsive.php
@@ -3,20 +3,21 @@
 namespace Drupal\views\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * Style plugin to render each item in a responsive grid cell.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "grid_responsive",
- *   title = @Translation("Responsive Grid"),
- *   help = @Translation("Displays rows in a responsive grid."),
- *   theme = "views_view_grid_responsive",
- *   display_types = {"normal"}
- * )
  */
+#[ViewsStyle(
+  id: "grid_responsive",
+  title: new TranslatableMarkup("Responsive Grid"),
+  help: new TranslatableMarkup("Displays rows in a responsive grid."),
+  theme: "views_view_grid_responsive",
+  display_types: ["normal"],
+)]
 class GridResponsive extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/HtmlList.php b/core/modules/views/src/Plugin/views/style/HtmlList.php
index 572855564fcaa194904348950d59fd449a05ccb2..8641f53ca7ee1321213f0e88493d4e2086bbff3a 100644
--- a/core/modules/views/src/Plugin/views/style/HtmlList.php
+++ b/core/modules/views/src/Plugin/views/style/HtmlList.php
@@ -3,20 +3,21 @@
 namespace Drupal\views\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * Style plugin to render each item in an ordered or unordered list.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "html_list",
- *   title = @Translation("HTML List"),
- *   help = @Translation("Displays rows as HTML list."),
- *   theme = "views_view_list",
- *   display_types = {"normal"}
- * )
  */
+#[ViewsStyle(
+  id: "html_list",
+  title: new TranslatableMarkup("HTML List"),
+  help: new TranslatableMarkup("Displays rows as HTML list."),
+  theme: "views_view_list",
+  display_types: ["normal"],
+)]
 class HtmlList extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/Opml.php b/core/modules/views/src/Plugin/views/style/Opml.php
index e59051dba68d433753ec24a139b8e752cec5022b..378825224a07d59592e8c1059ac5bd25c6483dfb 100644
--- a/core/modules/views/src/Plugin/views/style/Opml.php
+++ b/core/modules/views/src/Plugin/views/style/Opml.php
@@ -2,21 +2,22 @@
 
 namespace Drupal\views\Plugin\views\style;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * Default style plugin to render an OPML feed.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "opml",
- *   title = @Translation("OPML Feed"),
- *   help = @Translation("Generates an OPML feed from a view."),
- *   theme = "views_view_opml",
- *   display_types = {"feed"}
- * )
  */
+#[ViewsStyle(
+  id: "opml",
+  title: new TranslatableMarkup("OPML Feed"),
+  help: new TranslatableMarkup("Generates an OPML feed from a view."),
+  theme: "views_view_opml",
+  display_types: ["feed"],
+)]
 class Opml extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/Rss.php b/core/modules/views/src/Plugin/views/style/Rss.php
index 51394feda329fc29d3013b1d168c680aad8d7164..b39bbc4dd462dcfd35cb7e0ad55de916ebbc65a5 100644
--- a/core/modules/views/src/Plugin/views/style/Rss.php
+++ b/core/modules/views/src/Plugin/views/style/Rss.php
@@ -3,21 +3,22 @@
 namespace Drupal\views\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * Default style plugin to render an RSS feed.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "rss",
- *   title = @Translation("RSS Feed"),
- *   help = @Translation("Generates an RSS feed from a view."),
- *   theme = "views_view_rss",
- *   display_types = {"feed"}
- * )
  */
+#[ViewsStyle(
+  id: "rss",
+  title: new TranslatableMarkup("RSS Feed"),
+  help: new TranslatableMarkup("Generates an RSS feed from a view."),
+  theme: "views_view_rss",
+  display_types: ["feed"],
+)]
 class Rss extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/Table.php b/core/modules/views/src/Plugin/views/style/Table.php
index afca869af34461a5107e871821d7d2f0c416c920..078c63d8a86058649a074ec191e7f2357af99b71 100644
--- a/core/modules/views/src/Plugin/views/style/Table.php
+++ b/core/modules/views/src/Plugin/views/style/Table.php
@@ -6,21 +6,22 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheableDependencyInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 use Drupal\views\Plugin\views\wizard\WizardInterface;
 
 /**
  * Style plugin to render each item as a row in a table.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "table",
- *   title = @Translation("Table"),
- *   help = @Translation("Displays rows in a table."),
- *   theme = "views_view_table",
- *   display_types = {"normal"}
- * )
  */
+#[ViewsStyle(
+  id: "table",
+  title: new TranslatableMarkup("Table"),
+  help: new TranslatableMarkup("Displays rows in a table."),
+  theme: "views_view_table",
+  display_types: ["normal"],
+)]
 class Table extends StylePluginBase implements CacheableDependencyInterface {
 
   /**
diff --git a/core/modules/views/src/Plugin/views/style/UnformattedSummary.php b/core/modules/views/src/Plugin/views/style/UnformattedSummary.php
index 6460841afb2df78d63b9081728671c203ab2a219..4ae4e96ef1c9b05dfcec4ae3d0ff29b72a49ebfa 100644
--- a/core/modules/views/src/Plugin/views/style/UnformattedSummary.php
+++ b/core/modules/views/src/Plugin/views/style/UnformattedSummary.php
@@ -3,20 +3,21 @@
 namespace Drupal\views\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 
 /**
  * The default style plugin for summaries.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "unformatted_summary",
- *   title = @Translation("Unformatted"),
- *   help = @Translation("Displays the summary unformatted, with option for one after another or inline."),
- *   theme = "views_view_summary_unformatted",
- *   display_types = {"summary"}
- * )
  */
+#[ViewsStyle(
+  id: "unformatted_summary",
+  title: new TranslatableMarkup("Unformatted"),
+  help: new TranslatableMarkup("Displays the summary unformatted, with option for one after another or inline."),
+  theme: "views_view_summary_unformatted",
+  display_types: ["summary"],
+)]
 class UnformattedSummary extends DefaultSummary {
 
   protected function defineOptions() {
diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/MappingTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/MappingTest.php
index 3ae816a8d62b3a6c5402db4d367d2d6ae38be327..00ae9b3592499edb39d2f5a7f1f15b26b7675f39 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/MappingTest.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/MappingTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\views_test_data\Plugin\views\style;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 use Drupal\views\Plugin\views\style\Mapping;
 use Drupal\views\Plugin\views\field\NumericField;
 
@@ -9,15 +11,14 @@
  * Provides a test plugin for the mapping style.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "mapping_test",
- *   title = @Translation("Field mapping"),
- *   help = @Translation("Maps specific fields to specific purposes."),
- *   theme = "views_view_mapping_test",
- *   display_types = {"normal", "test"}
- * )
  */
+#[ViewsStyle(
+  id: "mapping_test",
+  title: new TranslatableMarkup("Field mapping"),
+  help: new TranslatableMarkup("Maps specific fields to specific purposes."),
+  theme: "views_view_mapping_test",
+  display_types: ["normal", "test"],
+)]
 class MappingTest extends Mapping {
 
   /**
diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTemplateTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTemplateTest.php
index b924d90e2a2322d78ee0f9a15ba9bae48b6fb0e9..d706072ca694362c44f97d3829ecd33292e91bdb 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTemplateTest.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTemplateTest.php
@@ -2,21 +2,22 @@
 
 namespace Drupal\views_test_data\Plugin\views\style;
 
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 use Drupal\views\Plugin\views\style\StylePluginBase;
 
 /**
  * Provides a general test style template plugin.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "test_template_style",
- *   title = @Translation("Test style template plugin"),
- *   help = @Translation("Provides a generic style template test plugin."),
- *   theme = "views_view_style_template_test",
- *   display_types = {"normal", "test"}
- * )
  */
+#[ViewsStyle(
+  id: "test_template_style",
+  title: new TranslatableMarkup("Test style template plugin"),
+  help: new TranslatableMarkup("Provides a generic style template test plugin."),
+  theme: "views_view_style_template_test",
+  display_types: ["normal", "test"],
+)]
 class StyleTemplateTest extends StylePluginBase {
 
   /**
diff --git a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTest.php b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTest.php
index fa6f57f60d482996880001154ecc652dcbff169e..0da33ad37437994aee29fecce133740b75279cba 100644
--- a/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTest.php
+++ b/core/modules/views/tests/modules/views_test_data/src/Plugin/views/style/StyleTest.php
@@ -3,22 +3,23 @@
 namespace Drupal\views_test_data\Plugin\views\style;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\views\Attribute\ViewsStyle;
 use Drupal\views\Plugin\views\style\StylePluginBase;
 
 /**
  * Provides a general test style plugin.
  *
  * @ingroup views_style_plugins
- *
- * @ViewsStyle(
- *   id = "test_style",
- *   title = @Translation("Test style plugin"),
- *   help = @Translation("Provides a generic style test plugin."),
- *   theme = "views_view_style_test",
- *   register_theme = FALSE,
- *   display_types = {"normal", "test"}
- * )
  */
+#[ViewsStyle(
+  id: "test_style",
+  title: new TranslatableMarkup("Test style plugin"),
+  help: new TranslatableMarkup("Provides a generic style test plugin."),
+  theme: "views_view_style_test",
+  register_theme: FALSE,
+  display_types: ["normal", "test"],
+)]
 class StyleTest extends StylePluginBase {
 
   /**