diff --git a/modules/product/src/Plugin/Field/FieldWidget/ProductVariationAttributesWidget.php b/modules/product/src/Plugin/Field/FieldWidget/ProductVariationAttributesWidget.php
index b4b092c..0f1f4c2 100644
--- a/modules/product/src/Plugin/Field/FieldWidget/ProductVariationAttributesWidget.php
+++ b/modules/product/src/Plugin/Field/FieldWidget/ProductVariationAttributesWidget.php
@@ -120,16 +120,22 @@ class ProductVariationAttributesWidget extends ProductVariationWidgetBase implem
       '#prefix' => '<div id="' . $wrapper_id . '">',
       '#suffix' => '</div>',
     ];
-    if ($form_state->getTriggeringElement()) {
+    $creator = \Drupal::service('commerce_product.variation_bulk_creator');
+    $all = $creator->getUsedAttributesCombinations($variations)['combinations'];
+    $variation = reset($variations);
+    if ($trigger = $form_state->getTriggeringElement()) {
       $parents = array_merge($element['#field_parents'], [$items->getName(), $delta]);
       $user_input = (array) NestedArray::getValue($form_state->getUserInput(), $parents);
+      $user_input['trigger_value'] = $trigger['#value'];
+      $user_input['all'] = $all;
       $selected_variation = $this->selectVariationFromUserInput($variations, $user_input);
+      unset($user_input);
     }
     else {
       $selected_variation = $this->variationStorage->loadFromContext($product);
       // The returned variation must also be enabled.
       if (!in_array($selected_variation, $variations)) {
-        $selected_variation = reset($variations);
+        $selected_variation = $variation;
       }
     }
 
@@ -146,14 +152,23 @@ class ProductVariationAttributesWidget extends ProductVariationWidgetBase implem
         'class' => ['attribute-widgets'],
       ],
     ];
-    foreach ($this->getAttributeInfo($selected_variation, $variations) as $field_name => $attribute) {
+
+    if ($all) {
+      $attribute_info = $this->getAttributeInfo($selected_variation, ['all' => $all, 'options' => $creator->getAttributeFieldOptionIds($variation)['options']]);
+    }
+    else {
+      $attribute_info = $this->getAttributeInfo($selected_variation, $variations);
+    }
+    unset($variations, $all, $selected_variation, $variation, $creator);
+
+    foreach ($attribute_info as $field_name => $attribute) {
       $element['attributes'][$field_name] = [
         '#id' => Html::getUniqueId('edit-purchased-entity-0-attributes-' . $field_name . '-' . $id),
         '#type' => $attribute['element_type'],
         '#title' => $attribute['title'],
         '#options' => $attribute['values'],
         '#required' => $attribute['required'],
-        '#default_value' => $selected_variation->getAttributeValueId($field_name),
+        '#default_value' => $attribute['default_value'],
         '#ajax' => [
           'callback' => [get_class($this), 'ajaxRefresh'],
           'wrapper' => $form['#wrapper_id'],
@@ -194,26 +209,60 @@ class ProductVariationAttributesWidget extends ProductVariationWidgetBase implem
    * @param \Drupal\commerce_product\Entity\ProductVariationInterface[] $variations
    *   An array of product variations.
    * @param array $user_input
-   *   The user input.
+   *   The user input and all the variation type attributes combinations.
    *
    * @return \Drupal\commerce_product\Entity\ProductVariationInterface
    *   The selected variation.
    */
   protected function selectVariationFromUserInput(array $variations, array $user_input) {
-    $selected_variation = NULL;
     if (!empty($user_input)) {
+      $user_input['valid'] = $user_input['attributes'];
+      if ($user_input['all']) {
+        $user_input['valid'] = [];
+        // The $creator returns '_none' for combinations with optional fields,
+        // but $user_input contains '0' for those fields, so change '0' to
+        // '_none' for filtering out unrelevant combinations properly.
+        $none_id = '0';
+        $trigger_name = array_search($user_input['trigger_value'], $user_input['attributes']);
+        $trigger_value = $user_input['trigger_value'] == $none_id ? '_none' : $user_input['trigger_value'];
+        foreach ($user_input['all'] as $index => $combination) {
+          if ($combination[$trigger_name] != $trigger_value) {
+            unset($user_input['all'][$index]);
+          }
+          else {
+            foreach ($user_input['attributes'] as $key => $value) {
+              $value = $value == $none_id ? '_none' : $value;
+              if ($combination[$key] == $value) {
+                $user_input['valid'][$key] = $value;
+              }
+            }
+          }
+        }
+        foreach ($user_input['all'] as $index => $combination) {
+          $merged = array_merge($combination, $user_input['valid']);
+          // The exact attributes combination selected by a user is found.
+          if ($combination == $merged) {
+            $user_input['attributes'] = $combination;
+          }
+        }
+      }
+      unset($user_input['all']);
       $attributes = $user_input['attributes'];
-      $first_value = current($attributes);
-      $first_key = current(array_keys($attributes));
+      $selected_variation = NULL;
       foreach ($variations as $variation) {
         $values = [];
         foreach ($attributes as $field_name => $value) {
-          $values[$field_name] = $variation->getAttributeValueId($field_name);
-          if ($first_key == $field_name && $values[$field_name] === $first_value) {
-            $selected_variation = $selected_variation ?: $variation;
+          $id = $variation->getAttributeValueId($field_name);
+          $values[$field_name] = is_null($id) ? '_none' : $id;
+          $merged = array_merge($user_input['valid'], $values);
+          // Select variation having at least some valid attribute ids.
+          if ($user_input['valid'] == $merged) {
+            $selected_variation = $variation;
           }
         }
-        if ($attributes === $values) {
+        $merged = array_merge($attributes, $values);
+        // The exact selected variation is found.
+        if ($attributes == $merged) {
           $selected_variation = $variation;
           break;
         }
@@ -228,58 +277,107 @@ class ProductVariationAttributesWidget extends ProductVariationWidgetBase implem
    *
    * @param \Drupal\commerce_product\Entity\ProductVariationInterface $selected_variation
    *   The selected product variation.
-   * @param \Drupal\commerce_product\Entity\ProductVariationInterface[] $variations
-   *   The available product variations.
+   * @param \Drupal\commerce_product\Entity\ProductVariationInterface[]|array $variations
+   *   The available product variations or an array of the current variation
+   *   type attributes combinations and options.
    *
    * @return array[]
    *   The attribute information, keyed by field name.
    */
   protected function getAttributeInfo(ProductVariationInterface $selected_variation, array $variations) {
-    $attributes = $values = [];
-    $field_definitions = $this->attributeFieldManager->getFieldDefinitions($selected_variation->bundle());
-    $field_map = $this->attributeFieldManager->getFieldMap($selected_variation->bundle());
-    $field_names = array_column($field_map, 'field_name');
-    $index = 0;
-    foreach ($field_names as $field_name) {
+    $bundle = $selected_variation->bundle();
+    $field_definitions = $this->attributeFieldManager->getFieldDefinitions($bundle);
+    $field_map = $this->attributeFieldManager->getFieldMap($bundle);
+    $field_names = array_unique(array_column($field_map, 'field_name'));
+    $form_display = entity_get_form_display($selected_variation->getEntityTypeId(), $bundle, 'default');
+    if (($last = end($variations)) && $last instanceof ProductVariationInterface) {
+      $creator = \Drupal::service('commerce_product.variation_bulk_creator');
+      $all = $creator->getUsedAttributesCombinations($variations)['combinations'];
+      $options = $creator->getAttributeFieldOptionIds($last)['options'];
+    }
+    else {
+      $all = $variations['all'];
+      $options = $variations['options'];
+    }
+    $attributes = $values = $default_settings = [];
+    // As fields with _none value are not returned so we need to restore them.
+    $selected_ids = array_merge(array_fill_keys($field_names, '_none'), $selected_variation->getAttributeValueIds());
+    $previous_field_name = array_keys($selected_ids)[0];
+    $previous_field_id = reset($selected_ids);
+    $default_settings['skip_option_label'] = $this->t('No, thanks!');
+    $default_settings['no_options_label'] = $this->t('No options available ...');
+    $default_settings['hide_no_options'] = FALSE;
+    // The id of an empty option. If changing the value then change the
+    // condition with the same value in self->selectVariationFromUserInput().
+    $none_id = '0';
+    // Prevent memory exhaustion as $variations array can be quite heavy.
+    unset($variations, $selected_variation, $creator);
+
+    foreach ($field_names as $index => $field_name) {
+      $field_id = $selected_ids[$field_name];
+      $values[$field_name] = [];
       /** @var \Drupal\commerce_product\Entity\ProductAttributeInterface $attribute_type */
       $attribute_type = $this->attributeStorage->load($field_map[$index]['attribute_id']);
-      $field = $field_definitions[$field_name];
+      //$field = $field_definitions[$field_name];
       $attributes[$field_name] = [
         'field_name' => $field_name,
-        'title' => $field->getLabel(),
-        'required' => $field->isRequired(),
+        'title' => $field_definitions[$field_name]->getLabel(),
+        'required' => $field_definitions[$field_name]->isRequired(),
         'element_type' => $attribute_type->getElementType(),
+        'default_value' => $field_id,
       ];
+      unset($field_definitions[$field_name]);
       // The first attribute gets all values. Every next attribute gets only
       // the values from variations matching the previous attribute value.
       // For 'Color' and 'Size' attributes that means getting the colors of all
       // variations, but only the sizes of variations with the selected color.
-      $callback = NULL;
-      if ($index > 0) {
-        $previous_field_name = $field_names[$index - 1];
-        $previous_field_value = $selected_variation->getAttributeValueId($previous_field_name);
-        $callback = function ($variation) use ($previous_field_name, $previous_field_value) {
-          /** @var \Drupal\commerce_product\Entity\ProductVariationInterface $variation */
-          return $variation->getAttributeValueId($previous_field_name) == $previous_field_value;
-        };
-      }
+      foreach ($all as $indeks => $combination) {
+        if ($index && $combination[$previous_field_name] != $previous_field_id) {
+          // Improve perfomance unsetting unrelevant combinations.
+          unset($all[$indeks]);
+          continue;
+        }
+        else {
+          $option = [];
+          // Add dummy empty option to choose nothing on an optional field.
+          if ($combination[$field_name] == '_none') {
+            $settings = $form_display->getRenderer($field_name)->getSettings() + $default_settings;
+            $option_id = $none_id;
+            $label = $settings['skip_option_label'];
+          }
+          else {
+            $option_id = $combination[$field_name];
+            $label = $options[$field_name][$combination[$field_name]];
+          }
+          $option[$option_id] = $label;
 
-      $value = $this->getAttributeValues($variations, $field_name, $callback);
-      if (!isset($values[$field_name])) {
-        $values[$field_name] = $value;
+          // In order to avoid weird results after reordering attribute fields
+          // ensure that selected option is at the top of the list.
+          // @see https://www.drupal.org/node/2707721
+          // @see https://www.drupal.org/files/issues/_add%20to%20cart.png
+          if ($combination[$field_name] == $field_id) {
+            $values[$field_name] = $option + $values[$field_name];
+          }
+          else {
+            $values[$field_name] += $option;
+          }
+        }
+      }
+      $single = count($values[$field_name]) == 1;
+      $no_options = $single && array_keys($values[$field_name])[0] == $none_id;
+      if (empty($values[$field_name]) || ($no_options && $settings['hide_no_options'])) {
+        unset($attributes[$field_name]);
+        continue;
       }
-      elseif (!in_array($values[$field_name], $value)) {
-        $values[$field_name] += $value;
+      if ($no_options) {
+        $values[$field_name][$none_id] = $settings['no_options_label'];
       }
+      $attributes[$field_name]['required'] = $single ?: $attributes[$field_name]['required'];
       $attributes[$field_name]['values'] = $values[$field_name];
-      $index++;
+      $previous_field_id = $field_id;
+      $previous_field_name = $field_name;
     }
 
-    // Filter out attributes with no values.
-    $attributes = array_filter($attributes, function ($attribute) {
-      return !empty($attribute['values']);
-    });
-
     return $attributes;
   }
 
diff --git a/src/Plugin/Field/FieldWidget/CommerceOptionsSelectWidget.php b/src/Plugin/Field/FieldWidget/CommerceOptionsSelectWidget.php
new file mode 100644
index 0000000..f473c34
--- /dev/null
+++ b/src/Plugin/Field/FieldWidget/CommerceOptionsSelectWidget.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Drupal\commerce\Plugin\Field\FieldWidget;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget;
+
+/**
+ * Plugin implementation of the 'commerce_options_select' widget.
+ *
+ * @FieldWidget(
+ *   id = "commerce_options_select",
+ *   label = @Translation("Commerce select list"),
+ *   field_types = {
+ *     "entity_reference",
+ *     "list_integer",
+ *     "list_float",
+ *     "list_string"
+ *   },
+ *   multiple_values = TRUE
+ * )
+ */
+class CommerceOptionsSelectWidget extends OptionsSelectWidget {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultSettings() {
+    return [
+      'skip_option_label' => t('No, thanks!'),
+      'no_options_label' => t('No options available ...'),
+      'hide_no_options' => FALSE,
+    ] + parent::defaultSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsForm(array $form, FormStateInterface $form_state) {
+    $settings = $this->getSettings();
+    $warning = $this->t('Leaving this field empty is not recommended.');
+
+    $element['skip_option_label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Skip option label'),
+      '#default_value' => $settings['skip_option_label'],
+      '#description' => $this->t('Indicates for a user that choosing this option will totally skip an optional field.'),
+      '#placeholder' => $warning,
+    ];
+    $element['hide_no_options'] = [
+      '#type' => 'checkbox',
+      '#title_display' => 'before',
+      '#title' => $this->t('Hide empty field'),
+      '#description' => $this->t('If checked, the element having only one empty option will be hidden. Not recommended. Instead set up an explanatory No options label below.'),
+      '#default_value' => $settings['hide_no_options'],
+    ];
+    $element['no_options_label'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('No options label'),
+      '#default_value' => $settings['no_options_label'],
+      '#description' => $this->t('Indicates for a user that there is no options to choose from on an optional field.'),
+      '#placeholder' => $warning,
+      '#states' => [
+        'visible' => [':input[name*="hide_no_options"]' => ['checked' => FALSE]],
+      ],
+    ];
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function settingsSummary() {
+    $summary = [];
+    $none = $this->t('None');
+    $settings = $this->getSettings();
+    $hidden = $settings['hide_no_options'];
+    $settings['hide_no_options'] = $hidden ? $this->t('Hidden') : $this->t('Not hidden');
+    $settings['no_options_label'] = $hidden ? '' : $settings['no_options_label'];
+    foreach ($settings as $name => $value) {
+      $value = empty($settings[$name]) ? $none : $value;
+      $summary[] = "{$name}: {$value}";
+    }
+
+    return $summary;
+  }
+
+}
