Form

Forms can be composed of <input>, <select>, <textarea> elements, and other specialized input elements, such as checkboxes, and radio buttons. The first example shows all the form-controls available in one single form.

<form action="..." method="..." class="w-400 mw-full"> <!-- w-400 = width: 40rem (400px), mw-full = max-width: 100% -->
  <!-- Input -->
  <div class="form-group">
    <label for="full-name" class="required">Full name</label>
    <input type="text" class="form-control" id="full-name" placeholder="Full name" required="required">
  </div>

  <!-- Radio -->
  <div class="form-group">
    <label for="gender-male" class="required">Gender</label>
    <div class="custom-radio">
      <input type="radio" name="gender" id="gender-male" value="male" required="required">
      <label for="gender-male">Male</label>
    </div>
    <div class="custom-radio">
      <input type="radio" name="gender" id="gender-female" value="female" required="required">
      <label for="gender-female">Female</label>
    </div>
    <div class="custom-radio">
      <input type="radio" name="gender" id="gender-other" value="other" required="required">
      <label for="gender-other">Other</label>
    </div>
  </div>

  <!-- Select -->
  <div class="form-group">
    <label for="area-of-specialization" class="required">Area of specialization</label>
    <select class="form-control" id="area-of-specialization" required="required">
      <option value="" selected="selected" disabled="disabled">Select your area of specialization</option>
      <option value="front-end">Front-end</option>
      <option value="back-end">Back-end</option>
      <option value="full-stack">Full-stack</option>
    </select>
  </div>

  <!-- Multi-select -->
  <div class="form-group">
    <label for="languages" class="required">Languages</label>
    <select class="form-control" id="languages" multiple="multiple" required="required" size="5">
      <option value="javascript">JavaScript</option>
      <option value="python">Python</option>
      <option value="php">PHP</option>
      ...
    </select>
  </div>

  <!-- Textarea -->
  <div class="form-group">
    <label for="description">Description</label>
    <textarea class="form-control" id="description" placeholder="Write a short description about yourself."></textarea>
  </div>

  <!-- File input -->
  <div class="form-group">
    <label for="picture" class="required">Display picture</label>
    <div class="custom-file">
      <input type="file" id="picture" required="required">
      <label for="picture">Choose picture</label>
    </div>
  </div>

  <!-- Switch -->
  <div class="form-group">
    <div class="custom-switch">
      <input type="checkbox" id="remember-my-information">
      <label for="remember-my-information">Remember my information</label>
    </div>
  </div>

  <!-- Checkbox -->
  <div class="form-group">
    <div class="custom-checkbox">
      <input type="checkbox" id="agree-to-terms">
      <label for="agree-to-terms">I agree to all the <a href="#" class="hyperlink">terms and conditions</a></label>
    </div>
  </div>

  <!-- Submit button -->
  <input class="btn btn-primary" type="submit" value="Submit">
</form>

<!-- Required for the custom file input -->
<script src="path/to/halfmoon.js"></script>

A few things to keep in mind

Here's a list of things to keep in mind when creating forms in Halfmoon.

  • The .form-control class styles the <input>, <select>, and <textarea> elements.
  • Putting the form-controls inside containers with the class .form-group provides margin between the elements.
  • Adding the .required class to <label> elements automatically adds a tiny red asterisk to signify the requirement.
  • The custom file input requires the core JavaScript file to work properly, mainly to update the file(s) chosen. You can read more about custom file inputs in the file input section (opens in new tab) of the docs.

Form rows #

Forms can also be composed using the grid system (opens in new tab). The main thing worth noting is that the columns must be placed inside .form-row containers, instead of .row. Please also note that columns do not have any margin/padding in Halfmoon, therefore, the form rows should always be given the classes .row-eq-spacing/.row-eq-spacing-{breakpoint}, especially when using inputs, select boxes, and textareas. You can learn more about equal in-between spacing in this section of the grid system (opens in new tab).

<form action="..." method="...">
  <!-- First row -->
  <div class="form-row row-eq-spacing-sm">
    <div class="col-sm">
      <label for="first-name" class="required">First name</label>
      <input type="text" class="form-control" id="first-name" placeholder="First name" required="required">
    </div>
    <div class="col-sm">
      <label for="last-name">Last name</label>
      <input type="text" class="form-control" id="last-name" placeholder="Last name">
    </div>
  </div>

  <!-- Second row container -->
  <div>
    <!-- Label -->
    <label for="day-of-birth" class="required">Date of birth</label>
    <!-- Second row -->
    <div class="form-row row-eq-spacing">
      <div class="col">
        <input type="text" class="form-control" id="day-of-birth" placeholder="Day" pattern="[0-9]*" inputmode="numeric" required="required">
      </div>
      <div class="col">
        <input type="text" class="form-control" id="month-of-birth" placeholder="Month" pattern="[0-9]*" inputmode="numeric" required="required">
      </div>
      <div class="col">
        <input type="text" class="form-control" id="year-of-birth" placeholder="Year" pattern="[0-9]*" inputmode="numeric" required="required">
      </div>
    </div>
  </div>

  <!-- Third row container -->
  <div>
    <label for="state" class="required">School</label>
    <!-- Third row -->
    <div class="form-row row-eq-spacing-md">
      <div class="col-md-3">
        <select class="form-control" id="state" required="required">
          <option value="" selected="selected" disabled="disabled">State</option>
          <option value="NY">NY</option>
          <option value="NJ">NJ</option>
          <option value="PA">PA</option>
        </select>
      </div>
      <div class="col-md-9">
        <input type="text" class="form-control" id="school-name" placeholder="Name of school" required="required">
      </div>
    </div>
  </div>

  <!-- Submit button container -->
  <div class="text-right"> <!-- text-right = text-align: right -->
    <input class="btn btn-primary" type="submit" value="Submit">
  </div>
</form>

Everything else from the grid system can be used with form rows, including the flex utilities (opens in new tab) for alignment, offsets, ordering, etc. You may also choose to use inline forms, which are described in one of the sections below.

On a side note, the inputs used for the date of birth is using the attributes type="text", pattern="[0-9]*", and inputmode="numeric", instead of the regular type="number". The rationale behind this choice has been illustrated nicely here by the GOV.UK Design System. However, at the end of the day, it is upto you which number input format you choose to use in your project.

Form text #

Helper text can be displayed to users using the .form-text class. This is especially useful for displaying hints to users regarding the nature of the expected input.

Only alphanumeric characters and underscores allowed.
Must be at least 8 characters long, and contain at least one special character.
Must match the above password exactly.
<form action="..." method="..." class="w-400 mw-full"> <!-- w-400 = width: 40rem (400px), mw-full = max-width: 100% -->
  <div class="form-group">
    <label for="username" class="required">Username</label>
    <input type="text" class="form-control" id="username" placeholder="Username" required="required">
    <div class="form-text">
      Only alphanumeric characters and underscores allowed.
    </div>
  </div>
  <div class="form-group">
    <label for="password" class="required">Password</label>
    <input type="password" class="form-control" id="password" placeholder="Password" required="required">
    <div class="form-text">
      Must be at least 8 characters long, and contain at least one special character.
    </div>
  </div>
  <div class="form-group">
    <label for="confirm-password" class="required">Confirm password</label>
    <input type="password" class="form-control" id="confirm-password" placeholder="Confirm password" required="required">
    <div class="form-text">
      Must match the above password exactly.
    </div>
  </div>
  <input class="btn btn-primary btn-block" type="submit" value="Sign up">
</form>

Invalid input and feedback #

Invalid feedback (or errors) can be displayed to users using the .invalid-feedback class. Moreover, adding the .is-invalid class to a .form-group element will style the <input>, <select>, or <textarea> inside to show users that the input is invalid.

  • Username exceeds the maximum length of 20 characters.
  • Username can not contain dashes.
Only alphanumeric characters and underscores allowed.
Must be at least 8 characters long, and contain at least one special character.
Does not match with the password above.
Must match the above password exactly.
<form action="..." method="..." class="w-400 mw-full"> <!-- w-400 = width: 40rem (400px), mw-full = max-width: 100% -->
  <div class="form-group is-invalid">
    <label for="username" class="required">Username</label>
    <div class="invalid-feedback">
      <ul>
        <li>Username exceeds the maximum length of 20 characters.</li>
        <li>Username can not contain dashes.</li>
      </ul>
    </div>
    <input type="text" class="form-control" id="username" placeholder="Username" required="required" value="my-really-long-username">
    <div class="form-text">
      Only alphanumeric characters and underscores allowed.
    </div>
  </div>
  <div class="form-group">
    <label for="password" class="required">Password</label>
    <input type="password" class="form-control" id="password" placeholder="Password" required="required" value="my-password">
    <div class="form-text">
      Must be at least 8 characters long, and contain at least one special character.
    </div>
  </div>
  <div class="form-group is-invalid">
    <label for="confirm-password" class="required">Confirm password</label>
    <div class="invalid-feedback">
      Does not match with the password above.
    </div>
    <input type="password" class="form-control" id="confirm-password" placeholder="Confirm password" required="required" value="mmyy-ppasswordd">
    <div class="form-text">
      Must match the above password exactly.
    </div>
  </div>
  <input class="btn btn-primary btn-block" type="submit" value="Sign up">
</form>

It should be noted that the .is-invalid class can also be added directly to the <input>, <select>, or <textarea> elements (along with the class .form-control) to style them like the ones in the above example. You can find examples for this on their individual docs sections.

Inline forms #

Inline forms can be created using the class .form-inline/.form-inline-{breakpoint}, where the breakpoint can be sm, md, lg, or xl. If a breakpoint is provided, the form will be inline for that screen size and up. For example, .form-inline-sm will make the form inline only for small screens and up, ie, width > 576px. Below this width, the form will "collapse" and be rendered like a regular form. The following things should be kept in mind when using inline forms:

  • Labels, form text, invalid feedback, input groups, and buttons (including input buttons) can all be used inside inline forms alongside the regular form-controls.
  • Custom checkboxes, radio buttons, switches, and file inputs can also be used. However, they need to placed inside a container with the .custom-control class.


Income
$

A-Z, 0-9 and - allowed.

A-Z, 0-9 and - allowed.
<div class="w-400 mw-full"> <!-- w-400 = width: 40rem (400px), mw-full = max-width: 100% -->
  <!-- Inline form with label, input, and button (input submit) -->
  <form action="..." method="..." class="form-inline">
    <label class="required" for="if-0-full-name">Name</label>
    <input type="text" class="form-control" placeholder="Full name" id="if-0-full-name" required="required">
    <input type="submit" class="btn btn-primary" value="Submit">
  </form>

  <!-- Inline form (sm) for sign in -->
  <form action="..." method="..." class="form-inline-sm">
    <input type="text" class="form-control" placeholder="Username" id="if-1-username" required="required">
    <input type="password" class="form-control" placeholder="Password" id="if-1-password" required="required">
    <input type="submit" class="btn btn-primary" value="Sign in">
  </form>
  
  <!-- Inline form with input group and button -->
  <form action="..." method="..." class="form-inline">
    <div class="input-group">
      <div class="input-group-prepend">
        <span class="input-group-text">Income</span>
      </div>
      <input type="text" class="form-control" value="0.00" id="if-2-income">
      <div class="input-group-append">
        <span class="input-group-text">$</span>
      </div>
    </div>
    <button class="btn btn-primary">Enter</button>
  </form>
  
  <!-- Inline form with form text and input -->
  <form action="..." method="..." class="form-inline">
    <input type="text" class="form-control" placeholder="Username" id="if-3-username">
    <div class="form-text">
      A-Z, 0-9 and - allowed.
    </div>
  </form>
  
  <!-- Inline form with invalid feedback and invalid input -->
  <form action="..." method="..." class="form-inline">
    <input type="text" class="form-control is-invalid" value="my_username" id="if-4-username">
    <div class="invalid-feedback">
      A-Z, 0-9 and - allowed.
    </div>
  </form>
</div>

As mentioned above, custom checkboxes, radio buttons, switches, and file inputs can also be used with inline forms using a container with the .custom-control class.




<div class="w-400 mw-full"> <!-- w-400 = width: 40rem (400px), mw-full = max-width: 100% -->
  <!-- Inline form with checkbox and input -->
  <form action="..." method="..." class="form-inline">
    <div class="custom-control">
      <div class="custom-checkbox">
        <input type="checkbox" id="if-5-checkbox" value="">
        <label for="if-5-checkbox">Checkbox</label>
      </div>
    </div>
    <input type="text" class="form-control" placeholder="Input" id="if-5-input">
  </form>
  
  <!-- Inline form with radio button and select box -->
  <form action="..." method="..." class="form-inline">
    <div class="custom-control">
      <div class="custom-radio">
        <input type="radio" id="if-6-radio" value="">
        <label for="if-6-radio">Radio</label>
      </div>
    </div>
    <select class="form-control" id="if-6-select">
      <option value="">Select</option>
      <option value="">Option 1</option>
      <option value="">Option 2</option>
    </select>
  </form>
  
  <!-- Inline form with switch and input -->
  <form action="..." method="..." class="form-inline">
    <div class="custom-control">
      <div class="custom-switch">
        <input type="checkbox" id="if-7-switch" value="">
        <label for="if-7-switch">Switch</label>
      </div>
    </div>
    <input type="text" class="form-control" placeholder="Input" id="if-7-input">
  </form>
  
  <!-- Inline form (sm) with file input, textarea, and button (input submit) -->
  <form action="..." method="..." class="form-inline-sm">
    <div class="custom-control">
      <div class="custom-file">
        <input type="file" id="if-8-file-input">
        <label for="if-8-file-input">Choose photo</label>
      </div>
    </div>
    <textarea class="form-control" placeholder="Write a short description about the picture" id="if-8-textarea"></textarea>
    <input type="submit" class="btn btn-primary" value="Upload">
  </form>
</div>

Please note that forms do not have any margins by default, so the forms in the above examples are separated by <br /> tags. This is not shown in the code for the sake of conciseness.

Inline forms with form-groups #

Practical use cases of inline forms often requires the use of form-groups (.form-group). For form-groups inside inline forms, whatever is placed inside the form-group will be inline, but each of the separate form-group will be block level. This can be used to create different types of forms with varying layouts. Given below is a simple example:

<!-- Inline form with form-groups -->
<form action="..." method="..." class="form-inline w-400 mw-full"> <!-- w-400 = width: 40rem (400px), mw-full = max-width: 100% -->
  <div class="form-group">
    <label class="required w-100" for="username">Username</label> <!-- w-100 = width: 10rem (100px) -->
    <input type="text" class="form-control" placeholder="Username" id="username" required="required">
  </div>
  <div class="form-group">
    <label class="required w-100" for="password">Password</label> <!-- w-100 = width: 10rem (100px) -->
    <input type="password" class="form-control" placeholder="Password" id="password" required="required">
  </div>
  <div class="form-group mb-0"> <!-- mb-0 = margin-bottom: 0 -->
    <div class="custom-control">
      <div class="custom-checkbox">
        <input type="checkbox" id="remember-me" value="">
        <label for="remember-me">Remember me</label>
      </div>
    </div>
    <input type="submit" class="btn btn-primary ml-auto" value="Sign in"> <!-- ml-auto = margin-left: auto -->
  </div>
</form>

Inline forms make use of flexbox, so we can use flex utilities (opens in new tab) for positioning content. For instance, the .ml-auto class pushes the button to the right side by setting the property margin-left: auto on it. Both the labels are also given a width of 10rem (100px), which means that the input boxes are aligned perfectly.

More examples #

More examples of the individual form control elements, along with different options, such as sizing and disabling, can be found in their separate docs sections: input, select, textarea, checkbox, radio, switch, and file input.


Up next: Input