diff --git a/core/lib/Drupal/Core/Layout/Attribute/Layout.php b/core/lib/Drupal/Core/Layout/Attribute/Layout.php
new file mode 100644
index 0000000000000000000000000000000000000000..11bf8a206cf663aaddbd1dc938d36d86d57e01e5
--- /dev/null
+++ b/core/lib/Drupal/Core/Layout/Attribute/Layout.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\Core\Layout\Attribute;
+
+use Drupal\Component\Plugin\Attribute\Plugin;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\Layout\LayoutDefinition;
+
+/**
+ * Defines a Layout attribute object.
+ *
+ * Layouts are used to define a list of regions and then output render arrays
+ * in each of the regions, usually using a template.
+ *
+ * Plugin Namespace: Plugin\Layout
+ *
+ * @see \Drupal\Core\Layout\LayoutInterface
+ * @see \Drupal\Core\Layout\LayoutDefault
+ * @see \Drupal\Core\Layout\LayoutPluginManager
+ * @see plugin_api
+ */
+#[\Attribute(\Attribute::TARGET_CLASS)]
+class Layout extends Plugin {
+
+  /**
+   * Any additional properties and values.
+   *
+   * @see \Drupal\Core\Layout\LayoutDefinition::$additional
+   *
+   * @var array
+   */
+  public readonly array $additional;
+
+  /**
+   * Constructs a Layout attribute.
+   *
+   * @param string $id
+   *   The plugin ID.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $label
+   *   (optional) The human-readable name. @todo Deprecate optional label in
+   *   https://www.drupal.org/project/drupal/issues/3392572.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $category
+   *   (optional) The human-readable category. @todo Deprecate optional category
+   *   in https://www.drupal.org/project/drupal/issues/3392572.
+   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $description
+   *   (optional) The description for advanced layouts.
+   * @param string|null $template
+   *   (optional) The template file to render the layout.
+   * @param string $theme_hook
+   *   (optional) The template hook to render the layout.
+   * @param string|null $path
+   *   (optional) Path (relative to the module or theme) to resources like icon or template.
+   * @param string|null $library
+   *   (optional) The asset library.
+   * @param string|null $icon
+   *   (optional) The path to the preview image (relative to the 'path' given).
+   * @param string[][]|null $icon_map
+   *   (optional) The icon map.
+   * @param array $regions
+   *   (optional) An associative array of regions in this layout.
+   * @param string|null $default_region
+   *   (optional) The default region.
+   * @param class-string $class
+   *   (optional) The layout plugin class.
+   * @param \Drupal\Core\Plugin\Context\ContextDefinitionInterface[] $context_definitions
+   *   (optional) The context definition.
+   * @param array $config_dependencies
+   *   (optional) The config dependencies.
+   * @param class-string|null $deriver
+   *   (optional) The deriver class.
+   * @param mixed $additional
+   *   (optional) Additional properties passed in that can be used by a deriver.
+   */
+  public function __construct(
+    public readonly string $id,
+    public readonly ?TranslatableMarkup $label = NULL,
+    public readonly ?TranslatableMarkup $category = NULL,
+    public readonly ?TranslatableMarkup $description = NULL,
+    public readonly ?string $template = NULL,
+    public readonly string $theme_hook = 'layout',
+    public readonly ?string $path = NULL,
+    public readonly ?string $library = NULL,
+    public readonly ?string $icon = NULL,
+    public readonly ?array $icon_map = NULL,
+    public readonly array $regions = [],
+    public readonly ?string $default_region = NULL,
+    public string $class = LayoutDefault::class,
+    public readonly array $context_definitions = [],
+    public readonly array $config_dependencies = [],
+    public readonly ?string $deriver = NULL,
+    ...$additional,
+  ) {
+    // Layout definitions support arbitrary properties being passed in, which
+    // are stored in the 'additional' property in LayoutDefinition. The variadic
+    // 'additional' parameter here saves arbitrary parameters passed into the
+    // 'additional' property in this attribute class. The 'additional' property
+    // gets passed to the LayoutDefinition constructor in ::get().
+    // @see \Drupal\Core\Layout\LayoutDefinition::$additional
+    // @see \Drupal\Core\Layout\LayoutDefinition::get()
+    $this->additional = $additional;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get(): LayoutDefinition {
+    return new LayoutDefinition(parent::get());
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Layout/LayoutDefinition.php b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
index 9936fcaa0d9d743f22f7fc5e8edb0670444f9c75..11cbf51436626c4a0916ab8f2985b882a511f1cc 100644
--- a/core/lib/Drupal/Core/Layout/LayoutDefinition.php
+++ b/core/lib/Drupal/Core/Layout/LayoutDefinition.php
@@ -132,7 +132,7 @@ class LayoutDefinition extends PluginDefinition implements PluginDefinitionInter
    * LayoutDefinition constructor.
    *
    * @param array $definition
-   *   An array of values from the annotation.
+   *   An array of values from the attribute.
    */
   public function __construct(array $definition) {
     // If there are context definitions in the plugin definition, they should
diff --git a/core/lib/Drupal/Core/Layout/LayoutPluginManager.php b/core/lib/Drupal/Core/Layout/LayoutPluginManager.php
index 2265194bf30bd2bd08b3c7ce8dadd2390ff047f4..5e8f2f3b2ae69756d6ef71330b3b257528ef23c1 100644
--- a/core/lib/Drupal/Core/Layout/LayoutPluginManager.php
+++ b/core/lib/Drupal/Core/Layout/LayoutPluginManager.php
@@ -2,16 +2,16 @@
 
 namespace Drupal\Core\Layout;
 
-use Drupal\Component\Annotation\Plugin\Discovery\AnnotationBridgeDecorator;
+use Drupal\Component\Plugin\Discovery\AttributeBridgeDecorator;
 use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Plugin\DefaultPluginManager;
-use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\AttributeDiscoveryWithAnnotations;
 use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
 use Drupal\Core\Plugin\Discovery\YamlDiscoveryDecorator;
-use Drupal\Core\Layout\Annotation\Layout;
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Plugin\FilteredPluginManagerTrait;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 
@@ -43,7 +43,7 @@ class LayoutPluginManager extends DefaultPluginManager implements LayoutPluginMa
    *   The theme handler to invoke the alter hook with.
    */
   public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler) {
-    parent::__construct('Plugin/Layout', $namespaces, $module_handler, LayoutInterface::class, Layout::class);
+    parent::__construct('Plugin/Layout', $namespaces, $module_handler, LayoutInterface::class, Layout::class, 'Drupal\Core\Layout\Annotation\Layout');
     $this->themeHandler = $theme_handler;
 
     $type = $this->getType();
@@ -70,13 +70,13 @@ protected function providerExists($provider) {
    */
   protected function getDiscovery() {
     if (!$this->discovery) {
-      $discovery = new AnnotatedClassDiscovery($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces);
+      $discovery = new AttributeDiscoveryWithAnnotations($this->subdir, $this->namespaces, $this->pluginDefinitionAttributeName, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces);
       $discovery = new YamlDiscoveryDecorator($discovery, 'layouts', $this->moduleHandler->getModuleDirectories() + $this->themeHandler->getThemeDirectories());
       $discovery
         ->addTranslatableProperty('label')
         ->addTranslatableProperty('description')
         ->addTranslatableProperty('category');
-      $discovery = new AnnotationBridgeDecorator($discovery, $this->pluginDefinitionAnnotationName);
+      $discovery = new AttributeBridgeDecorator($discovery, $this->pluginDefinitionAttributeName);
       $discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
       $this->discovery = $discovery;
     }
diff --git a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php
index c283c82d78753f25a8946bff6ecaacf27301683f..75d7e384f5f91825bc2a5780bfc50ffe0877ecc7 100644
--- a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php
+++ b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutContentFooter.php
@@ -2,26 +2,27 @@
 
 namespace Drupal\field_layout_test\Plugin\Layout;
 
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
- * Provides an annotated layout plugin for field_layout tests.
- *
- * @Layout(
- *   id = "test_layout_content_and_footer",
- *   label = @Translation("Test plugin: Content and Footer"),
- *   category = @Translation("Layout test"),
- *   description = @Translation("Test layout"),
- *   regions = {
- *     "content" = {
- *       "label" = @Translation("Content Region")
- *     },
- *     "footer" = {
- *       "label" = @Translation("Footer Region")
- *     }
- *   },
- * )
+ * Provides a Layout plugin for field_layout tests.
  */
+#[Layout(
+  id: 'test_layout_content_and_footer',
+  label: new TranslatableMarkup('Test plugin: Content and Footer'),
+  category: new TranslatableMarkup('Layout test'),
+  description: new TranslatableMarkup('Test layout'),
+  regions: [
+    "content" => [
+      "label" => new TranslatableMarkup("Content Region"),
+    ],
+    "footer" => [
+      "label" => new TranslatableMarkup("Footer Region"),
+    ],
+  ],
+)]
 class TestLayoutContentFooter extends LayoutDefault {
 
 }
diff --git a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php
index b01ec4518751b3b4391936009e4b2f1b71c829dc..9a0df30325f4a1201b8cd361a8a226dcd8fe5da3 100644
--- a/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php
+++ b/core/modules/field_layout/tests/modules/field_layout_test/src/Plugin/Layout/TestLayoutMainFooter.php
@@ -2,31 +2,32 @@
 
 namespace Drupal\field_layout_test\Plugin\Layout;
 
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
- * Provides an annotated layout plugin for field_layout tests.
- *
- * @Layout(
- *   id = "test_layout_main_and_footer",
- *   label = @Translation("Test plugin: Main and Footer"),
- *   category = @Translation("Layout test"),
- *   description = @Translation("Test layout"),
- *   regions = {
- *     "main" = {
- *       "label" = @Translation("Main Region")
- *     },
- *     "footer" = {
- *       "label" = @Translation("Footer Region")
- *     }
- *   },
- *   config_dependencies = {
- *     "module" = {
- *       "layout_discovery",
- *     },
- *   },
- * )
+ * Provides an attributed layout plugin for field_layout tests.
  */
+#[Layout(
+  id: 'test_layout_main_and_footer',
+  label: new TranslatableMarkup('Test plugin: Main and Footer'),
+  category: new TranslatableMarkup('Layout test'),
+  description: new TranslatableMarkup('Test layout'),
+  regions: [
+    "main" => [
+      "label" => new TranslatableMarkup("Main Region"),
+    ],
+    "footer" => [
+      "label" => new TranslatableMarkup("Footer Region"),
+    ],
+  ],
+  config_dependencies: [
+    "module" => [
+      "layout_discovery",
+    ],
+  ],
+)]
 class TestLayoutMainFooter extends LayoutDefault {
 
   /**
diff --git a/core/modules/layout_builder/layout_builder.api.php b/core/modules/layout_builder/layout_builder.api.php
index 66afc1fb175a49d6d475bc6a9f57e46937fcc915..d7e84c6a26b4acb672b025af83ae58fa1ea11b18 100644
--- a/core/modules/layout_builder/layout_builder.api.php
+++ b/core/modules/layout_builder/layout_builder.api.php
@@ -15,7 +15,7 @@
  *
  * By default, the Layout Builder access check requires the 'configure any
  * layout' permission. Individual section storage plugins may override this by
- * setting the 'handles_permission_check' annotation key to TRUE. Any section
+ * setting the 'handles_permission_check' attribute key to TRUE. Any section
  * storage plugin that uses 'handles_permission_check' must provide its own
  * complete routing access checking to avoid any access bypasses.
  *
diff --git a/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php b/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php
index a3f71b0ef9c60c123ad4bbbe4e75a9239088ad63..7d5b83e3c8a6c42d930885e35519ebed15a793ec 100644
--- a/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php
+++ b/core/modules/layout_builder/src/Plugin/Layout/BlankLayout.php
@@ -2,7 +2,9 @@
 
 namespace Drupal\layout_builder\Plugin\Layout;
 
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a layout plugin that produces no output.
@@ -13,11 +15,11 @@
  *
  * @internal
  *   This layout plugin is intended for internal use by Layout Builder only.
- *
- * @Layout(
- *   id = "layout_builder_blank",
- * )
  */
+#[Layout(
+  id: 'layout_builder_blank',
+  label: new TranslatableMarkup('Blank'),
+)]
 class BlankLayout extends LayoutDefault {
 
   /**
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php
index 26aafb0d8ed71b2c152430e9e15881c5c5f6fd37..c0b16b56b79e3dc13d5ea4155a642f0b51f7f76d 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutBuilderTestPlugin.php
@@ -2,19 +2,22 @@
 
 namespace Drupal\layout_builder_test\Plugin\Layout;
 
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
- * @Layout(
- *   id = "layout_builder_test_plugin",
- *   label = @Translation("Layout Builder Test Plugin"),
- *   regions = {
- *     "main" = {
- *       "label" = @Translation("Main Region")
- *     }
- *   },
- * )
+ * The Layout Builder Test Plugin.
  */
+#[Layout(
+  id: 'layout_builder_test_plugin',
+  label: new TranslatableMarkup('Layout Builder Test Plugin'),
+  regions: [
+    "main" => [
+      "label" => new TranslatableMarkup("Main Region"),
+    ],
+  ],
+)]
 class LayoutBuilderTestPlugin extends LayoutDefault {
 
   /**
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php
index 9b32bbb1b90e29e0499b8455597ed68573e31ab6..6298ca51941c1e5891fcef2de95d5554383c90ad 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/LayoutWithoutLabel.php
@@ -3,21 +3,22 @@
 namespace Drupal\layout_builder_test\Plugin\Layout;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Layout plugin without a label configuration.
- *
- * @Layout(
- *   id = "layout_without_label",
- *   label = @Translation("Layout Without Label"),
- *   regions = {
- *     "main" = {
- *       "label" = @Translation("Main Region")
- *     }
- *   },
- * )
  */
+#[Layout(
+  id: 'layout_without_label',
+  label: new TranslatableMarkup('Layout Without Label'),
+  regions: [
+    "main" => [
+      "label" => new TranslatableMarkup("Main Region"),
+    ],
+  ],
+)]
 class LayoutWithoutLabel extends LayoutDefault {
 
   /**
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php
index b53e954c87036899295d3701a7fe00f5b555d281..21becc3802471ff269394ea4926acd561e95cc4f 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Plugin/Layout/TestContextAwareLayout.php
@@ -2,22 +2,26 @@
 
 namespace Drupal\layout_builder_test\Plugin\Layout;
 
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\Plugin\Context\EntityContextDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
- * @Layout(
- *   id = "layout_builder_test_context_aware",
- *   label = @Translation("Layout Builder Test: Context Aware"),
- *   regions = {
- *     "main" = {
- *       "label" = @Translation("Main Region")
- *     }
- *   },
- *   context_definitions = {
- *     "user" = @ContextDefinition("entity:user")
- *   }
- * )
+ * The TestContextAwareLayout Class.
  */
+#[Layout(
+  id: 'layout_builder_test_context_aware',
+  label: new TranslatableMarkup('Layout Builder Test: Context Aware'),
+  regions: [
+    "main" => [
+      "label" => new TranslatableMarkup("Main Region"),
+    ],
+  ],
+  context_definitions: [
+    "user" => new EntityContextDefinition("entity:user"),
+  ],
+)]
 class TestContextAwareLayout extends LayoutDefault {
 
   /**
diff --git a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php
index 6ad62101dd96e4ecc78bfdf6e1bcf6323da63bdc..c82d18f46dcb44f1c88066449f9e50f1856204dd 100644
--- a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php
+++ b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestDependenciesPlugin.php
@@ -3,23 +3,24 @@
 namespace Drupal\layout_test\Plugin\Layout;
 
 use Drupal\Component\Plugin\DependentPluginInterface;
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Provides a plugin that contains config dependencies.
- *
- * @Layout(
- *   id = "layout_test_dependencies_plugin",
- *   label = @Translation("Layout plugin (with dependencies)"),
- *   category = @Translation("Layout test"),
- *   description = @Translation("Test layout"),
- *   regions = {
- *     "main" = {
- *       "label" = @Translation("Main Region")
- *     }
- *   }
- * )
  */
+#[Layout(
+  id: 'layout_test_dependencies_plugin',
+  label: new TranslatableMarkup('Layout plugin (with dependencies)'),
+  category: new TranslatableMarkup('Layout test'),
+  description: new TranslatableMarkup('Test layout'),
+  regions: [
+    "main" => [
+      "label" => new TranslatableMarkup("Main Region"),
+    ],
+  ],
+)]
 class LayoutTestDependenciesPlugin extends LayoutDefault implements DependentPluginInterface {
 
   /**
diff --git a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php
index 4598dccbf40a62019d66b1177a00398ace7ca892..7eb3450ec501a02907d8eab09a4464131e8884c8 100644
--- a/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php
+++ b/core/modules/system/tests/modules/layout_test/src/Plugin/Layout/LayoutTestPlugin.php
@@ -3,25 +3,26 @@
 namespace Drupal\layout_test\Plugin\Layout;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Layout\Attribute\Layout;
 use Drupal\Core\Layout\LayoutDefault;
 use Drupal\Core\Plugin\PluginFormInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * The plugin that handles the default layout template.
- *
- * @Layout(
- *   id = "layout_test_plugin",
- *   label = @Translation("Layout plugin (with settings)"),
- *   category = @Translation("Layout test"),
- *   description = @Translation("Test layout"),
- *   template = "templates/layout-test-plugin",
- *   regions = {
- *     "main" = {
- *       "label" = @Translation("Main Region")
- *     }
- *   }
- * )
  */
+#[Layout(
+  id: 'layout_test_plugin',
+  label: new TranslatableMarkup('Layout plugin (with settings)'),
+  category: new TranslatableMarkup('Layout test'),
+  description: new TranslatableMarkup('Test layout'),
+  template: "templates/layout-test-plugin",
+  regions: [
+    "main" => [
+      "label" => new TranslatableMarkup("Main Region"),
+    ],
+  ],
+)]
 class LayoutTestPlugin extends LayoutDefault implements PluginFormInterface {
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php b/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php
index 7ce72081d8e37571c48d5fc0acdb9372fed80d21..7610bf16bf584b133b9b60702c78ba6c5a15d69f 100644
--- a/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/Layout/LayoutPluginManagerTest.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Tests\Core\Layout;
 
+use Composer\Autoload\ClassLoader;
 use Drupal\Component\Plugin\Derivative\DeriverBase;
 use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -20,6 +21,8 @@
 use org\bovigo\vfs\vfsStream;
 use Prophecy\Argument;
 
+// cspell:ignore lorem, ipsum, consectetur, adipiscing
+
 /**
  * @coversDefaultClass \Drupal\Core\Layout\LayoutPluginManager
  * @group Layout
@@ -91,6 +94,9 @@ protected function setUp(): void {
     $this->cacheBackend = $this->prophesize(CacheBackendInterface::class);
 
     $namespaces = new \ArrayObject(['Drupal\Core' => vfsStream::url('root/core/lib/Drupal/Core')]);
+    $class_loader = new ClassLoader();
+    $class_loader->addPsr4("Drupal\\Core\\", vfsStream::url("root/core/lib/Drupal/Core"));
+    $class_loader->register(TRUE);
     $this->layoutPluginManager = new LayoutPluginManager($namespaces, $this->cacheBackend->reveal(), $this->moduleHandler->reveal(), $this->themeHandler->reveal());
   }
 
@@ -103,6 +109,7 @@ public function testGetDefinitions() {
       'module_a_provided_layout',
       'theme_a_provided_layout',
       'plugin_provided_layout',
+      'plugin_provided_by_annotation_layout',
     ];
 
     $layout_definitions = $this->layoutPluginManager->getDefinitions();
@@ -172,6 +179,8 @@ public function testGetDefinition() {
     $this->assertEquals($expected_regions, $regions);
     $this->assertInstanceOf(TranslatableMarkup::class, $regions['top']['label']);
     $this->assertInstanceOf(TranslatableMarkup::class, $regions['bottom']['label']);
+    // Check that arbitrary property value gets set correctly.
+    $this->assertSame('ipsum', $layout_definition->get('lorem'));
 
     $core_path = '/core/lib/Drupal/Core';
     $layout_definition = $this->layoutPluginManager->getDefinition('plugin_provided_layout');
@@ -198,6 +207,37 @@ public function testGetDefinition() {
     $regions = $layout_definition->getRegions();
     $this->assertEquals($expected_regions, $regions);
     $this->assertInstanceOf(TranslatableMarkup::class, $regions['main']['label']);
+    // Check that arbitrary property value gets set correctly.
+    $this->assertSame('adipiscing', $layout_definition->get('consectetur'));
+
+    $layout_definition = $this->layoutPluginManager->getDefinition('plugin_provided_by_annotation_layout');
+    $this->assertSame('plugin_provided_by_annotation_layout', $layout_definition->id());
+    $this->assertEquals('Layout by annotation plugin', $layout_definition->getLabel());
+    $this->assertEquals('Columns: 2', $layout_definition->getCategory());
+    $this->assertEquals('Test layout provided by annotated plugin', $layout_definition->getDescription());
+    $this->assertInstanceOf(TranslatableMarkup::class, $layout_definition->getLabel());
+    $this->assertInstanceOf(TranslatableMarkup::class, $layout_definition->getCategory());
+    $this->assertInstanceOf(TranslatableMarkup::class, $layout_definition->getDescription());
+    $this->assertSame('plugin-provided-annotation-layout', $layout_definition->getTemplate());
+    $this->assertSame($core_path, $layout_definition->getPath());
+    $this->assertNull($layout_definition->getLibrary());
+    $this->assertSame('plugin_provided_annotation_layout', $layout_definition->getThemeHook());
+    $this->assertSame("$core_path/templates", $layout_definition->getTemplatePath());
+    $this->assertSame('core', $layout_definition->getProvider());
+    $this->assertSame('left', $layout_definition->getDefaultRegion());
+    $this->assertSame('Drupal\Core\Plugin\Layout\TestAnnotationLayout', $layout_definition->getClass());
+    $expected_regions = [
+      'left' => [
+        'label' => new TranslatableMarkup('Left Region', [], ['context' => 'layout_region']),
+      ],
+      'right' => [
+        'label' => new TranslatableMarkup('Right Region', [], ['context' => 'layout_region']),
+      ],
+    ];
+    $regions = $layout_definition->getRegions();
+    $this->assertEquals($expected_regions, $regions);
+    $this->assertInstanceOf(TranslatableMarkup::class, $regions['left']['label']);
+    $this->assertInstanceOf(TranslatableMarkup::class, $regions['right']['label']);
   }
 
   /**
@@ -243,6 +283,12 @@ public function testGetThemeImplementations() {
         'template' => 'plugin-provided-layout',
         'path' => "$core_path/templates",
       ],
+      'plugin_provided_annotation_layout' => [
+        'render element' => 'content',
+        'base hook' => 'layout',
+        'template' => 'plugin-provided-annotation-layout',
+        'path' => "$core_path/templates",
+      ],
     ];
     $theme_implementations = $this->layoutPluginManager->getThemeImplementations();
     $this->assertEquals($expected, $theme_implementations);
@@ -264,10 +310,12 @@ public function testGetCategories() {
    * @covers ::getSortedDefinitions
    */
   public function testGetSortedDefinitions() {
+    // Sorted by category first, then label.
     $expected = [
       'module_a_provided_layout',
       'plugin_provided_layout',
       'theme_a_provided_layout',
+      'plugin_provided_by_annotation_layout',
     ];
 
     $layout_definitions = $this->layoutPluginManager->getSortedDefinitions();
@@ -286,6 +334,7 @@ public function testGetGroupedDefinitions() {
       ],
       'Columns: 2' => [
         'theme_a_provided_layout',
+        'plugin_provided_by_annotation_layout',
       ],
     ];
 
@@ -315,7 +364,9 @@ protected function setUpFilesystem() {
       label: Top region
     bottom:
       label: Bottom region
+  lorem: ipsum
 module_a_derived_layout:
+  label: 'Invalid provider derived layout'
   deriver: \Drupal\Tests\Core\Layout\LayoutDeriver
   invalid_provider: true
 EOS;
@@ -338,23 +389,52 @@ class: '\Drupal\Core\Layout\LayoutDefault'
     $plugin_provided_layout = <<<'EOS'
 <?php
 namespace Drupal\Core\Plugin\Layout;
+use Drupal\Core\Layout\Attribute\Layout;
+use Drupal\Core\Layout\LayoutDefault;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+/**
+ * The TestLayout Class.
+ */
+#[Layout(
+  id: 'plugin_provided_layout',
+  label: new TranslatableMarkup('Layout plugin'),
+  category: new TranslatableMarkup('Columns: 1'),
+  description: new TranslatableMarkup('Test layout'),
+  path: "core/lib/Drupal/Core",
+  template: "templates/plugin-provided-layout",
+  regions: [
+    "main" => [
+      "label" => new TranslatableMarkup("Main Region", [], ["context" => "layout_region"]),
+    ],
+  ],
+  consectetur: 'adipiscing',
+)]
+class TestLayout extends LayoutDefault {}
+EOS;
+    $plugin_provided_by_annotation_layout = <<<'EOS'
+<?php
+namespace Drupal\Core\Plugin\Layout;
 use Drupal\Core\Layout\LayoutDefault;
 /**
  * @Layout(
- *   id = "plugin_provided_layout",
- *   label = @Translation("Layout plugin"),
- *   category = @Translation("Columns: 1"),
- *   description = @Translation("Test layout"),
+ *   id = "plugin_provided_by_annotation_layout",
+ *   label = @Translation("Layout by annotation plugin"),
+ *   category = @Translation("Columns: 2"),
+ *   description = @Translation("Test layout provided by annotated plugin"),
  *   path = "core/lib/Drupal/Core",
- *   template = "templates/plugin-provided-layout",
+ *   template = "templates/plugin-provided-annotation-layout",
+ *   default_region = "left",
  *   regions = {
- *     "main" = {
- *       "label" = @Translation("Main Region", context = "layout_region")
+ *     "left" = {
+ *       "label" = @Translation("Left Region", context = "layout_region")
+ *     },
+ *     "right" = {
+ *        "label" = @Translation("Right Region", context = "layout_region")
  *     }
  *   }
  * )
  */
-class TestLayout extends LayoutDefault {}
+class TestAnnotationLayout extends LayoutDefault {}
 EOS;
     vfsStream::setup('root');
     vfsStream::create([
@@ -379,6 +459,7 @@ class TestLayout extends LayoutDefault {}
               'Plugin' => [
                 'Layout' => [
                   'TestLayout.php' => $plugin_provided_layout,
+                  'TestAnnotationLayout.php' => $plugin_provided_by_annotation_layout,
                 ],
               ],
             ],