diff --git a/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php b/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php
index e3af421ff4a08233a172e203ff76f191c3144243..22cd0d9ac678c71f7b904d3fe01c551656fe18e5 100644
--- a/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php
+++ b/core/modules/options/src/Plugin/Field/FieldType/ListStringItem.php
@@ -100,6 +100,10 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
         // Workaround for https://drupal.org/i/1300290#comment-12873635.
         \Drupal::service('plugin.manager.element_info')->getInfoProperty('machine_name', '#process', []),
       );
+      // Remove #element_validate from the machine name so that any value can be
+      // used as a key, while keeping the widget's behavior for generating
+      // defaults the same.
+      $element['allowed_values']['table'][$delta]['item']['key']['#element_validate'] = [];
     }
 
     return $element;
@@ -113,6 +117,14 @@ public static function processAllowedValuesKey(array &$element): array {
     array_pop($parents);
     $parents[] = 'label';
     $element['#machine_name']['source'] = $parents;
+
+    // Override the default description which is not applicable to this use of
+    // the machine name element given that it allows users to manually enter
+    // characters usually not allowed in machine names.
+    if (!isset($element['#description'])) {
+      $element['#description'] = '';
+    }
+
     return $element;
   }
 
diff --git a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
index c56f2f37d14b9f5c40319d7e8243f5651c08bfb7..ad83c71e98a4935f185d59cfec65a266bd784db1 100644
--- a/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
+++ b/core/modules/options/tests/src/Functional/OptionsFieldUITest.php
@@ -306,14 +306,15 @@ public function testOptionsAllowedValuesText() {
     $field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
     $this->assertSame($field_storage->getSetting('allowed_values'), ['zero' => 'Zero']);
 
-    // Check that string values with dots can not be used.
+    // Check that string values with special characters can be used.
     $input = [
       'field_storage[subform][settings][allowed_values][table][0][item][key]' => 'zero',
       'field_storage[subform][settings][allowed_values][table][0][item][label]' => 'Zero',
-      'field_storage[subform][settings][allowed_values][table][1][item][key]' => 'example.com',
+      'field_storage[subform][settings][allowed_values][table][1][item][key]' => '.example #example',
       'field_storage[subform][settings][allowed_values][table][1][item][label]' => 'Example',
     ];
-    $this->assertAllowedValuesInput($input, 'The machine-readable name must contain only lowercase letters, numbers, and underscores.', 'String value with dot is not supported.');
+    $array = ['zero' => 'Zero', '.example #example' => 'Example'];
+    $this->assertAllowedValuesInput($input, $array, '');
 
     // Check that the same key can only be used once.
     $input = [
diff --git a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php
index 02c099fcbe50853ce3f5d99d2c88e43ca15e3069..61df5dd91af00df994700b011dcb8ac4c34a4f48 100644
--- a/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php
+++ b/core/modules/options/tests/src/FunctionalJavascript/OptionsFieldUITest.php
@@ -375,6 +375,37 @@ public function providerTestOptionsAllowedValues() {
     return $test_cases;
   }
 
+  /**
+   * Tests `list_string` machine name with special characters.
+   */
+  public function testMachineNameSpecialCharacters() {
+    $this->fieldName = 'field_options_text';
+    $this->createOptionsField('list_string');
+    $this->drupalGet($this->adminPath);
+
+    $label_element_name = "field_storage[subform][settings][allowed_values][table][0][item][label]";
+    $this->getSession()->getPage()->fillField($label_element_name, 'Hello world');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->exposeOptionMachineName(1);
+
+    $key_element_name = "field_storage[subform][settings][allowed_values][table][0][item][key]";
+
+    // Ensure that the machine name was generated correctly.
+    $this->assertSession()->fieldValueEquals($key_element_name, 'hello_world');
+
+    // Ensure that the machine name can be overridden with a value that includes
+    // special characters.
+    $this->getSession()->getPage()->fillField($key_element_name, '.hello #world');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->getSession()->getPage()->pressButton('Save settings');
+    $this->assertSession()->statusMessageContains("Saved {$this->fieldName} configuration.");
+
+    // Ensure that the machine name was saved correctly.
+    $allowed_values = FieldStorageConfig::loadByName('node', $this->fieldName)
+      ->getSetting('allowed_values');
+    $this->assertSame(['.hello #world'], array_keys($allowed_values));
+  }
+
   /**
    * Assert the count of the allowed values rows.
    *