<section class="content">
  <h1 class="content__heading">Send Me a Message</h1>
  <p class="content__lede">Use this handy contact form to get in touch with me.</p>
  <form class="content__form contact-form">
    <div class="testing">
      <p>Does this do anything?</p>
    </div>
    <div class="contact-form__input-group">
      <input class="contact-form__input contact-form__input--radio" id="salutation-mr" name="salutation" type="radio" value="Mr."/>
      <label class="contact-form__label contact-form__label--radio" for="salutation-mr">Mr.</label>
      <input class="contact-form__input contact-form__input--radio" id="salutation-mrs" name="salutation" type="radio" value="Mrs."/>
      <label class="contact-form__label contact-form__label--radio" for="salutation-mrs">Mrs.</label>
      <input class="contact-form__input contact-form__input--radio" id="salutation-ms" name="salutation" type="radio" value="Ms."/>
      <label class="contact-form__label contact-form__label--radio" for="salutation-ms">Ms.</label>
    </div>
    <div class="contact-form__input-group">
      <label class="contact-form__label" for="name">Full Name</label>
      <input class="contact-form__input contact-form__input--text" id="name" name="name" type="text"/>
    </div>
    <div class="contact-form__input-group">
      <label class="contact-form__label" for="email">Email Address</label>
      <input class="contact-form__input contact-form__input--email" id="email" name="email" type="email"/>
    </div>
    <div class="contact-form__input-group">
      <label class="contact-form__label" for="subject">How can I help you?</label>
      <select class="contact-form__input contact-form__input--select" id="subject" name="subject">
        <option>I have a problem.</option>
        <option>I have a general question.</option>
      </select>
    </div>
    <div class="contact-form__input-group">
      <label class="contact-form__label" for="message">Enter a Message</label>
      <textarea class="contact-form__input contact-form__input--textarea" id="message" name="message" rows="6" cols="65"></textarea>
    </div>
    <div class="contact-form__input-group">
      <p class="contact-form__label--checkbox-group">Please send me:</p>
      <input class="contact-form__input contact-form__input--checkbox" id="snacks-pizza" name="snacks" type="checkbox" value="pizza"/>
      <label class="contact-form__label contact-form__label--checkbox" for="snacks-pizza">Pizza</label>
      <input class="contact-form__input contact-form__input--checkbox" id="snacks-cake" name="snacks" type="checkbox" value="cake"/>
      <label class="contact-form__label contact-form__label--checkbox" for="snacks-cake">Cake</label>
    </div>
    <input name="secret" type="hidden" value="1b3a9374-1a8e-434e-90ab-21aa7b9b80e7"/>
    <button class="contact-form__button" type="submit">Send It!</button>
  </form>
</section>
<div class="results">
  <h2 class="results__heading">Form Data</h2>
  <pre class="results__display-wrapper"><code class="results__display"></code></pre>
</div>
@use postcss-nested;
@use postcss-simple-vars;
@use postcss-cssnext;

$color-lightest: #f9fdfe;
$color-gray-light: #cdcfcf;
$color-gray-medium: #686a69;
$color-gray-dark: #414643;
$color-darkest: #2a2f2c;

/* A simple reset. */
*,::before,::after {
  margin: 0;
  box-sizing: border-box;
}
  
/* Heydon Pickering’s lobotomized owl. Details: https://bit.ly/1H7MXUD */
*+* {
  margin-top: 1rem;
}

/* Set up fonts, colors and all that jazz. */
body {
  background: $color-lightest;
  color: $color-gray-medium;
  font-family: 'Open Sans', sans-serif;
  font-size: 18px;
  line-height: 1.75;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings use a different font because they’re hipsters. */
h1,h2 {
  color: $color-darkest;
  font-family: Lato, sans-serif;
  font-weight: 300;
  line-height: 1.125;
}

/* Set up general layout rules for outer containers. */
.content,.results {
  width: 90vw;
  max-width: 550px;
  margin: 8vh auto;
}

.content {
  &__heading {
    font-size: 125%;
  }
  
  &__lede {
    margin-top: 0.5rem;
    font-size: 87.5%;
  }
}

.results {
  &__heading {
    font-size: 110%;
  }
  
  &__display-wrapper {
    margin-top: 1rem;
    padding: 0.5rem 1rem;
    background: $color-lightest;
    border: 1px solid $color-gray-light;
    overflow-x: scroll;
  }
}

.contact-form {
  position: relative;
  display: block;
  margin: 0;
  padding: 1rem 0 2rem;
  border-top: 1px solid $color-gray-light;
  border-bottom: 1px solid $color-gray-light;
  overflow: hidden;
  
  &__input-group {
    margin-top: 0.25rem;
    padding: 0.5rem 1rem;
  }
  
  &__label {
    display: block;
    color: $color-gray-dark;
    font-family: Lato, sans-serif;
    font-size: 75%;
    line-height: 1.125;
    
    &--checkbox-group {
      display: inline-block;
      margin-right: 1rem;
      font-size: 75%;
    }
    
    &--checkbox,&--radio {
      display: inline-block;
      margin-left: 0.25rem;
    }
  }
  
  &__input {
    display: block;
    margin-top: 0;
    padding: 0.5rem 0.75rem;
    border: 1px solid $color-gray-light;
    width: 100%;
    font-family: 'Open Sans', sans-serif;
    font-size: 1rem;
    transition: 150ms border-color linear;
    
    &--checkbox,&--radio {
      display: inline-block;
      width: auto;
      
      &~& {
        margin-left: 1rem;
      }
    }
    
    &:focus,&:active {
      border-color: $color-gray-medium;
      outline: 0;
    }
  }
  
  &__button {
    display: block;
    margin: 0.5rem 1rem 0;
    padding: 0 1rem 0.125rem;
    background-color: $color-gray-medium;
    border: 0;
    color: $color-lightest;
    font-family: lato, sans-serif;
    font-size: 100%;
    letter-spacing: 0.05em;
    line-height: 1.5;
    text-transform: uppercase;
    transition: 150ms all linear;
    
    &:hover,&:active,&:focus {
      background: $color-darkest;
      cursor: pointer;
      outline: 0;
    }
  }
}
View Compiled
/**
 * Checks that an element has a non-empty `name` and `value` property.
 * @param  {Element} element  the element to check
 * @return {Bool}             true if the element is an input, false if not
 */
const isValidElement = element => {
  return element.name && element.value;
};

/**
 * Checks if an element’s value can be saved (e.g. not an unselected checkbox).
 * @param  {Element} element  the element to check
 * @return {Boolean}          true if the value should be added, false if not
 */
const isValidValue = element => {
  return (!['checkbox', 'radio'].includes(element.type) || element.checked);
};

/**
 * Checks if an input is a checkbox, because checkboxes allow multiple values.
 * @param  {Element} element  the element to check
 * @return {Boolean}          true if the element is a checkbox, false if not
 */
const isCheckbox = element => element.type === 'checkbox';

/**
 * Checks if an input is a `select` with the `multiple` attribute.
 * @param  {Element} element  the element to check
 * @return {Boolean}          true if the element is a multiselect, false if not
 */
const isMultiSelect = element => element.options && element.multiple;

/**
 * Retrieves the selected options from a multi-select as an array.
 * @param  {HTMLOptionsCollection} options  the options for the select
 * @return {Array}                          an array of selected option values
 */
const getSelectValues = options => [].reduce.call(options, (values, option) => {
  return option.selected ? values.concat(option.value) : values;
}, []);

/**
 * A more verbose implementation of `formToJSON()` to explain how it works.
 *
 * NOTE: This function is unused, and is only here for the purpose of explaining how
 * reducing form elements works.
 *
 * @param  {HTMLFormControlsCollection} elements  the form elements
 * @return {Object}                               form data as an object literal
 */
const formToJSON_deconstructed = elements => {
  
  // This is the function that is called on each element of the array.
  const reducerFunction = (data, element) => {
    
    // Add the current field to the object.
    data[element.name] = element.value;
    
    // For the demo only: show each step in the reducer’s progress.
    console.log(JSON.stringify(data));

    return data;
  };
  
  // This is used as the initial value of `data` in `reducerFunction()`.
  const reducerInitialValue = {};
  
  // To help visualize what happens, log the inital value, which we know is `{}`.
  console.log('Initial `data` value:', JSON.stringify(reducerInitialValue));
  
  // Now we reduce by `call`-ing `Array.prototype.reduce()` on `elements`.
  const formData = [].reduce.call(elements, reducerFunction, reducerInitialValue);
  
  // The result is then returned for use elsewhere.
  return formData;
};

/**
 * Retrieves input data from a form and returns it as a JSON object.
 * @param  {HTMLFormControlsCollection} elements  the form elements
 * @return {Object}                               form data as an object literal
 */
const formToJSON = elements => [].reduce.call(elements, (data, element) => {

  // Make sure the element has the required properties and should be added.
  if (isValidElement(element) && isValidValue(element)) {

    /*
     * Some fields allow for more than one value, so we need to check if this
     * is one of those fields and, if so, store the values as an array.
     */
    if (isCheckbox(element)) {
      data[element.name] = (data[element.name] || []).concat(element.value);
    } else if (isMultiSelect(element)) {
      data[element.name] = getSelectValues(element);
    } else {
      data[element.name] = element.value;
    }
  }

  return data;
}, {});

/**
 * A handler function to prevent default submission and run our custom script.
 * @param  {Event} event  the submit event triggered by the user
 * @return {void}
 */
const handleFormSubmit = event => {
  
  // Stop the form from submitting since we’re handling that with AJAX.
  event.preventDefault();
  
  // Call our function to get the form data.
  const data = formToJSON(form.elements);

  // Demo only: print the form data onscreen as a formatted JSON object.
  const dataContainer = document.getElementsByClassName('results__display')[0];
  
  // Use `JSON.stringify()` to make the output valid, human-readable JSON.
  dataContainer.textContent = JSON.stringify(data, null, "  ");
  
  // ...this is where we’d actually do something with the form data...
};

/*
 * This is where things actually get started. We find the form element using
 * its class name, then attach the `handleFormSubmit()` function to the 
 * `submit` event.
 */
const form = document.getElementsByClassName('contact-form')[0];
form.addEventListener('submit', handleFormSubmit);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.13.0/polyfill.min.js