diff --git a/src/Plugin/VariantCollectionInterface.php b/src/Plugin/VariantCollectionInterface.php
new file mode 100644
index 0000000..af6c3e6
--- /dev/null
+++ b/src/Plugin/VariantCollectionInterface.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\ctools\Plugin\VariantCollectionInterface.
+ */
+
+namespace Drupal\ctools\Plugin;
+
+/**
+ * Provides an interface for objects that have variants e.g. Pages.
+ */
+interface VariantCollectionInterface {
+
+  /**
+   * Adds a new variant to the entity.
+   *
+   * @param array $configuration
+   *   An array of configuration for the new variant.
+   *
+   * @return string
+   *   The variant ID.
+   */
+  public function addVariant(array $configuration);
+
+  /**
+   * Retrieves a specific variant.
+   *
+   * @param string $variant_id
+   *   The variant ID.
+   *
+   * @return \Drupal\Core\Display\VariantInterface
+   *   The variant object.
+   */
+  public function getVariant($variant_id);
+
+  /**
+   * Removes a specific variant.
+   *
+   * @param string $variant_id
+   *   The variant ID.
+   *
+   * @return $this
+   */
+  public function removeVariant($variant_id);
+
+  /**
+   * Returns the variants available for the entity.
+   *
+   * @return \Drupal\Core\Display\VariantInterface[]
+   *   An array of the variants.
+   */
+  public function getVariants();
+
+}
diff --git a/src/Plugin/VariantCollectionTrait.php b/src/Plugin/VariantCollectionTrait.php
new file mode 100644
index 0000000..57e68d1
--- /dev/null
+++ b/src/Plugin/VariantCollectionTrait.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\ctools\Plugin\VariantCollectionTrait.
+ */
+
+namespace Drupal\ctools\Plugin;
+
+/**
+ * Provides methods for VariantCollectionInterface.
+ */
+trait VariantCollectionTrait {
+
+  /**
+   * The plugin collection that holds the variants.
+   *
+   * @var \Drupal\ctools\Plugin\VariantCollection
+   */
+  protected $variantCollection;
+
+  /**
+   * @see \Drupal\ctools\Plugin\VariantCollectionInterface::addVariant()
+   */
+  public function addVariant(array $configuration) {
+    $configuration['uuid'] = $this->uuidGenerator()->generate();
+    $this->getVariants()->addInstanceId($configuration['uuid'], $configuration);
+    return $configuration['uuid'];
+  }
+
+  /**
+   * @see \Drupal\ctools\Plugin\VariantCollectionInterface::getVariant()
+   */
+  public function getVariant($variant_id) {
+    return $this->getVariants()->get($variant_id);
+  }
+
+  /**
+   * @see \Drupal\ctools\Plugin\VariantCollectionInterface::removeVariant()
+   */
+  public function removeVariant($variant_id) {
+    $this->getVariants()->removeInstanceId($variant_id);
+    return $this;
+  }
+
+  /**
+   * @see \Drupal\ctools\Plugin\VariantCollectionInterface::getVariants()
+   */
+  public function getVariants() {
+    if (!$this->variantCollection) {
+      $this->variantCollection = new VariantCollection(\Drupal::service('plugin.manager.display_variant'), $this->getVariantConfig());
+      $this->variantCollection->sort();
+    }
+    return $this->variantCollection;
+  }
+
+  /**
+   * Returns the configuration for stored variants.
+   *
+   * @return array
+   *   An array of variant configuration, keyed by the unique variant ID.
+   */
+  abstract protected function getVariantConfig();
+
+  /**
+   * Returns the UUID generator.
+   *
+   * @return \Drupal\Component\Uuid\UuidInterface
+   */
+  abstract protected function uuidGenerator();
+
+}
diff --git a/tests/src/Unit/VariantCollectionTraitTest.php b/tests/src/Unit/VariantCollectionTraitTest.php
new file mode 100644
index 0000000..1be3b58
--- /dev/null
+++ b/tests/src/Unit/VariantCollectionTraitTest.php
@@ -0,0 +1,214 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\ctools\Unit\VariantCollectionTraitTest.
+ */
+
+namespace Drupal\Tests\ctools\Unit;
+
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Display\VariantInterface;
+use Drupal\ctools\Plugin\VariantCollectionTrait;
+use Drupal\ctools\Plugin\VariantCollection;
+use Drupal\Tests\UnitTestCase;
+use Prophecy\Argument;
+
+/**
+ * Tests the methods of a variant-aware class.
+ *
+ * @coversDefaultClass \Drupal\ctools\Plugin\VariantCollectionTrait
+ *
+ * @group Ctools
+ */
+class VariantCollectionTraitTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\Component\Plugin\PluginManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $manager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $container = new ContainerBuilder();
+    $this->manager = $this->prophesize(PluginManagerInterface::class);
+    $container->set('plugin.manager.display_variant', $this->manager->reveal());
+    \Drupal::setContainer($container);
+  }
+
+  /**
+   * @covers ::getVariants
+   */
+  public function testGetVariantsEmpty() {
+    $trait_object = new TestVariantCollectionTrait();
+    $this->manager->createInstance()->shouldNotBeCalled();
+
+    $variants = $trait_object->getVariants();
+    $this->assertInstanceOf(VariantCollection::class, $variants);
+    $this->assertSame(0, count($variants));
+  }
+
+  /**
+   * @covers ::getVariants
+   */
+  public function testGetVariants() {
+    $trait_object = new TestVariantCollectionTrait();
+    $config = [
+      'foo' => ['id' => 'foo_plugin'],
+      'bar' => ['id' => 'bar_plugin'],
+    ];
+    foreach ($config as $value) {
+      $plugin = $this->prophesize(VariantInterface::class);
+      $this->manager->createInstance($value['id'], $value)->willReturn($plugin->reveal());
+    }
+    $trait_object->setVariantConfig($config);
+
+    $variants = $trait_object->getVariants();
+    $this->assertInstanceOf(VariantCollection::class, $variants);
+    $this->assertSame(2, count($variants));
+    return $variants;
+  }
+
+  /**
+   * @covers ::getVariants
+   *
+   * @depends testGetVariants
+   */
+  public function testGetVariantsSort(VariantCollection $variants) {
+    $this->assertSame(['bar' => 'bar', 'foo' => 'foo'], $variants->getInstanceIds());
+  }
+
+  /**
+   * @covers ::addVariant
+   */
+  public function testAddVariant() {
+    $config = ['id' => 'foo'];
+    $uuid = 'test-uuid';
+    $expected_config = $config + ['uuid' => $uuid];
+
+    $uuid_generator = $this->prophesize(UuidInterface::class);
+    $uuid_generator->generate()
+      ->willReturn($uuid)
+      ->shouldBeCalledTimes(1);
+    $trait_object = new TestVariantCollectionTrait();
+    $trait_object->setUuidGenerator($uuid_generator->reveal());
+
+    $plugin_prophecy = $this->prophesize(VariantInterface::class);
+    $plugin_prophecy->getConfiguration()
+      ->willReturn($expected_config)
+      ->shouldBeCalled();
+    $plugin_prophecy->setConfiguration($expected_config)
+      ->willReturn($expected_config)
+      ->shouldBeCalled();
+
+    $this->manager->createInstance('foo', $expected_config)
+      ->willReturn($plugin_prophecy->reveal());
+
+    $resulting_uuid = $trait_object->addVariant($config);
+    $this->assertSame($uuid, $resulting_uuid);
+
+    $variants = $trait_object->getVariants();
+    $this->assertSame([$uuid => $uuid], $variants->getInstanceIds());
+    $this->assertSame([$uuid => $expected_config], $variants->getConfiguration());
+    $this->assertSame($plugin_prophecy->reveal(), $variants->get($uuid));
+    return [$trait_object, $uuid, $plugin_prophecy->reveal()];
+  }
+
+  /**
+   * @covers ::getVariant
+   *
+   * @depends testAddVariant
+   */
+  public function testGetVariant($data) {
+    list($trait_object, $uuid, $plugin) = $data;
+    $this->manager->createInstance()->shouldNotBeCalled();
+
+    $this->assertSame($plugin, $trait_object->getVariant($uuid));
+    return [$trait_object, $uuid];
+  }
+
+  /**
+   * @covers ::removeVariant
+   *
+   * @depends testGetVariant
+   */
+  public function testRemoveVariant($data) {
+    list($trait_object, $uuid) = $data;
+
+    $this->assertSame($trait_object, $trait_object->removeVariant($uuid));
+    $this->assertFalse($trait_object->getVariants()->has($uuid));
+    return [$trait_object, $uuid];
+  }
+
+  /**
+   * @covers ::getVariant
+   *
+   * @depends testRemoveVariant
+   *
+   * @expectedException \Drupal\Component\Plugin\Exception\PluginNotFoundException
+   * @expectedExceptionMessage Plugin ID 'test-uuid' was not found.
+   */
+  public function testGetVariantException($data) {
+    list($trait_object, $uuid) = $data;
+    // Attempt to retrieve a variant that has been removed.
+    $this->assertNull($trait_object->getVariant($uuid));
+  }
+
+}
+
+class TestVariantCollectionTrait {
+  use VariantCollectionTrait;
+
+  /**
+   * @var array
+   */
+  protected $variantConfig = [];
+
+  /**
+   * @var \Drupal\Component\Uuid\UuidInterface
+   */
+  protected $uuidGenerator;
+
+  /**
+   * @param \Drupal\Component\Uuid\UuidInterface $uuid_generator
+   *
+   * @return $this
+   */
+  public function setUuidGenerator(UuidInterface $uuid_generator) {
+    $this->uuidGenerator = $uuid_generator;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function uuidGenerator() {
+    return $this->uuidGenerator;
+  }
+
+  /**
+   * Sets the variant configuration.
+   *
+   * @param array $config
+   *   The variant configuration.
+   *
+   * @return $this
+   */
+  public function setVariantConfig(array $config) {
+    $this->variantConfig = $config;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getVariantConfig() {
+    return $this->variantConfig;
+  }
+
+}
