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..741c89d
--- /dev/null
+++ b/core/modules/breakpoint/breakpoint.module
@@ -0,0 +1,504 @@
+<?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() {
+  config_install_default_config('module', 'breakpoint');
+  $themes = list_themes();
+  breakpoint_themes_enabled(array_keys($themes));
+  $modules = module_list();
+  breakpoint_modules_enabled(array_keys($modules));
+}
+
+/**
+ * Implements hook_themes_enabled().
+ *
+ * Import breakpoints from all new enabled themes.
+ *
+ * @param array $theme_list
+ *   An array of theme names.
+ */
+function breakpoint_themes_enabled($theme_list) {
+  $themes = list_themes();
+  foreach ($theme_list as $theme_key) {
+    if ($themes[$theme_key]->status) {
+      $theme_breakpoints = breakpoint_get_theme_breakpoint_list($theme_key);
+      if (!empty($theme_breakpoints)) {
+        if ($breakpoint_group = _breakpoint_import_breakpoints($themes[$theme_key]->info['name'], $theme_key, Breakpoint::SOURCE_TYPE_THEME, $theme_breakpoints)) {
+          $breakpoint_group->save();
+          $uri = $breakpoint_group->uri();
+          if ($uri) {
+            $uri_options = $uri;
+            unset($uri_options['path']);
+            $uri = $uri['path'];
+          }
+          $message = t('The breakpoints from theme %theme are imported.', array(
+            '%theme' => check_plain($themes[$theme_key]->info['name']),
+          ));
+          if (module_exists('breakpoint_ui') && $uri) {
+            $message .= '<p>' . l(t('A new breakpoint group is created for theme %theme.', array(
+              '%theme' => check_plain($themes[$theme_key]->info['name']),
+            )), $uri, $uri_options);
+          }
+          drupal_set_message($message, 'status');
+        }
+      }
+
+      // Import custom groups.
+      _breakpoint_import_breakpoint_groups($theme_key, Breakpoint::SOURCE_TYPE_THEME);
+    }
+  }
+}
+
+/**
+ * Implements hook_themes_disabled().
+ *
+ * Remove breakpoints from all disabled themes.
+ *
+ * @param array $theme_list
+ *   An array of theme names.
+ */
+function breakpoint_themes_disabled($theme_list) {
+  _breakpoint_delete_breakpoints($theme_list, Breakpoint::SOURCE_TYPE_THEME);
+}
+
+/**
+ * Implements hook_modules_enabled().
+ *
+ * Import breakpoints from all new enabled modules.
+ *
+ * @param $modules
+ *   An array of the modules that were enabled.
+ */
+function breakpoint_modules_enabled($modules) {
+  foreach ($modules as $module) {
+    $module_breakpoints = breakpoint_get_module_breakpoint_list($module);
+    if (!empty($module_breakpoints)) {
+      if ($breakpoint_group = _breakpoint_import_breakpoints($module, $module, Breakpoint::SOURCE_TYPE_MODULE, $module_breakpoints)) {
+        $breakpoint_group->save();
+        $uri = $breakpoint_group->uri();
+        if ($uri) {
+          $uri_options = $uri;
+          unset($uri_options['path']);
+          $uri = $uri['path'];
+        }
+        $message = t('The breakpoints from module %module are imported.', array(
+          '%module' => $module,
+        ));
+        if (module_exists('breakpoint_ui') && $uri) {
+          $message .= '<p>' . l(t('A new breakpoint group is created for module %module.', array(
+            '%module' => $module,
+          )), $uri, $uri_options);
+        }
+        drupal_set_message($message, 'status');
+      }
+    }
+
+    // Import custom groups.
+    _breakpoint_import_breakpoint_groups($module, Breakpoint::SOURCE_TYPE_MODULE);
+  }
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ *
+ * Remove breakpoints from all uninstalled modules.
+ *
+ * @param $modules
+ *   An array of the modules that were uninstalled.
+ */
+function breakpoint_modules_uninstalled($modules) {
+  _breakpoint_delete_breakpoints($modules, Breakpoint::SOURCE_TYPE_MODULE);
+}
+
+/**
+ * Import breakpoints from theme or module.
+ *
+ * @param string $label
+ *   Name of the breakpoint group.
+ * @param string $id
+ *   Id of the breakpoint group.
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ * @param array $media_queries
+ *   Array of media queries keyed by id.
+ *
+ * @return Drupal\breakpoint\BreakpointGroup|false
+ *   Returns the new breakpoint group if one is created.
+ */
+function _breakpoint_import_breakpoints($label, $id, $source_type, $media_queries) {
+  if (!empty($media_queries)) {
+    $weight = 0;
+    // Use the existing breakpoint group if it exists.
+    $breakpoint_group = entity_load('breakpoint_group', $source_type . '.' . $id);
+    if (!$breakpoint_group) {
+      // Build a new breakpoint group.
+      $breakpoint_group = entity_create('breakpoint_group', array(
+        'id' => $id,
+        'label' => $label,
+        'source' => $id,
+        'sourceType' => $source_type,
+      ));
+    }
+    else {
+      // Reset label.
+      $breakpoint_group->label = $label;
+    }
+    foreach ($media_queries as $name => $media_query) {
+      // Use the existing breakpoint if it exists.
+      $breakpoint = entity_load('breakpoint', $source_type . '.' . $id . '.' . $name);
+      if (!$breakpoint) {
+        // Build a new breakpoint.
+        $breakpoint = entity_create('breakpoint', array(
+          'name' => $name,
+          'label' => drupal_ucfirst($name),
+          'mediaQuery' => $media_query,
+          'source' => $id,
+          'sourceType' => $source_type,
+          'weight' => $weight++,
+        ));
+        $breakpoint->save();
+      }
+      else {
+        // Reset name, label, weight and media query.
+        $breakpoint->name = $name;
+        $breakpoint->label = drupal_ucfirst($name);
+        $breakpoint->mediaQuery = $media_query;
+        $breakpoint->weight = $weight++;
+      }
+      $breakpoint_group->breakpoints[$breakpoint->id()] = $breakpoint;
+    }
+    return $breakpoint_group;
+  }
+  return FALSE;
+}
+
+/**
+ * Import breakpoint groups from theme or module.
+ *
+ * @param string $group_id
+ *   Id of the breakpoint group.
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ *
+ * @return boolean
+ */
+function _breakpoint_import_breakpoint_groups($group_id, $source_type) {
+  $breakpoint_groups = config($group_id . '.breakpoint_groups');
+  if ($breakpoint_groups) {
+    foreach ($breakpoint_groups->get() as $id => $data) {
+      // Breakpoints is mandatory.
+      if (isset($data['breakpoints']) && !empty($data['breakpoints'])) {
+        // Use the existing breakpoint group if it exists.
+        $breakpoint_group = entity_load('breakpoint_group', $source_type . '.' . $id);
+        if (!$breakpoint_group) {
+          $breakpoint_group = entity_create('breakpoint_group', array(
+            'id' => $id,
+            'label' => isset($data['label']) ? $data['label'] : drupal_ucfirst($data[$id]),
+            'source' => $group_id,
+            'sourceType' => $source_type,
+          ));
+        }
+        else {
+          // Reset label.
+          $breakpoint_group->label = isset($data['label']) ? $data['label'] : drupal_ucfirst($data[$id]);
+        }
+        foreach ($data['breakpoints'] as $breakpoint_id => $multipliers) {
+          // Check if breakpoint exists, assume short name.
+          $breakpoint = entity_load('breakpoint', $source_type . '.' . $group_id . '.' . $breakpoint_id);
+          // If the breakpoint doesn't exist, try using the full name.
+          if (!$breakpoint) {
+            $breakpoint = entity_load('breakpoint', $breakpoint_id);
+          }
+          if ($breakpoint) {
+            // Check if the multipliers are set, if not set them now.
+            if (is_array($multipliers) && !empty($multipliers)) {
+              // Check settings first.
+              $settings = breakpoint_settings();
+              $defined_multipliers = $settings->multipliers;
+              $new_multipliers = array_diff($multipliers, $defined_multipliers);
+              if (!empty($new_multipliers)) {
+                $defined_multipliers = array_merge($defined_multipliers, $new_multipliers);
+                breakpoint_settings_save_multipliers($defined_multipliers);
+              }
+
+              // Check breakpoint multipliers.
+              $multipliers = drupal_map_assoc(array_values($multipliers));
+              $new_multipliers = array_diff($multipliers, $breakpoint->multipliers);
+              if (!empty($new_multipliers)) {
+                $breakpoint->multipliers += $new_multipliers;
+                $breakpoint->save();
+              }
+            }
+
+            // Add breakpoint to group.
+            $breakpoint_group->breakpoints[$breakpoint->id()] = $breakpoint;
+            $breakpoint_group->save();
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Remove breakpoints from all disabled themes or uninstalled modules.
+ *
+ * @param array $ids
+ *   Id's of the breakpoint group.
+ * @param string $sourceType
+ *   Either Breakpoint::SOURCE_TYPE_THEME or Breakpoint::SOURCE_TYPE_MODULE.
+ *
+ */
+function _breakpoint_delete_breakpoints($ids, $source_type) {
+  $breakpoint_groups = entity_load_multiple('breakpoint_group', $ids);
+  foreach ($breakpoint_groups as $breakpoint_group) {
+    if ($breakpoint_group->sourceType == $source_type) {
+      // delete the default group.
+      $breakpoint_group->delete();
+
+      // delete all breakpoints defined by this theme/module.
+      $names = drupal_container()->get('config.storage')->listAll('breakpoint.breakpoint.' . $source_type . '.' . $breakpoint_group->id() . '.');
+      $entity_info = entity_get_info('breakpoint');
+
+      foreach ($names as &$name) {
+        $name = drupal_substr($name, drupal_strlen($entity_info['config prefix']) + 1);
+      }
+      $breakpoints = entity_load_multiple('breakpoint', $names);
+
+      foreach ($breakpoints as $breakpoint) {
+        if ($breakpoint->sourceType == $source_type && $breakpoint->source = $breakpoint_group->id) {
+          $breakpoint->delete();
+        }
+      }
+    }
+  }
+  // Deletet 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
+ *   Id 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();
+    }
+  }
+}
+
+/**
+ * Load general settings.
+ *
+ * @return array
+ *   array containing general breakpoint settings.
+ */
+function breakpoint_settings() {
+  $config = config('breakpoint');
+  if ($config->isNew()) {
+    return FALSE;
+  }
+  return (object)$config->get();
+}
+
+/**
+ * Save multipliers to settings.
+ *
+ * @param array $multipliers
+ *   array containing multipliers.
+ */
+function breakpoint_settings_save_multipliers($multipliers) {
+  $config = config('breakpoint');
+  $config->set('multipliers', $multipliers);
+  $config->save();
+}
+
+/**
+ * Reload breakpoint groups as they were defined in the theme.
+ *
+ * @param string $theme_key
+ *   The name of the theme.
+ *
+ * @return BreakpointGroup
+ *   Returns a BreakpointGroup containing the breakpoints defined by the theme.
+ */
+function breakpoint_group_reload_from_theme($theme_key) {
+  // Clear caches so theme info is fresh.
+  system_rebuild_theme_data();
+  drupal_theme_rebuild();
+
+  $themes = list_themes();
+  if (isset($themes[$theme_key]) && $themes[$theme_key]->status) {
+    $theme_breakpoints = breakpoint_get_theme_breakpoint_list($theme_key);
+    if (!empty($theme_breakpoints)) {
+      return _breakpoint_import_breakpoints($themes[$theme_key]->info['name'], $theme_key, Breakpoint::SOURCE_TYPE_THEME, $theme_breakpoints);
+    }
+  }
+  return FALSE;
+}
+
+/**
+ * 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_breakpoint_list($theme_key) {
+  $themes = list_themes();
+  if (!isset($themes[$theme_key])) {
+    return array();
+  }
+
+  $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_breakpoint_list($module) {
+  if (!module_exists($module)) {
+    return array();
+  }
+
+  $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 Needed for menu_callback and machine_name.
+ *
+ * @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 Needed for menu_callback and machine_name.
+ *
+ * @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_select_options() {
+  $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_select_options() {
+  $options = array();
+  $breakpoints = entity_load_multiple('breakpoint');
+  foreach ($breakpoints as $breakpoint) {
+    $options[$breakpoint->id()] = $breakpoint->label() . ' (' . $breakpoint->source . ' - ' . $breakpoint->sourceType .   ') [' . $breakpoint->mediaQuery . ']';
+  }
+
+  return $options;
+}
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..f0113e1
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Breakpoint.php
@@ -0,0 +1,374 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Breakpoint.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Component\Uuid\Uuid;
+use Exception;
+
+/**
+ * Defines the Breakpoint entity.
+ */
+class Breakpoint extends ConfigEntityBase {
+
+  /**
+   * The possible values for sourceType.
+   */
+  const SOURCE_TYPE_THEME = 'theme';
+  const SOURCE_TYPE_MODULE = 'module';
+  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).
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * The breakpoint label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The breakpoint media query.
+   *
+   * @var string
+   */
+  public $mediaQuery = '';
+
+  /**
+   * The original media query.
+   *
+   * @var string
+   */
+  public $originalMediaQuery = '';
+
+  /**
+   * 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 status.
+   *
+   * @var string
+   */
+  public $status = TRUE;
+
+  /**
+   * The breakpoint weight.
+   *
+   * @var weight
+   */
+  public $weight = 0;
+
+  /**
+   * The breakpoint multipliers.
+   *
+   * @var multipliers
+   */
+  public $multipliers = array();
+
+  /**
+   * The Breakpoint overridden status.
+   *
+   * @var boolean
+   */
+  public $overridden = FALSE;
+
+  /**
+   * Overrides Drupal\config\ConfigEntityBase::__construct().
+   */
+  public function __construct(array $values = array(), $entity_type = 'breakpoint') {
+    parent::__construct($values, $entity_type);
+    if (!isset($this->uuid)) {
+      $uuid = new Uuid();
+      $this->uuid = $uuid->generate();
+    }
+  }
+
+  /**
+   * Overrides Drupal\config\ConfigEntityBase::save().
+   */
+  public function save() {
+    if (empty($this->id)) {
+      $this->id = $this->buildConfigName();
+    }
+    if (empty($this->label)) {
+      $this->label = drupal_ucfirst($this->name);
+    }
+
+    // Check the media query.
+    if (!$this->isValid()) {
+      throw new Exception(t('Invalid media query detected.'));
+    }
+    // Remove ununsed multipliers.
+    $this->multipliers = array_filter($this->multipliers);
+
+    // Add '1x' multiplier.
+    if (!array_key_exists('1x', $this->multipliers)) {
+      $this->multipliers = array('1x' => '1x') + $this->multipliers;
+    }
+    return parent::save();
+  }
+
+  /**
+   * Get config name.
+   */
+  public function getConfigName() {
+    return $this->sourceType
+      . '.' . $this->source
+      . '.' . $this->name;
+  }
+
+  /**
+   * Build config name.
+   */
+  protected function buildConfigName() {
+    // 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 Exception(
+          t(
+            "Expected one of '@custom', '@module' or '@theme' for breakpoint sourceType property but got '@sourcetype'.",
+            array(
+              '@custom' => Breakpoint::SOURCE_TYPE_CUSTOM,
+              '@module' => Breakpoint::SOURCE_TYPE_MODULE,
+              '@theme' => Breakpoint::SOURCE_TYPE_THEME,
+              '@sourcetype' => $this->sourceType,
+            )
+          )
+      );
+    }
+    // Check for illegal characters in breakpoint source.
+    if (preg_match('/[^a-z_]+/', $this->source)) {
+      throw new Exception(t("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 Exception(t("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->sourceType
+      . '.' . $this->source
+      . '.' . $this->name;
+  }
+
+  /**
+   * Override a breakpoint and save it.
+   *
+   */
+  public function override() {
+    if (!$this->overridden) {
+      $this->overridden = TRUE;
+      $this->originalMediaQuery = $this->mediaQuery;
+      $this->save();
+    }
+  }
+
+  /**
+   * Revert a breakpoint and save it.
+   *
+   */
+  public function revert() {
+    if ($this->overridden) {
+      $this->overridden = FALSE;
+      $this->mediaQuery = $this->originalMediaQuery;
+      $this->save();
+    }
+  }
+
+  /**
+   * Duplicate a breakpoint.
+   *
+   * The new breakpoint inherits the media query
+   *
+   */
+  public function duplicate() {
+    return entity_create('breakpoint', array(
+      'mediaQuery' => $this->mediaQuery,
+    ));
+  }
+
+  /**
+   * Shortcut function to enable a breakpoint and save it.
+   *
+   * @see breakpoint_action_confirm_submit()
+   */
+  public function enable() {
+    if (!$this->status) {
+      $this->status = TRUE;
+      $this->save();
+    }
+  }
+
+  /**
+   * Shortcut function to disable a breakpoint and save it.
+   *
+   * @see breakpoint_action_confirm_submit()
+   */
+  public function disable() {
+    if ($this->status) {
+      $this->status = FALSE;
+      $this->save();
+    }
+  }
+
+  /**
+   * Check if the mediaQuery is valid.
+   *
+   * @see isValidMediaQuery()
+   */
+  public function isValid() {
+    return $this::isValidMediaQuery($this->mediaQuery);
+  }
+
+  /**
+   * Is the breakpoint editable.
+   */
+  public function isEditable() {
+    // Custom breakpoints are always editable.
+    if ($this->sourceType == Breakpoint::SOURCE_TYPE_CUSTOM) {
+      return TRUE;
+    }
+    // Overridden breakpoints are editable.
+    if ($this->overridden) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Check if a mediaQuery is valid.
+   *
+   * @see http://www.w3.org/TR/css3-mediaqueries/
+   * @see http://www.w3.org/Style/CSS/Test/MediaQueries/20120229/reports/implement-report.html
+   */
+  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)) {
+                return FALSE;
+              }
+            }
+            // Full expression.
+            elseif (isset($matches[3]) && !isset($matches[4])) {
+              $value = trim($matches[3]);
+              if (!array_key_exists($matches[1], $media_features)) {
+                return FALSE;
+              }
+              if (is_array($media_features[$matches[1]])) {
+                // Check if value is allowed.
+                if (!array_key_exists($value, $media_features[$matches[1]])) {
+                  return FALSE;
+                }
+              }
+              else {
+                switch ($media_features[$matches[1]]) {
+                  case 'length':
+                    $length_matches = array();
+                    if (preg_match('/^(\-)?(\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') {
+                        return FALSE;
+                      }
+                      // If there's a unit, a number is needed as well.
+                      if ($length_matches[2] === '' && $length_matches[3] !== '') {
+                        return FALSE;
+                      }
+                    }
+                    else {
+                      return FALSE;
+                    }
+                    break;
+                }
+              }
+            }
+          }
+
+          // Check [ONLY | NOT]? S* media_type
+          elseif (preg_match('/^((?:only|not)?\s?)([\w\-]+)$/i', trim($query_part), $matches)) {
+            if ($media_type_found) {
+              return FALSE;
+            }
+            $media_type_found = TRUE;
+          }
+          else {
+            return FALSE;
+          }
+        }
+      }
+      return TRUE;
+    }
+    return FALSE;
+  }
+}
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..798b2df
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/BreakpointGroup.php
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\breakpoint\BreakpointGroup.
+ */
+
+namespace Drupal\breakpoint;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Component\Uuid\Uuid;
+
+/**
+ * Defines the BreakpointGroup entity.
+ */
+class BreakpointGroup extends ConfigEntityBase {
+
+  /**
+   * The BreakpointGroup ID (machine name).
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The BreakpointGroup UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The BreakpointGroup label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The BreakpointGroup breakpoints.
+   *
+   * @var array
+   *   Array containing all breakpoints of this group.
+   *
+   * @see Drupal\breakpoints\Breakpoint
+   */
+  public $breakpoints = array();
+
+  /**
+   * The breakpoint source: theme or module name.
+   *
+   * @var string
+   */
+  public $source = '';
+
+  /**
+   * The BreakpointGroup 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;
+
+  /**
+   * The BreakpointGroup overridden status.
+   *
+   * @var boolean
+   */
+  public $overridden = FALSE;
+
+  /**
+   * Overrides Drupal\config\ConfigEntityBase::__construct().
+   */
+  public function __construct(array $values = array(), $entity_type = 'breakpoint_group') {
+    parent::__construct($values, $entity_type);
+    // Assign a new UUID if there is none yet.
+    if (!isset($this->uuid)) {
+      $uuid = new Uuid();
+      $this->uuid = $uuid->generate();
+    }
+    $this->loadAllBreakpoints();
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity::save().
+   */
+  public function save() {
+    // Only save the keys, but return the full objects.
+    $this->breakpoints = array_keys($this->breakpoints);
+    parent::save();
+    $this->loadAllBreakpoints();
+  }
+
+  /**
+   * Override a breakpoint group.
+   */
+  public function override() {
+    // Custom breakpoint group can't be overridden.
+    if ($this->sourceType === Breakpoint::SOURCE_TYPE_CUSTOM) {
+      return FALSE;
+    }
+
+    // Mark all breakpoints as overridden.
+    foreach ($this->breakpoints as $key => $breakpoint) {
+      if ($breakpoint->sourceType === $this->sourceType && $breakpoint->source == $this->id()) {
+        $breakpoint->override();
+      }
+    }
+
+    // Mark breakpoint group as overridden.
+    $this->overridden = TRUE;
+    $this->save();
+    return $this;
+  }
+
+  /**
+   * Revert a breakpoint group after it has been overridden.
+   */
+  public function revert() {
+    if (!$this->overridden || $this->sourceType === Breakpoint::SOURCE_TYPE_CUSTOM) {
+      return FALSE;
+    }
+
+    // Reload all breakpoints from theme.
+    $reloaded_set = breakpoint_group_reload_from_theme($this->id());
+    if ($reloaded_set) {
+      $this->breakpoints = $reloaded_set->breakpoints;
+      $this->overridden = FALSE;
+      $this->save();
+    }
+    return $this;
+  }
+
+  /**
+   * Duplicate a breakpoint group.
+   *
+   * The new breakpoint group inherits the breakpoints.
+   *
+   */
+  public function duplicate() {
+    return entity_create('breakpoint_group', array(
+      'breakpoints' => $this->breakpoints,
+    ));
+  }
+
+  /**
+   * Is the breakpoint group editable.
+   */
+  public function isEditable() {
+    // Custom breakpoint groups are always editable.
+    if ($this->sourceType == Breakpoint::SOURCE_TYPE_CUSTOM) {
+      return TRUE;
+    }
+    // Overridden breakpoints groups are editable.
+    if ($this->overridden) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Load all breakpoints, remove non-existing ones.
+   */
+  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/Tests/BreakpointApiTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointApiTest.php
new file mode 100644
index 0000000..4b805ee
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointApiTest.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointsApiTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\breakpoint\Tests\BreakpointsTestBase;
+use Drupal\breakpoint\Breakpoint;
+use Exception;
+
+/**
+ * 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() {
+    $breakpoint = entity_create('breakpoint', array(
+      'label' => drupal_strtolower($this->randomName()),
+      'source' => 'custom_module',
+      // Try an invalid sourceType.
+      'sourceType' => 'oops',
+    ));
+
+    try {
+      $breakpoint->save();
+    }
+    catch (Exception $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('breakpoint_config_name: An exception is thrown when an invalid sourceType is entered.'));
+    $this->assertEqual((string) $breakpoint->id(), '', t('breakpoint_config_name: No id is set when an invalid sourceType is entered.'));
+
+    // Try an invalid source.
+    $breakpoint->sourceType = Breakpoint::SOURCE_TYPE_CUSTOM;
+    $breakpoint->source = 'custom*_module source';
+    $exception = FALSE;
+    try {
+      $breakpoint->save();
+    }
+    catch (Exception $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('breakpoint_config_name: An exception is thrown when an invalid source is entered.'));
+    $this->assertEqual((string) $breakpoint->id(), '', t('breakpoint_config_name: No id is set when an invalid sourceType is entered.'));
+
+    // Try an invalid name (make sure there is at least once capital letter).
+    $breakpoint->source = 'custom_module';
+    $breakpoint->name = drupal_ucfirst($this->randomName());
+    $exception = FALSE;
+    try {
+      $breakpoint->save();
+    }
+    catch (Exception $e) {
+      $exception = TRUE;
+    }
+    $this->assertTrue($exception, t('breakpoint_config_name: An exception is thrown when an invalid name is entered.'));
+    $this->assertEqual((string) $breakpoint->id(), '', t('breakpoint_config_name: No id is set when an invalid sourceType is entered.'));
+
+    // Try a valid breakpoint.
+    $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..e3ca30e
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointCrudTest.php
@@ -0,0 +1,73 @@
+<?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->getConfigName();
+    $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);
+
+    // Disable the breakpoint.
+    $breakpoint->disable();
+    $this->verifyBreakpoint($breakpoint);
+
+    // Enable the breakpoint.
+    $breakpoint->enable();
+    $this->verifyBreakpoint($breakpoint);
+
+    // Override the breakpoint.
+    $breakpoint->enable();
+    $this->verifyBreakpoint($breakpoint);
+
+    // Revert the breakpoint.
+    $breakpoint->enable();
+    $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/BreakpointGroupCrudTest.php b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCrudTest.php
new file mode 100644
index 0000000..fb7831c
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointGroupCrudTest.php
@@ -0,0 +1,71 @@
+<?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,
+      'id' => 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,
+    ));
+    $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..9eea156
--- /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',
+      'breakpoints',
+      'overridden',
+      '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..68438dc
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php
@@ -0,0 +1,125 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\breakpoint\Tests\BreakpointMediaQueryTest.
+ */
+
+namespace Drupal\breakpoint\Tests;
+
+use Drupal\simpletest\UnitTestBase;
+use Drupal\breakpoint\Breakpoint;
+use Exception;
+
+/**
+ * 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 (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) {
+      try {
+        $this->assertTrue(Breakpoint::isValidMediaQuery($media_query), $media_query . ' is valid.');
+      }
+      catch (Exception $e) {
+        $this->assertTrue(FALSE, $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 (Exception $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..bb89e52
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointTestBase.php
@@ -0,0 +1,57 @@
+<?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',
+      'status',
+      'weight',
+      'overridden',
+      'multipliers',
+    );
+    $assert_group = t('Breakpoints API');
+
+    // Verify breakpoint_load().
+    $compare_breakpoint = is_null($compare_breakpoint) ? breakpoint_load($breakpoint->getConfigName()) : $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..95e30d6
--- /dev/null
+++ b/core/modules/breakpoint/lib/Drupal/breakpoint/Tests/BreakpointThemeTest.php
@@ -0,0 +1,168 @@
+<?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',
+      'id' => 'breakpoint_test_theme',
+      'sourceType' => Breakpoint::SOURCE_TYPE_THEME,
+      'overridden' => FALSE,
+    ));
+    $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);
+
+    // Override the breakpoints.
+    $overridden_set = clone $breakpoint_group_obj;
+    $breakpoint_group = entity_load('breakpoint_group', 'breakpoint_test_theme');
+    $breakpoint_group = $breakpoint_group->override();
+
+    // Verify the group is overridden.
+    $overridden_set->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(),
+    );
+    $overridden_set->overridden = 1;
+    $this->verifyBreakpointGroup($overridden_set);
+
+    // Revert the breakpoint group.
+    $breakpoint_group = entity_load('breakpoint_group', 'breakpoint_test_theme');
+    $breakpoint_group = $breakpoint_group->revert();
+
+    // Verify the breakpoint group has its original values again when loaded.
+    $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_test_theme'), 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',
+      'id' => 'test',
+      'sourceType' => Breakpoint::SOURCE_TYPE_THEME,
+      'source' => 'breakpoint_test_theme',
+      'overridden' => FALSE,
+    ));
+    $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', 'test'), 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',
+      'id' => 'module_test',
+      'sourceType' => Breakpoint::SOURCE_TYPE_MODULE,
+      'source' => 'breakpoint_theme_test',
+      'overridden' => FALSE,
+    ));
+    $breakpoint_group_obj->breakpoints = array(
+      'theme.breakpoint_test_theme.mobile' => array(),
+      'theme.breakpoint_test_theme.narrow' => array('3.x', '4.x'),
+      'theme.breakpoint_test_theme.wide' => array(),
+    );
+
+    // Verify we can load this breakpoint defined by the theme.
+    $this->verifyBreakpointGroup($breakpoint_group_obj);
+
+    // Check that the multipliers are added to the settings.
+    $settings = breakpoint_settings();
+    $expected_settings = array(
+      0 => '1x',
+      1 => '1.5x',
+      2 => '2x',
+      3 => '3x',
+      4 => '4x',
+    );
+    $this->assertEqual($settings->multipliers, $expected_settings, 'Multipliers are added to settings.');
+
+    // Disable the test theme and verify the breakpoint group still exists.
+    theme_disable(array('breakpoint_test_theme'));
+    $this->assertTrue(entity_load('breakpoint_group', 'module_test'), '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', 'module_test'), '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', 'module_test'), '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..450709a
--- /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: ['3x', '4x']
+    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..b3ca39f
--- /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 = FALSE
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..f06ac50
--- /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: ['1.5x', '2x']
+    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)'
