diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index fff63c1..d937411 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -173,6 +173,9 @@ Block module
 Book module
 - Peter Wolanin 'pwolanin' http://drupal.org/user/49851
 
+Breakpoint module
+- Peter Droogmans 'attiks' http://drupal.org/user/105002
+
 Color module
 - ?
 
diff --git a/core/modules/breakpoint/breakpoint.info b/core/modules/breakpoint/breakpoint.info
new file mode 100644
index 0000000..fee160f
--- /dev/null
+++ b/core/modules/breakpoint/breakpoint.info
@@ -0,0 +1,7 @@
+name = Breakpoint
+description = Manage breakpoints and breakpoint groups for responsive designs.
+package = Core
+version = VERSION
+core = 8.x
+
+dependencies[] = config
diff --git a/core/modules/breakpoint/breakpoint.module b/core/modules/breakpoint/breakpoint.module
new file mode 100644
index 0000000..c34657f
--- /dev/null
+++ b/core/modules/breakpoint/breakpoint.module
@@ -0,0 +1,400 @@
+<?php
+
+/**
+ * @file
+ * Manage breakpoints and breakpoint groups for responsive designs.
+ */
+
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\BreakpointGroup;
+
+/**
+ * Implements hook_enable().
+ *
+ * Import breakpoints from all enabled themes.
+ */
+function breakpoint_enable() {
+  // Import breakpoints from themes.
+  $themes = list_themes();
+  _breakpoint_theme_enabled(array_keys($themes));
+
+  // Import breakpoints from modules.
+  $modules = module_list();
+  _breakpoint_modules_enabled(array_keys($modules));
+}
+
+/**
+ * Implements hook_themes_enabled().
+ *
+ * @param array $theme_list
+ *   An array of theme names.
+ *
+ * @see _breakpoint_theme_enabled()
+ */
+function breakpoint_themes_enabled($theme_list) {
+  _breakpoint_theme_enabled($theme_list);
+}
+
+/**
+ * Implements hook_themes_disabled().
+ *
+ * @param array $theme_list
+ *   An array of theme names.
+ *
+ * @see _breakpoint_delete_breakpoints()
+ */
+function breakpoint_themes_disabled($theme_list) {
+  _breakpoint_delete_breakpoints($theme_list, Breakpoint::SOURCE_TYPE_THEME);
+}
+
+/**
+ * Implements hook_modules_enabled().
+ *
+ * @param array $modules
+ *   An array of the modules that were enabled.
+ *
+ * @see _breakpoint_modules_enabled()
+ */
+function breakpoint_modules_enabled($modules) {
+  _breakpoint_modules_enabled($modules);
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ *
+ * @param array $modules
+ *   An array of the modules that were uninstalled.
+ *
+ * @see _breakpoint_delete_breakpoints()
+ */
+function breakpoint_modules_uninstalled($modules) {
+  _breakpoint_delete_breakpoints($modules, Breakpoint::SOURCE_TYPE_MODULE);
+}
+
+/**
+ * Import breakpoints from all new enabled themes.
+ *
+ * @param array $theme_list
+ *   An array of theme names.
+ */
+function _breakpoint_theme_enabled($theme_list) {
+  $themes = list_themes();
+  foreach ($theme_list as $theme_key) {
+    if ($themes[$theme_key]->status) {
+      $media_queries = breakpoint_get_theme_media_queries($theme_key);
+      _breakpoint_import_media_queries($theme_key, $themes[$theme_key]->info['name'], Breakpoint::SOURCE_TYPE_THEME, $media_queries);
+      // Import custom groups.
+      _breakpoint_import_breakpoint_groups($theme_key, Breakpoint::SOURCE_TYPE_THEME);
+    }
+  }
+}
+
+/**
+ * Import breakpoints from all new enabled modules.
+ *
+ * @param array $modules
+ *   An array of the modules that were enabled.
+ */
+function _breakpoint_modules_enabled($modules) {
+  foreach ($modules as $module) {
+    $media_queries = breakpoint_get_module_media_queries($module);
+    _breakpoint_import_media_queries($module, $module, Breakpoint::SOURCE_TYPE_MODULE, $media_queries);
+    // Import custom groups.
+    _breakpoint_import_breakpoint_groups($module, Breakpoint::SOURCE_TYPE_MODULE);
+  }
+}
+
+/**
+ * Import media queries from a theme or module and create a default group.
+ *
+ * @param string $group_name
+ *   Machine readable name of the breakpoint group.
+ * @param string $label
+ *   Human readable name of the breakpoint group.
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ * @param array $media_queries
+ *   An array of breakpoints in the form $breakpoint['name'] = 'media query'.
+ */
+function _breakpoint_import_media_queries($group_name, $label, $source_type, $media_queries) {
+  if (!empty($media_queries)) {
+    // Create a new breakpoint group if it doesn't exist.
+    $breakpoint_group = _breakpoint_group_create_or_load($group_name, $label, $group_name, $source_type);
+
+    // Load all media queries, create a breakpoint for each one and add them
+    // to this breakpoint group.
+    foreach ($media_queries as $name => $media_query) {
+      $breakpoint_group->addBreakpointFromMediaQuery($name, $media_query);
+    }
+
+    $breakpoint_group->save();
+  }
+}
+
+/**
+ * Import breakpoint groups from theme or module.
+ *
+ * @param string $source
+ *   The theme or module name
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ */
+function _breakpoint_import_breakpoint_groups($source, $source_type) {
+  $breakpoint_groups = config($source . '.breakpoint_groups');
+  if ($breakpoint_groups) {
+    foreach ($breakpoint_groups->get() as $group_name => $data) {
+      // Breakpoints is mandatory, extra check since this is coming from config.
+      if (isset($data['breakpoints']) && !empty($data['breakpoints'])) {
+        // Create a new breakpoint group if it doesn't exist.
+        $breakpoint_group = _breakpoint_group_create_or_load($group_name, isset($data['label']) ? $data['label'] : $group_name, $source, $source_type);
+        // Add the breakpoints.
+        $breakpoint_group->addBreakpoints($data['breakpoints']);
+        $breakpoint_group->save();
+      }
+      else {
+        throw new \Exception('Illegal config file detected.');
+      }
+    }
+  }
+}
+
+/**
+ * Remove breakpoints from all disabled themes or uninstalled modules.
+ *
+ * The source type has to match the original source type, otherwise the group
+ * will not be deleted. All groups created by the theme or module will be
+ * deleted as well.
+ *
+ * @param array $list
+ *   A list of modules or themes that are disabled.
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ */
+function _breakpoint_delete_breakpoints($list, $source_type) {
+  $ids = config_get_storage_names_with_prefix('breakpoint.breakpoint_group.' . $source_type . '.');
+  $entity_info = entity_get_info('breakpoint_group');
+
+  // Remove the breakpoint.breakpoint part of the breakpoint identifier.
+  foreach ($ids as &$id) {
+    $id = drupal_substr($id, drupal_strlen($entity_info['config prefix']) + 1);
+  }
+  $breakpoint_groups = entity_load_multiple('breakpoint_group', $ids);
+
+  foreach ($breakpoint_groups as $breakpoint_group) {
+    if ($breakpoint_group->sourceType == $source_type && in_array($breakpoint_group->source, $list)) {
+      // Delete the automatically created breakpoint group.
+      $breakpoint_group->delete();
+
+      // Get all breakpoints defined by this theme/module.
+      $breakpoint_ids = drupal_container()->get('config.storage')->listAll('breakpoint.breakpoint.' . $source_type . '.' . $breakpoint_group->id() . '.');
+      $entity_info = entity_get_info('breakpoint');
+
+      // Remove the breakpoint.breakpoint part of the breakpoint identifier.
+      foreach ($breakpoint_ids as &$breakpoint_id) {
+        $breakpoint_id = drupal_substr($breakpoint_id, drupal_strlen($entity_info['config prefix']) + 1);
+      }
+      $breakpoints = entity_load_multiple('breakpoint', $breakpoint_ids);
+
+      // Make sure we only delete breakpoints defined by this theme/module.
+      foreach ($breakpoints as $breakpoint) {
+        if ($breakpoint->sourceType == $source_type && $breakpoint->source == $breakpoint_group->name) {
+          $breakpoint->delete();
+        }
+      }
+    }
+  }
+
+  // Delete groups defined by a module/theme even if that module/theme didn't
+  // define any breakpoints.
+  foreach ($ids as $id) {
+    // Delete all breakpoint groups defined by the theme or module.
+    _breakpoint_delete_breakpoint_groups($id, $source_type);
+  }
+}
+
+/**
+ * Remove breakpoint groups from all disabled themes or uninstalled modules.
+ *
+ * @param array $group_id
+ *   Machine readable name of the breakpoint group.
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ */
+function _breakpoint_delete_breakpoint_groups($group_id, $source_type) {
+  $breakpoint_groups = entity_load_multiple('breakpoint_group');
+  foreach ($breakpoint_groups as $breakpoint_group) {
+    if ($breakpoint_group->sourceType == $source_type && $breakpoint_group->source == $group_id) {
+      $breakpoint_group->delete();
+    }
+  }
+}
+
+/**
+ * Get a list of available breakpoints from a specified theme.
+ *
+ * @param string $theme_key
+ *   The name of the theme.
+ *
+ * @return array
+ *   An array of breakpoints in the form $breakpoint['name'] = 'media query'.
+ */
+function breakpoint_get_theme_media_queries($theme_key) {
+  $themes = list_themes();
+  if (!isset($themes[$theme_key])) {
+    throw new \Exception('Illegal theme_key passed.');
+  }
+
+  $config = config($theme_key . '.breakpoints');
+  if ($config) {
+    return $config->get();
+  }
+  return array();
+}
+
+/**
+ * Get a list of available breakpoints from a specified module.
+ *
+ * @param string $module
+ *   The name of the module.
+ *
+ * @return array
+ *   An array of breakpoints in the form $breakpoint['name'] = 'media query'.
+ */
+function breakpoint_get_module_media_queries($module) {
+  if (!module_exists($module)) {
+    throw new \Exception('Illegal module name passed.');
+  }
+
+  $config = config($module . '.breakpoints');
+  if ($config) {
+    return $config->get();
+  }
+  return array();
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function breakpoint_entity_info() {
+  // Breakpoint.
+  $types['breakpoint'] = array(
+    'label' => 'Breakpoint',
+    'entity class' => 'Drupal\breakpoint\Breakpoint',
+    'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
+    'config prefix' => 'breakpoint.breakpoint',
+    'entity keys' => array(
+      'id' => 'id',
+      'label' => 'label',
+      'uuid' => 'uuid',
+    ),
+  );
+
+  // Breakpoint group.
+  $types['breakpoint_group'] = array(
+    'label' => 'Breakpoint group',
+    'entity class' => 'Drupal\breakpoint\BreakpointGroup',
+    'controller class' => 'Drupal\Core\Config\Entity\ConfigStorageController',
+    'config prefix' => 'breakpoint.breakpoint_group',
+    'entity keys' => array(
+      'id' => 'id',
+      'label' => 'label',
+      'uuid' => 'uuid',
+    ),
+  );
+
+  return $types;
+}
+
+/**
+ * Load one breakpoint group by its identifier.
+ *
+ * @param string $id
+ *   The id of the breakpoint group to load.
+ *
+ * @return Drupal\breakpoint\BreakpointGroup|false
+ *   The breakpoint group, or FALSE if there is no entity with the given id.
+ *
+ * @todo Remove this in a follow-up issue.
+ * @see http://drupal.org/node/1798214
+ */
+function breakpoint_group_load($id) {
+  return entity_load('breakpoint_group', $id);
+}
+
+/**
+ * Load one breakpoint by its identifier.
+ *
+ * @param int $id
+ *   The id of the breakpoint to load.
+ *
+ * @return Drupal\breakpoint\Breakpoint
+ *   The entity object, or FALSE if there is no entity with the given id.
+ *
+ * @todo Remove this in a follow-up issue.
+ * @see http://drupal.org/node/1798214
+ */
+function breakpoint_load($id) {
+  return entity_load('breakpoint', $id);
+}
+
+/**
+ * Load all breakpoint groups as select options.
+ *
+ * @return array
+ *   An array containing breakpoint group labels indexed by their ids.
+ */
+function breakpoint_group_labels() {
+  $options = array();
+  $breakpoint_groups = entity_load_multiple('breakpoint_group');
+  foreach ($breakpoint_groups as $breakpoint_group) {
+    $options[$breakpoint_group->id()] = $breakpoint_group->label();
+  }
+  asort($options);
+  return $options;
+}
+
+/**
+ * Load all breakpoints as select options.
+ *
+ * @return array
+ *   An array containing breakpoints indexed by their ids.
+ */
+function breakpoint_labels() {
+  $options = array();
+  $breakpoints = entity_load_multiple('breakpoint');
+  foreach ($breakpoints as $breakpoint) {
+    $options[$breakpoint->id()] = $breakpoint->label() . ' (' . $breakpoint->source . ' - ' . $breakpoint->sourceType .   ') [' . $breakpoint->mediaQuery . ']';
+  }
+
+  return $options;
+}
+
+/**
+ * Helper function to easily create/load a breakpoint group.
+ *
+ * @param string $name
+ *   Machine readable name of the breakpoint group.
+ * @param string $label
+ *   Human readable name of the breakpoint group.
+ * @param string $source
+ *   Machine readable name of the defining theme or module.
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ *
+ * @return Drupal\breakpoint\BreakpointGroup
+ */
+function _breakpoint_group_create_or_load($name, $label, $source, $source_type) {
+  // Try loading the breakpoint group.
+  $breakpoint_group = entity_load('breakpoint_group', $source_type . '.' . $source . '.' . $name);
+  // Create a new breakpoint group if it doesn't exist.
+  if (!$breakpoint_group) {
+    // Build a new breakpoint group.
+    $breakpoint_group = entity_create('breakpoint_group', array(
+      'name' => $name,
+      'label' => $label,
+      'source' => $source,
+      'sourceType' => $source_type,
+    ));
+  }
+  return $breakpoint_group;
+}
diff --git a/core/modules/breakpoint/config/breakpoint.yml b/core/modules/breakpoint/config/breakpoint.yml
new file mode 100644
index 0000000..0ba703d
--- /dev/null
+++ b/core/modules/breakpoint/config/breakpoint.yml
@@ -0,0 +1,2 @@
+multipliers: [1x, 1.5x, 2x]
+
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php
new file mode 100644
index 0000000..50059d0
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php
@@ -0,0 +1,283 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Breakpoint.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\breakpoint\InvalidBreakpointException;
+use Drupal\breakpoint\InvalidBreakpointNameException;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+use Drupal\breakpoint\InvalidBreakpointMediaQueryException;
+
+/**
+ * Defines the Breakpoint entity.
+ */
+class Breakpoint extends ConfigEntityBase {
+
+  /**
+   * Denotes that a breakpoint or breakpoint group is defined by a theme.
+   */
+  const SOURCE_TYPE_THEME = 'theme';
+
+  /**
+   * Denotes that a breakpoint or breakpoint group is defined by a module.
+   */
+  const SOURCE_TYPE_MODULE = 'module';
+
+  /**
+   * Denotes that a breakpoint or breakpoint group is defined by the user.
+   */
+  const SOURCE_TYPE_CUSTOM = 'custom';
+
+  /**
+   * The breakpoint ID (config name).
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The breakpoint UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The breakpoint name (machine name) as specified by theme or module.
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * The breakpoint label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The breakpoint media query.
+   *
+   * @var string
+   */
+  public $mediaQuery = '';
+
+  /**
+   * The breakpoint source.
+   *
+   * @var string
+   */
+  public $source = 'user';
+
+  /**
+   * The breakpoint source type.
+   *
+   * @var string
+   *   Allowed values:
+   *     Breakpoint::SOURCE_TYPE_THEME
+   *     Breakpoint::SOURCE_TYPE_MODULE
+   *     Breakpoint::SOURCE_TYPE_CUSTOM
+   */
+  public $sourceType = Breakpoint::SOURCE_TYPE_CUSTOM;
+
+  /**
+   * The breakpoint weight.
+   *
+   * @var weight
+   */
+  public $weight = 0;
+
+  /**
+   * The breakpoint multipliers.
+   *
+   * @var multipliers
+   */
+  public $multipliers = array();
+
+  /**
+   * Overrides Drupal\config\ConfigEntityBase::save().
+   */
+  public function save() {
+    // Check if everything is valid.
+    if (!$this->isValid()) {
+      throw new InvalidBreakpointException('Invalid data detected.');
+    }
+
+    // Build an id if non is set.
+    // Since a particular name can be used by multiple theme/modules we need
+    // to make a unique id.
+    if (empty($this->id)) {
+      $this->id = $this->sourceType . '.' . $this->source . '.' . $this->name;
+    }
+
+    // Set the label if none is set.
+    if (empty($this->label)) {
+      $this->label = $this->name;
+    }
+
+    // Remove unused multipliers.
+    $this->multipliers = array_filter($this->multipliers);
+
+    // Always add '1x' multiplier, use array_key_exists since the value might
+    // be NULL.
+    if (!array_key_exists('1x', $this->multipliers)) {
+      $this->multipliers = array('1x' => '1x') + $this->multipliers;
+    }
+    return parent::save();
+  }
+
+  /**
+   * Duplicates a breakpoint.
+   *
+   * The new breakpoint inherits the media query.
+   *
+   * @return Drupal\breakpoint\Breakpoint
+   */
+  public function duplicate() {
+    return entity_create('breakpoint', array(
+      'mediaQuery' => $this->mediaQuery,
+    ));
+  }
+
+  /**
+   * Checks if the breakpoint is valid.
+   *
+   * @throws Drupal\breakpoint\InvalidBreakpointSourceTypeException
+   * @throws Drupal\breakpoint\InvalidBreakpointSourceException
+   * @throws Drupal\breakpoint\InvalidBreakpointNameException
+   * @throws Drupal\breakpoint\InvalidBreakpointMediaQueryException
+   *
+   * @see isValidMediaQuery()
+   */
+  public function isValid() {
+    // Check for illegal values in breakpoint source type.
+    if (!in_array($this->sourceType, array(
+        Breakpoint::SOURCE_TYPE_CUSTOM,
+        Breakpoint::SOURCE_TYPE_MODULE,
+        Breakpoint::SOURCE_TYPE_THEME)
+      )) {
+      throw new InvalidBreakpointSourceTypeException(format_string('Invalid source type @source_type', array(
+        '@source_type' => $this->sourceType,
+      )));
+    }
+    // Check for illegal characters in breakpoint source.
+    if (preg_match('/[^a-z_]+/', $this->source)) {
+      throw new InvalidBreakpointSourceException(format_string("Invalid value '@source' for breakpoint source property. Breakpoint source property can only contain lowercase letters and underscores.", array('@source' => $this->source)));
+    }
+    // Check for illegal characters in breakpoint names.
+    if (preg_match('/[^0-9a-z_\-]/', $this->name)) {
+      throw new InvalidBreakpointNameException(format_string("Invalid value '@name' for breakpoint name property. Breakpoint name property can only contain lowercase alphanumeric characters, underscores (_), and hyphens (-).", array('@name' => $this->name)));
+    }
+    return $this::isValidMediaQuery($this->mediaQuery);
+  }
+
+  /**
+   * Checks if a mediaQuery is valid.
+   *
+   * @throws Drupal\breakpoint\InvalidBreakpointMediaQueryException
+   *
+   * @return true
+   *   Returns true if the media query is valid.
+   *
+   * @see http://www.w3.org/TR/css3-mediaqueries/
+   * @see http://www.w3.org/Style/CSS/Test/MediaQueries/20120229/reports/implement-report.html
+   * @see https://github.com/adobe/webkit/blob/master/Source/WebCore/css/
+   */
+  public static function isValidMediaQuery($media_query) {
+    $media_features = array(
+      'width' => 'length', 'min-width' => 'length', 'max-width' => 'length',
+      'height' => 'length', 'min-height' => 'length', 'max-height' => 'length',
+      'device-width' => 'length', 'min-device-width' => 'length', 'max-device-width' => 'length',
+      'device-height' => 'length', 'min-device-height' => 'length', 'max-device-height' => 'length',
+      'orientation' => array('portrait', 'landscape'),
+      'aspect-ratio' => 'ratio', 'min-aspect-ratio' => 'ratio', 'max-aspect-ratio' => 'ratio',
+      'device-aspect-ratio' => 'ratio', 'min-device-aspect-ratio' => 'ratio', 'max-device-aspect-ratio' => 'ratio',
+      'color' => 'integer', 'min-color' => 'integer', 'max-color' => 'integer',
+      'color-index' => 'integer', 'min-color-index' => 'integer', 'max-color-index' => 'integer',
+      'monochrome' => 'integer', 'min-monochrome' => 'integer', 'max-monochrome' => 'integer',
+      'resolution' => 'resolution', 'min-resolution' => 'resolution', 'max-resolution' => 'resolution',
+      'scan' => array('progressive', 'interlace'),
+      'grid' => 'integer',
+    );
+    if ($media_query) {
+      // Strip new lines and trim.
+      $media_query = str_replace(array("\r", "\n"), ' ', trim($media_query));
+
+      // Remove comments /* ... */.
+      $media_query = preg_replace('/\/\*[\s\S]*?\*\//', '', $media_query);
+
+      // Check mediaQuery_list: S* [mediaQuery [ ',' S* mediaQuery ]* ]?
+      $parts = explode(',', $media_query);
+      foreach ($parts as $part) {
+        // Split on ' and '
+        $query_parts = explode(' and ', trim($part));
+        $media_type_found = FALSE;
+        foreach ($query_parts as $query_part) {
+          $matches = array();
+          // Check expression: '(' S* media_feature S* [ ':' S* expr ]? ')' S*
+          if (preg_match('/^\(([\w\-]+)(:\s?([\w\-\.]+))?\)/', trim($query_part), $matches)) {
+            // Single expression.
+            if (isset($matches[1]) && !isset($matches[2])) {
+              if (!array_key_exists($matches[1], $media_features)) {
+                throw new InvalidBreakpointMediaQueryException('Invalid media feature detected.');
+              }
+            }
+            // Full expression.
+            elseif (isset($matches[3]) && !isset($matches[4])) {
+              $value = trim($matches[3]);
+              if (!array_key_exists($matches[1], $media_features)) {
+                throw new InvalidBreakpointMediaQueryException('Invalid media feature detected.');
+              }
+              if (is_array($media_features[$matches[1]])) {
+                // Check if value is allowed.
+                if (!array_key_exists($value, $media_features[$matches[1]])) {
+                  throw new InvalidBreakpointMediaQueryException('Value is not allowed.');
+                }
+              }
+              else {
+                switch ($media_features[$matches[1]]) {
+                  case 'length':
+                    $length_matches = array();
+                    if (preg_match('/^(\-)?(\d+(?:\.\d+)?)?((?:|em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|dpi|dpcm))$/i', trim($value), $length_matches)) {
+                      // Only -0 is allowed.
+                      if ($length_matches[1] === '-' && $length_matches[2] !== '0') {
+                        throw new InvalidBreakpointMediaQueryException('Invalid length detected.');
+                      }
+                      // If there's a unit, a number is needed as well.
+                      if ($length_matches[2] === '' && $length_matches[3] !== '') {
+                        throw new InvalidBreakpointMediaQueryException('Unit found, value is missing.');
+                      }
+                    }
+                    else {
+                      throw new InvalidBreakpointMediaQueryException('Invalid unit detected.');
+                    }
+                    break;
+                }
+              }
+            }
+          }
+
+          // Check [ONLY | NOT]? S* media_type
+          elseif (preg_match('/^((?:only|not)?\s?)([\w\-]+)$/i', trim($query_part), $matches)) {
+            if ($media_type_found) {
+              throw new InvalidBreakpointMediaQueryException('Only one media type is allowed.');
+            }
+            $media_type_found = TRUE;
+          }
+          else {
+            throw new InvalidBreakpointMediaQueryException('Invalid media query detected.');
+          }
+        }
+      }
+      return TRUE;
+    }
+    throw new InvalidBreakpointMediaQueryException('Media query is empty.');
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php b/core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php
new file mode 100644
index 0000000..de56397
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php
@@ -0,0 +1,214 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\BreakpointGroup.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+
+/**
+ * Defines the BreakpointGroup entity.
+ */
+class BreakpointGroup extends ConfigEntityBase {
+
+  /**
+   * The breakpoint group ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The breakpoint group UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+ /**
+   * The breakpoint group machine name.
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * The breakpoint group label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The breakpoint group breakpoints.
+   *
+   * @var array
+   *   Array containing all breakpoints of this group.
+   *
+   * @see Drupal\breakpoints\Breakpoint
+   */
+  public $breakpoints = array();
+
+  /**
+   * The breakpoint group source: theme or module name. Use 'user' for
+   * user-created groups.
+   *
+   * @var string
+   */
+  public $source = 'user';
+
+  /**
+   * The breakpoint group source type.
+   *
+   * @var string
+   *   Allowed values:
+   *     Breakpoint::SOURCE_TYPE_THEME
+   *     Breakpoint::SOURCE_TYPE_MODULE
+   *     Breakpoint::SOURCE_TYPE_CUSTOM
+   *
+   * @see Drupal\breakpoint\Breakpoint
+   */
+  public $sourceType = Breakpoint::SOURCE_TYPE_CUSTOM;
+
+  /**
+   * Overrides Drupal\config\ConfigEntityBase::__construct().
+   */
+  public function __construct(array $values, $entity_type) {
+    parent::__construct($values, $entity_type);
+    $this->loadAllBreakpoints();
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\Entity::save().
+   */
+  public function save() {
+    // Check if everything is valid.
+    if (!$this->isValid()) {
+      throw new InvalidBreakpointException('Invalid data detected.');
+    }
+    if (empty($this->id)) {
+      $this->id = $this->sourceType . '.' . $this->source . '.' . $this->name;
+    }
+    // Only save the keys, but return the full objects.
+    $this->breakpoints = array_keys($this->breakpoints);
+    parent::save();
+    $this->loadAllBreakpoints();
+  }
+
+  /**
+   * Checks if the breakpoint group is valid.
+   *
+   * @throws Drupal\breakpoint\InvalidBreakpointSourceTypeException
+   * @throws Drupal\breakpoint\InvalidBreakpointSourceException
+   *
+   * @return true
+   *   Returns true if the breakpoint group is valid.
+   */
+  public function isValid() {
+    // Check for illegal values in breakpoint group source type.
+    if (!in_array($this->sourceType, array(
+        Breakpoint::SOURCE_TYPE_CUSTOM,
+        Breakpoint::SOURCE_TYPE_MODULE,
+        Breakpoint::SOURCE_TYPE_THEME)
+      )) {
+      throw new InvalidBreakpointSourceTypeException(format_string('Invalid source type @source_type', array(
+        '@source_type' => $this->sourceType,
+      )));
+    }
+    // Check for illegal characters in breakpoint group source.
+    if (preg_match('/[^a-z_]+/', $this->source) || empty($this->source)) {
+      throw new InvalidBreakpointSourceException(format_string("Invalid value '@source' for breakpoint group source property. Breakpoint group source property can only contain lowercase letters and underscores.", array('@source' => $this->source)));
+    }
+    // Check for illegal characters in breakpoint group name.
+    if (preg_match('/[^a-z0-9_]+/', $this->name || empty($this->name))) {
+      throw new InvalidBreakpointNameException(format_string("Invalid value '@name' for breakpoint group name property. Breakpoint group name property can only contain lowercase letters, numbers and underscores.", array('@name' => $this->name)));
+    }
+    return TRUE;
+  }
+
+  /**
+   * Duplicates a breakpoint group.
+   *
+   * The new breakpoint group inherits the breakpoints.
+   *
+   * @return Drupal\breakpoint\BreakpointGroup
+   */
+  public function duplicate() {
+    return entity_create('breakpoint_group', array(
+      'breakpoints' => array_keys($this->breakpoints),
+      'name' => 'clone_of_' . $this->name,
+    ));
+  }
+
+  /**
+   * Adds a breakpoint using a name and a media query.
+   *
+   * @param string $name
+   *   The name of the breakpoint.
+   * @param string $media_query
+   *   Media query.
+   */
+  public function addBreakpointFromMediaQuery($name, $media_query) {
+    // Use the existing breakpoint if it exists.
+    $breakpoint = entity_load('breakpoint', $this->sourceType . '.' . $this->name . '.' . $name);
+    if (!$breakpoint) {
+      // Build a new breakpoint.
+      $breakpoint = entity_create('breakpoint', array(
+        'name' => $name,
+        'label' => $name,
+        'mediaQuery' => $media_query,
+        'source' => $this->name,
+        'sourceType' => $this->sourceType,
+        'weight' => count($this->breakpoints),
+      ));
+      $breakpoint->save();
+    }
+    $this->breakpoints[$breakpoint->id()] = $breakpoint;
+  }
+
+  /**
+   * Adds one or more breakpoints to this group.
+   *
+   * The breakpoint name is either the machine_name or the id of a breakpoint.
+   *
+   * @param type $breakpoints
+   *   Array containing breakpoints keyed by their id.
+   */
+  public function addBreakpoints($breakpoints) {
+    foreach ($breakpoints as $breakpoint_name) {
+      // Check if breakpoint exists, assume $breakpoint_name is a machine name.
+      $breakpoint = entity_load('breakpoint', $this->sourceType . '.' . $this->source . '.' . $breakpoint_name);
+      // If the breakpoint doesn't exist, assume $breakpoint_name is an id.
+      if (!$breakpoint) {
+        $breakpoint = entity_load('breakpoint', $breakpoint_name);
+      }
+      // If the breakpoint doesn't exists, do not add it.
+      if ($breakpoint) {
+        // Add breakpoint to group.
+        $this->breakpoints[$breakpoint->id()] = $breakpoint;
+      }
+    }
+  }
+
+  /**
+   * Loads all breakpoints, remove non-existing ones.
+   *
+   * @return array
+   *   Array containing breakpoints keyed by their id.
+   */
+  protected function loadAllBreakpoints() {
+    $breakpoints = $this->breakpoints;
+    $this->breakpoints = array();
+    foreach ($breakpoints as $breakpoint_id) {
+      $breakpoint = breakpoint_load($breakpoint_id);
+      if ($breakpoint) {
+        $this->breakpoints[$breakpoint_id] = $breakpoint;
+      }
+    }
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointException.php
new file mode 100644
index 0000000..6889fa2
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointException.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointException.
+ */
+
+namespace Drupal\breakpoint;
+
+/**
+ * Base exception for breakpoint exception.
+ */
+class InvalidBreakpointException extends \RuntimeException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointMediaQueryException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointMediaQueryException.php
new file mode 100644
index 0000000..3999965
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointMediaQueryException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointMediaQueryException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an illegal media query is detected.
+ */
+class InvalidBreakpointMediaQueryException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointNameException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointNameException.php
new file mode 100644
index 0000000..1121465
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointNameException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointNameException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an invalid name is detected.
+ */
+class InvalidBreakpointNameException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceException.php
new file mode 100644
index 0000000..3ad5556
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointSourceException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an invalid source is detected.
+ */
+class InvalidBreakpointSourceException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceTypeException.php b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceTypeException.php
new file mode 100644
index 0000000..d9e2483
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/InvalidBreakpointSourceTypeException.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\InvalidBreakpointSourceTypeException.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\breakpoint\InvalidBreakpointException;
+
+/**
+ * Exception thrown if an invalid source_type is detected.
+ */
+class InvalidBreakpointSourceTypeException extends InvalidBreakpointException {}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointAPITest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointAPITest.php
new file mode 100644
index 0000000..302d673
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointAPITest.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointAPITest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointsTestBase;
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\InvalidBreakpointNameException;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+
+/**
+ * Tests for general breakpoint API functions.
+ */
+class BreakpointAPITest extends BreakpointTestBase {
+
+  /**
+   * Drupal\simpletest\WebTestBase\getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Breakpoint general API functions',
+      'description' => 'Test general API functions of the breakpoint module.',
+      'group' => 'Breakpoint',
+    );
+  }
+
+  /**
+   * Test Breakpoint::buildConfigName().
+   */
+  public function testConfigName() {
+    // Try an invalid sourceType.
+    $breakpoint = entity_create('breakpoint', array(
+      'label' => drupal_strtolower($this->randomName()),
+      'source' => 'custom_module',
+      'sourceType' => 'oops',
+    ));
+
+    $exception = FALSE;
+    try {
+      $breakpoint->save();
+    }
+    catch (InvalidBreakpointSourceTypeException $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('breakpoint_config_name: An exception is thrown when an invalid sourceType is entered.'));
+
+    // Try an invalid source.
+    $breakpoint->id = '';
+    $breakpoint->sourceType = Breakpoint::SOURCE_TYPE_CUSTOM;
+    $breakpoint->source = 'custom*_module source';
+
+    $exception = FALSE;
+    try {
+      $breakpoint->save();
+    }
+    catch (InvalidBreakpointSourceException $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('breakpoint_config_name: An exception is thrown when an invalid source is entered.'));
+
+    // Try an invalid name (make sure there is at least once capital letter).
+    $breakpoint->id = '';
+    $breakpoint->source = 'custom_module';
+    $breakpoint->name = drupal_ucfirst($this->randomName());
+
+    $exception = FALSE;
+    try {
+      $breakpoint->save();
+    }
+    catch (InvalidBreakpointNameException $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('breakpoint_config_name: An exception is thrown when an invalid name is entered.'));
+
+    // Try a valid breakpoint.
+    $breakpoint->id = '';
+    $breakpoint->name = drupal_strtolower($this->randomName());
+    $breakpoint->mediaQuery = 'all';
+
+    $exception = FALSE;
+    try {
+      $breakpoint->save();
+    }
+    catch (\Exception $e) {
+      $exception = TRUE;
+    }
+    $this->assertFalse($exception, t('breakpoint_config_name: No exception is thrown when a valid breakpoint is passed.'));
+    $this->assertEqual($breakpoint->id(), Breakpoint::SOURCE_TYPE_CUSTOM . '.custom_module.' . $breakpoint->name, t('breakpoint_config_name: A id is set when a valid breakpoint is passed.'));
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCrudTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCrudTest.php
new file mode 100644
index 0000000..38bc183
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCrudTest.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointCrudTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointTestBase;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Tests for breakpoint CRUD operations.
+ */
+class BreakpointCrudTest extends BreakpointTestBase {
+
+  /**
+   * Drupal\simpletest\WebTestBase\getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Breakpoint CRUD operations',
+      'description' => 'Test creation, loading, updating, deleting of breakpoints.',
+      'group' => 'Breakpoint',
+    );
+  }
+
+  /**
+   * Test CRUD operations for breakpoints.
+   */
+  public function testBreakpointCrud() {
+    // Add a breakpoint with minimum data only.
+    $breakpoint = entity_create('breakpoint', array(
+      'label' => drupal_strtolower($this->randomName()),
+      'mediaQuery' => '(min-width: 600px)',
+    ));
+    $breakpoint->save();
+
+    $this->verifyBreakpoint($breakpoint);
+
+    // Test breakpoint_load_all
+    $all_breakpoints = entity_load_multiple('breakpoint');
+    $config_name = $breakpoint->id();
+    $this->assertTrue(isset($all_breakpoints[$config_name]), t('breakpoint_load_all: New breakpoint is present when loading all breakpoints.'));
+    $this->verifyBreakpoint($breakpoint, $all_breakpoints[$config_name]);
+
+    // Update the breakpoint.
+    $breakpoint->weight = 1;
+    $breakpoint->multipliers['2x'] = '2x';
+    $breakpoint->save();
+    $this->verifyBreakpoint($breakpoint);
+
+    // Delete the breakpoint.
+    $breakpoint->delete();
+    $this->assertFalse(breakpoint_load($config_name), t('breakpoint_load: Loading a deleted breakpoint returns false.'), t('Breakpoints API'));
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupAPITest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupAPITest.php
new file mode 100644
index 0000000..6639bda
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupAPITest.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointGroupAPITest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointsTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\InvalidBreakpointNameException;
+use Drupal\breakpoint\InvalidBreakpointSourceException;
+use Drupal\breakpoint\InvalidBreakpointSourceTypeException;
+
+/**
+ * Tests for general breakpoint group API functions.
+ */
+class BreakpointGroupAPITest extends BreakpointGroupTestBase {
+
+  /**
+   * Drupal\simpletest\WebTestBase\getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Breakpoint group general API functions',
+      'description' => 'Test general API functions of the breakpoint module.',
+      'group' => 'Breakpoint',
+    );
+  }
+
+  /**
+   * Test Breakpoint::buildConfigName().
+   */
+  public function testConfigName() {
+    // Try an invalid sourceType.
+    $label = $this->randomName();
+    $breakpoint_group = entity_create('breakpoint_group', array(
+      'label' => $label,
+      'name' => drupal_strtolower($label),
+      'source' => 'custom_module',
+      'sourceType' => 'oops',
+    ));
+
+    $exception = FALSE;
+    try {
+      $breakpoint_group->save();
+    }
+    catch (InvalidBreakpointSourceTypeException $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('An exception is thrown when an invalid sourceType is entered.'));
+
+    // Try an invalid source.
+    $breakpoint_group->name = '';
+    $breakpoint_group->sourceType = Breakpoint::SOURCE_TYPE_CUSTOM;
+    $breakpoint_group->source = 'custom*_module source';
+
+    $exception = FALSE;
+    try {
+      $breakpoint_group->save();
+    }
+    catch (InvalidBreakpointSourceException $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('An exception is thrown when an invalid source is entered.'));
+
+    // Try a valid breakpoint_group.
+    $breakpoint_group->name = 'test';
+    $breakpoint_group->source = 'custom_module_source';
+
+    $exception = FALSE;
+    try {
+      $breakpoint_group->save();
+    }
+    catch (\Exception $e) {
+      $exception = TRUE;
+    }
+    $this->assertFalse($exception, t('No exception is thrown when a valid data is passed.'));
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCrudTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCrudTest.php
new file mode 100644
index 0000000..3d8fd9b
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCrudTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointGroupCrudTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointGroupTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Tests for breakpoint group CRUD operations.
+ */
+class BreakpointGroupCrudTest extends BreakpointGroupTestBase {
+
+  /**
+   * Drupal\simpletest\WebTestBase\getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Breakpoint group CRUD operations',
+      'description' => 'Test creation, loading, updating, deleting of breakpoint groups.',
+      'group' => 'Breakpoint',
+    );
+  }
+
+  /**
+   * Test CRUD operations for breakpoint groups.
+   */
+  public function testBreakpointGroupCrud() {
+    // Add breakpoints.
+    $breakpoints = array();
+    for ($i = 0; $i <= 3; $i++) {
+      $width = ($i + 1) * 200;
+      $breakpoint = entity_create('breakpoint', array(
+        'name' => drupal_strtolower($this->randomName()),
+        'weight' => $i,
+        'mediaQuery' => "(min-width: {$width}px)",
+      ));
+      $breakpoint->save();
+      $breakpoints[$breakpoint->id()] = $breakpoint;
+    }
+    // Add a breakpoint group with minimum data only.
+    $label = $this->randomName();
+
+    $group = entity_create('breakpoint_group', array(
+      'label' => $label,
+      'name' => drupal_strtolower($label),
+    ));
+    $group->save();
+    $this->verifyBreakpointGroup($group);
+
+    // Update the breakpoint group.
+    $group->breakpoints = array_keys($breakpoints);
+    $group->save();
+    $this->verifyBreakpointGroup($group);
+
+    // Duplicate the breakpoint group.
+    $new_set = entity_create('breakpoint_group', array(
+      'breakpoints' => $group->breakpoints,
+      'name' => 'clone_of_' . $group->name,
+    ));
+    $duplicated_set = $group->duplicate();
+    $this->verifyBreakpointGroup($duplicated_set, $new_set);
+
+    // Delete the breakpoint group.
+    $group->delete();
+    $this->assertFalse(entity_load('breakpoint_group', $group->id()), t('breakpoint_group_load: Loading a deleted breakpoint group returns false.'), t('Breakpoints API'));
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupTestBase.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupTestBase.php
new file mode 100644
index 0000000..24a50ab
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupTestBase.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointGroupTestBase.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+
+/**
+ * Base class for Breakpoint group tests.
+ */
+abstract class BreakpointGroupTestBase extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('breakpoint');
+
+  /**
+   * Drupal\simpletest\WebTestBase\setUp().
+   */
+  public function setUp() {
+    parent::setUp();
+  }
+
+  /**
+   * Verify that a breakpoint is properly stored.
+   */
+  public function verifyBreakpointGroup(BreakpointGroup $group, BreakpointGroup $compare_set = NULL) {
+    $properties = array(
+      'label',
+      'id',
+      'name',
+      'breakpoints',
+      'sourceType',
+    );
+    $assert_set = t('Breakpoints API');
+
+    // Verify breakpoint_group_load().
+    $compare_set = is_null($compare_set) ? entity_load('breakpoint_group', $group->id()) : $compare_set;
+
+    foreach ($properties as $property) {
+      $t_args = array(
+        '%group' => $group->label(),
+        '%property' => $property,
+      );
+      if (is_array($compare_set->{$property})) {
+        $this->assertEqual(array_keys($compare_set->{$property}), array_keys($group->{$property}), t('breakpoint_group_load: Proper %property for breakpoint group %group.', $t_args), $assert_set);
+      }
+      else {
+        $t_args = array(
+          '%group' => $group->label(),
+          '%property' => $property,
+          '%property1' => $compare_set->{$property},
+          '%property2' => $group->{$property},
+        );
+        $this->assertEqual($compare_set->{$property}, $group->{$property}, t('breakpoint_group_load: Proper %property: %property1 == %property2 for breakpoint group %group.', $t_args), $assert_set);
+      }
+    }
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
new file mode 100644
index 0000000..1ebda06
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointMediaQueryTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\breakpoint\Breakpoint;
+use Drupal\breakpoint\InvalidBreakpointMediaQueryException;
+
+/**
+ * Tests for media queries in a breakpoint.
+ */
+class BreakpointMediaQueryTest extends UnitTestBase {
+
+  /**
+   * Drupal\simpletest\WebTestBase\getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Breakpoint media query tests',
+      'description' => 'Test validation of media queries.',
+      'group' => 'Breakpoint',
+    );
+  }
+
+  /**
+   * Test valid media queries.
+   */
+  public function testValidMediaQueries() {
+    $media_queries = array(
+      // Bartik breakpoints.
+      '(min-width: 0px)',
+      'all and (min-width: 560px) and (max-width:850px)',
+      'all and (min-width: 851px)',
+      // Seven breakpoints.
+      '(min-width: 0em)',
+      'screen and (min-width: 40em)',
+      // Stark breakpoints.
+      '(min-width: 0px)',
+      'all and (min-width: 480px) and (max-width: 959px)',
+      'all and (min-width: 960px)',
+      '(orientation)',
+      'all and (orientation)',
+      'not all and (orientation)',
+      'only all and (orientation)',
+      'screen and (width)',
+      'screen and (width: 0)',
+      'screen and (width: 0px)',
+      'screen and (width: 0em)',
+      'screen and (min-width: -0)',
+      'screen and (max-width: 0)',
+      'screen and (max-width: 0.3)',
+      'screen and (min-width)',
+      // Multiline and comments.
+      'screen and /* this is a comment */ (min-width)',
+      "screen\nand /* this is a comment */ (min-width)",
+      "screen\n\nand /* this is\n a comment */ (min-width)",
+    );
+
+    foreach ($media_queries as $media_query) {
+      $this->assertTrue(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is valid.');
+    }
+  }
+
+  /**
+   * Test invalid media queries.
+   */
+  public function testInvalidMediaQueries() {
+    $media_queries = array(
+      '',
+      'not (orientation)',
+      'only (orientation)',
+      'all and not all',
+      'screen and (width: 0xx)',
+      'screen and (width: -8xx)',
+      'screen and (width: -xx)',
+      'screen and (width: xx)',
+      'screen and (width: px)',
+      'screen and (width: -8px)',
+      'screen and (width: -0.8px)',
+      'screen and (height: 0xx)',
+      'screen and (height: -8xx)',
+      'screen and (height: -xx)',
+      'screen and (height: xx)',
+      'screen and (height: px)',
+      'screen and (height: -8px)',
+      'screen and (height: -0.8px)',
+      'screen and (device-width: 0xx)',
+      'screen and (device-width: -8xx)',
+      'screen and (device-width: -xx)',
+      'screen and (device-width: xx)',
+      'screen and (device-width: px)',
+      'screen and (device-width: -8px)',
+      'screen and (device-width: -0.8px)',
+      'screen and (device-height: 0xx)',
+      'screen and (device-height: -8xx)',
+      'screen and (device-height: -xx)',
+      'screen and (device-height: xx)',
+      'screen and (device-height: px)',
+      'screen and (device-height: -8px)',
+      'screen and (device-height: -0.8px)',
+      'screen and (min-orientation)',
+      'screen and (max-orientation)',
+      'screen and (min-orientation: landscape)',
+      'screen and (max-orientation: landscape)',
+      'screen and (orientation: bogus)',
+      '(orientation: bogus)',
+    );
+
+    foreach ($media_queries as $media_query) {
+      try {
+        $this->assertFalse(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is not valid.');
+      }
+      catch (InvalidBreakpointMediaQueryException $e) {
+        $this->assertTrue(TRUE, $media_query . ' is not valid.');
+      }
+    }
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php
new file mode 100644
index 0000000..27b73c7
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointTestBase.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Base class for Breakpoint tests.
+ */
+abstract class BreakpointTestBase extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('breakpoint');
+
+  /**
+   * Drupal\simpletest\WebTestBase\setUp().
+   */
+  public function setUp() {
+    parent::setUp();
+  }
+
+  /**
+   * Verify that a breakpoint is properly stored.
+   */
+  public function verifyBreakpoint(Breakpoint $breakpoint, Breakpoint $compare_breakpoint = NULL) {
+    $properties = array(
+      'label',
+      'mediaQuery',
+      'source',
+      'sourceType',
+      'weight',
+      'multipliers',
+    );
+    $assert_group = t('Breakpoints API');
+
+    // Verify breakpoint_load().
+    $compare_breakpoint = is_null($compare_breakpoint) ? breakpoint_load($breakpoint->id()) : $compare_breakpoint;
+    foreach ($properties as $property) {
+      $t_args = array(
+        '%breakpoint' => $breakpoint->label(),
+        '%property' => $property,
+      );
+      $this->assertEqual($compare_breakpoint->{$property}, $breakpoint->{$property}, t('breakpoint_load: Proper %property for breakpoint %breakpoint.', $t_args), $assert_group);
+    }
+  }
+}
diff --git a/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php
new file mode 100644
index 0000000..7072e02
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointsThemeTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointGroupTestBase;
+use Drupal\breakpoint\BreakpointGroup;
+use Drupal\breakpoint\Breakpoint;
+
+/**
+ * Test breakpoints provided by themes.
+ */
+class BreakpointThemeTest extends BreakpointGroupTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('breakpoint_theme_test');
+
+  /**
+   * Drupal\simpletest\WebTestBase\getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Breakpoint theme functionality',
+      'description' => 'Thoroughly test the breakpoints provided by a theme.',
+      'group' => 'Breakpoint',
+    );
+  }
+
+  /**
+   * Drupal\simpletest\WebTestBase\setUp().
+   */
+  public function setUp() {
+    parent::setUp();
+    theme_enable(array('breakpoint_test_theme'));
+  }
+
+  /**
+   * Test the breakpoints provided by a theme.
+   */
+  public function testThemeBreakpoints() {
+    // Verify the breakpoint group for breakpoint_test_theme was created.
+    $breakpoint_group_obj = entity_create('breakpoint_group', array(
+      'label' => 'Breakpoint test theme',
+      'name' => 'breakpoint_test_theme',
+      'source' => 'breakpoint_test_theme',
+      'sourceType' => Breakpoint::SOURCE_TYPE_THEME,
+      'id' => Breakpoint::SOURCE_TYPE_THEME . '.breakpoint_test_theme.breakpoint_test_theme',
+    ));
+    $breakpoint_group_obj->breakpoints = array(
+      'theme.breakpoint_test_theme.mobile' => array(),
+      'theme.breakpoint_test_theme.narrow' => array(),
+      'theme.breakpoint_test_theme.wide' => array(),
+      'theme.breakpoint_test_theme.tv' => array(),
+    );
+
+    // Verify we can load this breakpoint defined by the theme.
+    $this->verifyBreakpointGroup($breakpoint_group_obj);
+
+    // Disable the test theme and verify the breakpoint group is deleted.
+    theme_disable(array('breakpoint_test_theme'));
+    $this->assertFalse(entity_load('breakpoint_group', $breakpoint_group_obj->id()), t('breakpoint_group_load: Loading a deleted breakpoint group returns false.'), t('Breakpoints API'));
+  }
+
+  /**
+   * Test the breakpoints defined by the custom group.
+   */
+  public function testThemeBreakpointGroup() {
+    // Verify the breakpoint group 'test' was created by breakpoint_test_theme.
+    $breakpoint_group_obj = entity_create('breakpoint_group', array(
+      'label' => 'Test',
+      'name' => 'test',
+      'sourceType' => Breakpoint::SOURCE_TYPE_THEME,
+      'source' => 'breakpoint_test_theme',
+      'id' => Breakpoint::SOURCE_TYPE_THEME . '.breakpoint_test_theme.test',
+    ));
+    $breakpoint_group_obj->breakpoints = array(
+      'theme.breakpoint_test_theme.mobile' => array('1.5x', '2.x'),
+      'theme.breakpoint_test_theme.narrow' => array(),
+      'theme.breakpoint_test_theme.wide' => array(),
+    );
+
+    // Verify we can load this breakpoint defined by the theme.
+    $this->verifyBreakpointGroup($breakpoint_group_obj);
+
+    // Disable the test theme and verify the breakpoint group is deleted.
+    theme_disable(array('breakpoint_test_theme'));
+    $this->assertFalse(entity_load('breakpoint_group', $breakpoint_group_obj->id()), t('breakpoint_group_load: Loading a deleted breakpoint group returns false.'), t('Breakpoints API'));
+  }
+
+  /**
+   * Test the breakpoints defined by the custom group in the module.
+   */
+  public function testThemeBreakpointGroupModule() {
+    // Call the import manually, since the testbot needs to enable the module
+    // first, otherwise the theme isn't detected.
+    _breakpoint_import_breakpoint_groups('breakpoint_theme_test', Breakpoint::SOURCE_TYPE_MODULE);
+
+    // Verify the breakpoint group 'module_test' was created by
+    // breakpoint_theme_test module.
+    $breakpoint_group_obj = entity_create('breakpoint_group', array(
+      'label' => 'Test Module',
+      'name' => 'module_test',
+      'sourceType' => Breakpoint::SOURCE_TYPE_MODULE,
+      'source' => 'breakpoint_theme_test',
+      'id' => Breakpoint::SOURCE_TYPE_MODULE . '.breakpoint_theme_test.module_test',
+    ));
+    $breakpoint_group_obj->breakpoints = array(
+      'theme.breakpoint_test_theme.mobile' => array(),
+      'theme.breakpoint_test_theme.narrow' => array(),
+      'theme.breakpoint_test_theme.wide' => array(),
+    );
+
+    // Verify we can load this breakpoint defined by the theme.
+    $this->verifyBreakpointGroup($breakpoint_group_obj);
+
+    // Disable the test theme and verify the breakpoint group still exists.
+    theme_disable(array('breakpoint_test_theme'));
+    $this->assertTrue(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'Breakpoint group still exists if theme is disabled.');
+
+    // Disable the test module and verify the breakpoint group still exists.
+    module_disable(array('breakpoint_theme_test'));
+    $this->assertTrue(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'Breakpoint group still exists if module is disabled.');
+
+    // Uninstall the test module and verify the breakpoint group is deleted.
+    module_uninstall(array('breakpoint_theme_test'));
+    $this->assertFalse(entity_load('breakpoint_group', $breakpoint_group_obj->id()), 'Breakpoint group is removed if module is uninstalled.');
+  }
+
+}
diff --git a/core/modules/breakpoint/tests/breakpoint_theme_test.info b/core/modules/breakpoint/tests/breakpoint_theme_test.info
new file mode 100644
index 0000000..d2896f6
--- /dev/null
+++ b/core/modules/breakpoint/tests/breakpoint_theme_test.info
@@ -0,0 +1,6 @@
+name = Breakpoint theme test
+description = Test breakpoints provided by themes
+package = Other
+core = 8.x
+hidden = TRUE
+dependencies[] = breakpoint
diff --git a/core/modules/breakpoint/tests/breakpoint_theme_test.module b/core/modules/breakpoint/tests/breakpoint_theme_test.module
new file mode 100644
index 0000000..50b5ff0
--- /dev/null
+++ b/core/modules/breakpoint/tests/breakpoint_theme_test.module
@@ -0,0 +1,13 @@
+<?php
+/**
+ * @file
+ * Test breakpoint functionality for breakpoints provided by themes.
+ */
+
+/**
+ * Implements hook_system_theme_info().
+ */
+function breakpoint_theme_test_system_theme_info() {
+  $themes['breakpoint_test_theme'] = drupal_get_path('module', 'breakpoint_theme_test') . '/themes/breakpoint_test_theme/breakpoint_test_theme.info';
+  return $themes;
+}
diff --git a/core/modules/breakpoint/tests/config/breakpoint_theme_test.breakpoint_groups.yml b/core/modules/breakpoint/tests/config/breakpoint_theme_test.breakpoint_groups.yml
new file mode 100644
index 0000000..4e3e6f0
--- /dev/null
+++ b/core/modules/breakpoint/tests/config/breakpoint_theme_test.breakpoint_groups.yml
@@ -0,0 +1,6 @@
+module_test:
+  label: Test Module
+  breakpoints:
+    - theme.breakpoint_test_theme.mobile
+    - theme.breakpoint_test_theme.narrow
+    - theme.breakpoint_test_theme.wide
diff --git a/core/modules/breakpoint/tests/themes/breakpoint_test_theme/breakpoint_test_theme.info b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/breakpoint_test_theme.info
new file mode 100644
index 0000000..e3dab72
--- /dev/null
+++ b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/breakpoint_test_theme.info
@@ -0,0 +1,5 @@
+name = Breakpoint test theme
+description = Test theme for breakpoint.
+core = 8.x
+base theme = bartik
+hidden = TRUE
diff --git a/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoint_groups.yml b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoint_groups.yml
new file mode 100644
index 0000000..2c1e9e3
--- /dev/null
+++ b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoint_groups.yml
@@ -0,0 +1,6 @@
+test:
+  label: Test
+  breakpoints:
+    - mobile
+    - narrow
+    - wide
diff --git a/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoints.yml b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoints.yml
new file mode 100644
index 0000000..534fbf5
--- /dev/null
+++ b/core/modules/breakpoint/tests/themes/breakpoint_test_theme/config/breakpoint_test_theme.breakpoints.yml
@@ -0,0 +1,4 @@
+mobile: '(min-width: 0px)'
+narrow: '(min-width: 560px)'
+wide: '(min-width: 851px)'
+tv: 'only screen and (min-width: 3456px)'
