Skip to main content

Ask users for Email addresses

How to ask a user for an email address.

<div class="ons-field">
  <label class="ons-label  ons-label--with-description " for="email">Email address
  </label>
  <span id="description-hint" class="ons-label__description  ons-input--with-description">
    This will not be stored and only used once to send your confirmation
  </span>
  <input type="email" id="email" class="ons-input ons-input--text ons-input-type__input" autocomplete="email" aria-describedby="description-hint" />
</div>
{% from "components/input/_macro.njk" import onsInput %}
{{
    onsInput({
        "id": "email",
        "type": "email",
        "autocomplete": "email",
        "label": {
            "text": "Email address",
            "description": "This will not be stored and only used once to send your confirmation"
        }
    })
}}
Name Type Required Description
id string true The id of the input. This will also be added to the label if a label is specified
type string false The type of the input, for example, number, email, tel. Will default to text
classes string false Classes to add to the input.
width string false Required width of the input. Will also accept the bp-suffix e.g. '7@m'
name string false The name of the input
value string | number false The value to set the input to
min number false Minimum accepted number or date
max number false Maximum accepted number or date
minLength number false Minimum accepted length of input value
maxLength number false Maximum accepted length of input value
attributes object false HTML attributes (for example, data attributes) to add to the input
label Label (ref) false Settings for the input label. for will automatically be set to match the input id
prefix InputPrefix false Settings to prefix the input with
suffix InputSuffix false Settings to suffix the input with
fieldId string false Id for the field
fieldClasses string false Classes for the field
dontWrap boolean false Prevents the input from being wrapped in a field component
mutuallyExclusive MutuallyExclusive (ref) false Configuration object if this is a mutually exclusive input
CharCheckLimit CharCheckLimit false Configuration object if this input has a character count
legend string Only if mutuallyExclusive is set Text content for the legend
legendClasses string false Classes for the legend
error Error (ref) false Configuration for validation errors
autocomplete string true Autocomplete attribute used to override the browsers native autocomplete
accessiblePlaceholder boolean false Will add the provided label as an accessible placeholder
searchButton Button (ref) false Settings for the button used for a search pattern.
postTextboxLinkText string false The text for the link to follow the textbox
postTextboxLinkUrl string false The url for the link to follow the textbox
listeners object false Creates a script element that adds an event listener to the element by id. Takes key { event } and value { function }
required boolean false Adds the required attribute to the input to indicate that the user must specify a value for the input before the owning form can be submitted

Prefix/Suffix

Name Type Required Description
text string true The text for the prefix/suffix
title string true The title of the prefix/suffix. For example where text is “cm”, title would be “centimetres”
id string false Id for the prefix/suffix

CharCheckLimit

Name Type Required Description
limit number false The maximum amount of characters a user should type in
charcheckCountdown boolean false Displays the number of remaining characters allowed based on the limit
charCountPlural string false Required if CharCheckLimit is supplied. The string that will render how many characters are remaining. {x} Will be replaced with the number
charCountSingular string false Required if CharCheckLimit is supplied. The string that will render how many characters are remaining (singular). {x} Will be replaced with the number
charCountOverLimitSingular string false Required if CharCheckLimit is supplied. The string that will render how many characters are over (singular). {x} Will be replaced with the number
charCountOverLimitPlural string false Required if CharCheckLimit is supplied. The string that will render how many characters are over (plural). {x} Will be replaced with the number
{% macro onsInput(params) %}
    {% from "components/mutually-exclusive/_macro.njk" import onsMutuallyExclusive %}
    {% from "components/char-check-limit/_macro.njk" import onsCharLimit %}
    {% from "components/search/_macro.njk" import onsSearch %}
    {% from "components/field/_macro.njk" import onsField %}
    {% from "components/label/_macro.njk" import onsLabel %}

    {% if params.type == "number" %}
        {# Type must be "text" or Firefox and Safari will set a blank value to the server if non numeric characters are entered -
        they don’t block non numeric characters: https://bugzilla.mozilla.org/show_bug.cgi?id=1398528 #}
        {% set type = "text" %}
        {% set pattern = "[0-9]*" %}
        {% set inputmode = "numeric" %}
    {% elif params.type is defined and params.type %}
        {% set type = params.type %}
    {% elif params.searchButton is defined and params.searchButton %}
        {% set type = "search" %}
    {% else %}
        {% set type = "text" %}
    {% endif %}

    {% set exclusiveClass = " ons-js-exclusive-group-item" if params.mutuallyExclusive else "" %}
    {% set inputPlaceholder = ' ons-input--placeholder' if params.accessiblePlaceholder else "" %}

    {% set input %}
        <input
            type="{{ type }}"
            id="{{ params.id }}"
            class="ons-input ons-input--text ons-input-type__input{% if params.error is defined and params.error %} ons-input--error{% endif %}{% if params.searchButton is defined and params.searchButton %} ons-search__input{% endif %}{% if params.classes is defined and params.classes %} {{ params.classes }}{% endif %}{% if params.width is defined and params.width %} ons-input{% if params.type is defined and (params.type == 'number' or params.type == 'tel') %}-number{% endif %}--w-{{ params.width }}{% endif %}{{ exclusiveClass }}{{ inputPlaceholder }}"
            {% if params.prefix is defined and params.prefix or params.suffix is defined and params.suffix %}title="{{ params.prefix.title if params.prefix }}{{ params.suffix.title if params.suffix }}"{% endif %}
            {% if params.name is defined and params.name %}name="{{ params.name }}"{% endif %}
            {% if params.value is defined and params.value %}value="{{ params.value }}"{% endif %}
            {% if params.accept is defined and params.accept %}accept="{{ params.accept }}"{% endif %}
            {% if params.min is defined and params.min %}min="{{ params.min }}"{% endif %}
            {% if params.max is defined and params.max %}max="{{ params.max }}"{% endif %}
            {% if params.minLength is defined and params.minLength %}minlength="{{ params.minLength }}"{% endif %}
            {% if params.maxLength is defined and params.maxLength %}maxlength="{{ params.maxLength }}"{% endif %}
            {% if pattern is defined and pattern %}pattern="{{ pattern }}"{% endif %}
            {% if inputmode is defined and inputmode %}inputmode="{{ inputmode }}"{% endif %}
            {% if params.autocomplete is defined and params.autocomplete %}autocomplete="{{ params.autocomplete }}"{% endif %}
            {% if params.accessiblePlaceholder is defined and params.accessiblePlaceholder %}placeholder="{{ params.label.text }}"{% endif %}
            {% if params.charCheckLimit is defined and params.charCheckLimit %}data-char-check-ref="{{ params.id }}-check-remaining" data-char-check-num="{{ params.charCheckLimit.limit }}" aria-describedby="{{ params.id }}-check-remaining"{% endif %}
            {% if params.charCheckLimit is defined and params.charCheckLimit and params.charCheckLimit.charcheckCountdown is defined and params.charCheckLimit.charcheckCountdown %}data-char-check-countdown="true"{% endif %}
            {% if params.attributes is defined and params.attributes %}{% for attribute, value in (params.attributes.items() if params.attributes is mapping and params.attributes.items else params.attributes) %}{{ attribute }}{% if value is defined and value %}="{{ value }}"{% endif %} {% endfor %}{% endif %}
            {% if params.label is defined and params.label and params.label.description is defined and params.label.description %}{% if params.label.id is defined and params.label.id %}aria-describedby="{{ params.label.id }}-description-hint"{% else %}aria-describedby="description-hint"{% endif %}{% endif %}
            {% if params.required is defined and params.required == true %}required="required"{% endif %}
        />
        {% if params.listeners %}
            <script{% if csp_nonce %} nonce="{{ csp_nonce() }}"{% endif %}>
            {% for listener, value in (params.listeners.items() if params.listeners is mapping and params.listeners.items else params.listeners) %}
                document.getElementById("{{ params.id }}").addEventListener('{{ listener }}', function(){ {{ value }} });
            {% endfor %}
            </script>
        {% endif %}
        {% if params.postTextboxLinkText is defined and params.postTextboxLinkText %}
            <a class="ons-u-fs-s" href="{{ params.postTextboxLinkUrl }}">{{ params.postTextboxLinkText }}</a>
        {% endif %}
    {% endset %}
    {% set field %}
        {% if params.label is defined and params.label and params.label.text is defined and params.label.text %}
            {{ onsLabel({
                "for": params.id,
                "id": params.label.id,
                "text": params.label.text,
                "classes": params.label.classes,
                "description": params.label.description,
                "attributes": params.label.attributes,
                "accessiblePlaceholder": params.accessiblePlaceholder
            }) }}
        {% endif %}

        {% if params.prefix is defined and params.prefix or params.suffix is defined and params.suffix %}
            {% if params.prefix is defined and params.prefix %}
                {% set prefixClass = " ons-input-type--prefix" %}
            {% endif %}

            <span class="ons-input-type{{ prefixClass }}">
                <span class="ons-input-type__inner">
                    {{ input | safe }}
                    {% set abbr = params.prefix or params.suffix %}
                    <abbr
                        class="ons-input-type__type ons-js-input-abbr"
                        aria-hidden="true"
                        title="{{ abbr.title }}"
                        {% if abbr.id is defined and abbr.id %} id="{{ abbr.id }}"{% endif %}
                        >{{ abbr.text or abbr.title }}</abbr>
                </span>
            </span>
        {% elif params.searchButton is defined and params.searchButton %}
            <span class="ons-grid--flex ons-search">
                {% call onsSearch({
                    "accessiblePlaceholder": params.accessiblePlaceholder,
                    "searchButton": {
                        "type": params.searchButton.type,
                        "text": params.searchButton.text,
                        "id": params.searchButton.id,
                        "attributes": params.searchButton.attributes,
                        "classes": params.searchButton.classes,
                        "iconType": params.searchButton.iconType
                    }
                }) %}
                    {{ input | safe }}
                {% endcall %}
            </span>
        {% else %}
            {{ input | safe }}
        {% endif %}
    {% endset %}

    {% if params.charCheckLimit is defined and params.charCheckLimit and params.charCheckLimit.limit is defined and params.charCheckLimit.limit %}
        {% set charCheckField %}
            {% call onsCharLimit({
                "id": params.id ~ "-check",
                "limit": params.charCheckLimit.limit,
                "type": "check",
                "charCountSingular": params.charCheckLimit.charCountSingular,
                "charCountPlural": params.charCheckLimit.charCountPlural,
                "charCountOverLimitSingular": params.charCheckLimit.charCountOverLimitSingular,
                "charCountOverLimitPlural": params.charCheckLimit.charCountOverLimitPlural
            }) %}
                {{ field | safe }}
            {% endcall %}
        {% endset %}
    {% endif %}

    {% if params.mutuallyExclusive is defined and params.mutuallyExclusive %}
        {% call onsMutuallyExclusive({
            "id": params.fieldId,
            "legend": params.legend,
            "legendClasses": params.legendClasses ~ "ons-js-input-legend",
            "description": params.description,
            "dontWrap": params.dontWrap,
            "legendIsQuestionTitle": params.legendIsQuestionTitle,
            "checkbox": params.mutuallyExclusive.checkbox,
            "or": params.mutuallyExclusive.or,
            "deselectMessage": params.mutuallyExclusive.deselectMessage,
            "deselectGroupAdjective": params.mutuallyExclusive.deselectGroupAdjective,
            "deselectCheckboxAdjective": params.mutuallyExclusive.deselectCheckboxAdjective,
            "error": params.error,
            "autosuggestResults": params.autosuggestResults
        }) %}
            {% if charCheckField is defined and charCheckField %}
                {{ charCheckField | safe }}
            {% else %}
                {{ field | safe }}
            {% endif %}
        {% endcall %}
    {% elif type == "hidden" %}
        {{ field | safe }}
    {% else %}
        {% call onsField({
            "id": params.fieldId,
            "classes": params.fieldClasses,
            "dontWrap": params.dontWrap,
            "legendIsQuestionTitle": params.legendIsQuestionTitle,
            "error": params.error
        }) %}
            {% if charCheckField is defined and charCheckField %}
                {{ charCheckField | safe }}
            {% else %}
                {{ field | safe }}
            {% endif %}
        {% endcall %}
    {% endif %}
{% endmacro %}
.ons-input-type {
  display: block;

  // Keep the entire component display block, but use inline-flex on inner to prevent the orange focus from going full width
  &__inner {
    display: inline-flex;
    position: relative;
  }

  // Double ampersand is needed to solve specificity issues
  & &__input {
    flex: 1 1 auto;
    position: relative;
    z-index: 1;

    &:focus {
      box-shadow: inset 0 0 0 1px $color-input;
    }
  }

  &__type[title] {
    background-color: $color-button-secondary;
    border: 1px solid $color-input;
    display: block;
    flex: 0 0 auto;
    font-size: 1rem;
    font-weight: $font-weight-bold;
    line-height: normal;
    padding: $input-padding-vertical $input-padding-horizontal * 2;
    text-align: center;
    text-decoration: none;
    white-space: nowrap;
  }

  &__input:focus + &__type[title]::after {
    border-radius: $input-radius;
    bottom: 0;
    box-shadow: 0 0 0 3px $color-focus;
    content: '';
    display: block;
    left: 0;
    position: absolute;
    right: 0;
    top: 0;
  }

  &:not(&--prefix) & {
    &__type[title] {
      border-left: 0;
      border-radius: 0 $input-radius $input-radius 0;
    }

    &__input {
      border-radius: $input-radius 0 0 $input-radius;
    }
  }

  &--prefix & {
    &__type[title] {
      border-radius: $input-radius 0 0 $input-radius;
      border-right: 0;
      order: 0;
    }

    &__input {
      border-radius: 0 $input-radius $input-radius 0;
      order: 1;
    }
  }
}

.ons-input {
  border: $input-border-width solid $color-input;
  border-radius: $input-radius;
  color: inherit;
  display: block;
  font-family: inherit;
  font-size: 1rem;
  line-height: 1rem;
  padding: $input-padding-vertical $input-padding-horizontal;
  position: relative;
  width: 100%;
  z-index: 3;

  &::-ms-clear {
    display: none;
  }

  @include mq(s) {
    &--text,
    &--select {
      &:not(.ons-input--block):not([class*='input--w-']) {
        width: $input-width;
      }
    }
  }

  &--text,
  &--textarea {
    // Prevent inner shadow on iOS
    appearance: none;
  }

  &:focus {
    box-shadow: 0 0 0 3px $color-focus, inset 0 0 0 1px $color-input;
    outline: none;
  }

  &:disabled {
    border-color: $color-grey-75;
    cursor: not-allowed;
  }

  &--error:not(:focus) {
    border: 1px solid $color-ruby-red;
    box-shadow: inset 0 0 0 1px $color-ruby-red;
  }

  &--with-description {
    margin-bottom: 0.55rem;
  }
}

// Text input widths
@include input-width('ons-input--w-{x}');

// Number input widths
@include input-width('ons-input-number--w-{x}', 0.54rem);

.ons-input--postcode {
  max-width: input-width-calc($chars: 5, $num-chars: 2, $spaces: 1);
  width: 100%;
}

.ons-input__helper {
  font-size: 0.8rem;
  font-weight: $font-weight-bold;
  margin-top: 0.2rem;
}

.ons-input--select {
  appearance: none;
  background: $color-white url('#{$static}/img/icons--chevron-down.svg') no-repeat center right 10px;
  background-size: 1rem;
  line-height: 1.3rem;
  padding: 0.39rem 2rem 0.39rem $input-padding-horizontal;

  &::-ms-expand {
    display: none;
  }
}

.ons-input--textarea {
  line-height: normal;
  resize: vertical;
  width: 100%;
}

.ons-input--block {
  display: block;
  width: 100%;
}

.ons-input--placeholder {
  background: transparent;
  &::placeholder {
    color: transparent;
  }
  &:valid:not(:placeholder-shown) {
    background-color: $color-white;
  }
  &:focus {
    background-color: $color-white;
  }
}

.ons-input--limit-reached:not(:focus) {
  border: $input-border-width solid $color-ruby-red;
}

.ons-input__limit {
  display: block;

  &--reached {
    color: $color-ruby-red;
  }
}

.ons-input--ghost {
  border: 2px solid rgba(255, 255, 255, 0.6);
  &:focus {
    border: 2px solid $color-input;
  }
}

.ons-input-search {
  @extend .ons-input--block;

  &--icon {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='%23ffffff'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath d='M11.86 10.23 8.62 6.99a4.63 4.63 0 1 0-6.34 1.64 4.55 4.55 0 0 0 2.36.64 4.65 4.65 0 0 0 2.33-.65l3.24 3.23a.46.46 0 0 0 .65 0l1-1a.48.48 0 0 0 0-.62Zm-5-3.32a3.28 3.28 0 0 1-2.31.93 3.22 3.22 0 1 1 2.35-.93Z'/%3E%3C/svg%3E");
    background-position: 12px 10px;
    background-repeat: no-repeat;
    background-size: 18px 18px;
    padding-left: 2.4rem;

    &:focus,
    &:active,
    &:valid:not(:placeholder-shown) {
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='%23000000'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath d='M11.86 10.23 8.62 6.99a4.63 4.63 0 1 0-6.34 1.64 4.55 4.55 0 0 0 2.36.64 4.65 4.65 0 0 0 2.33-.65l3.24 3.23a.46.46 0 0 0 .65 0l1-1a.48.48 0 0 0 0-.62Zm-5-3.32a3.28 3.28 0 0 1-2.31.93 3.22 3.22 0 1 1 2.35-.93Z'/%3E%3C/svg%3E");
    }
    &:focus,
    &:active {
      background-position: 12px 10px;
      box-shadow: 0 0 0 3px $color-focus;
    }
  }
}

When to use this pattern

Use this pattern when you need to collect an email address from the user.

How to use this pattern

When using this pattern you should:

  • tell users why you need an email address and what it will be used for
  • make sure the field can accept all email addresses
  • help users enter an email address in the correct format

Explain why you need the email address

Use the ons-label__description with the input component to reassure users why you need an email address and what it will be used for.

Allow all email addresses

The email address field needs to accommodate the maximum number of characters allowed. An email address can be up to 320 characters long and include a set of special characters as detailed in the RFC 3696 specification from the Internet Engineering Task Force 

How to check email addresses

To help users enter a valid email address, you should:

  • allow them to paste the email address
  • check they have entered something in the email address field
  • check that what they have entered is valid
  • show an error message if they have not entered anything or what they have entered is not valid
  • ask them to confirm it is correct using the check answers pattern

Error messages

Use the correct errors pattern and show the error details above the email address field.

<div class="ons-panel ons-panel--error ons-panel--no-title ons-u-mb-s" id="email-error">
  <span class="ons-u-vh">Error: </span>
  <div class="ons-panel__body">
    <p class="ons-panel__error">
      <strong>Enter an email address in a valid format, for example, name@example.com</strong>
    </p>
    <div class="ons-field">
      <label class="ons-label  ons-label--with-description " for="email">Email address
      </label>
      <span id="description-hint" class="ons-label__description  ons-input--with-description">
        This will not be stored and only used once to send your confirmation
      </span>
      <input type="email" id="email" class="ons-input ons-input--text ons-input-type__input ons-input--error" value="name.com" autocomplete="email" aria-describedby="description-hint" required="required" />
    </div>
  </div>
</div>
{% from "components/input/_macro.njk" import onsInput %}
{{
    onsInput({
        "id": "email",
        "type": "email",
        "autocomplete": "email",
        "value": "name.com",
        "label": {
            "text": "Email address",
            "description": "This will not be stored and only used once to send your confirmation"
        },
        "required": true,
        "error": {
            "id": "email-error",
            "text": "Enter an email address in a valid format, for example, name@example.com",
            "dsExample": isPatternLib
        }
    })
}}
If the email address field is empty

Use “Enter an email address”.

If the email address entered is not in a valid format

Use “Enter an email address in a valid format, for example, name@example.com”.

Help improve this pattern

Let us know how we could improve this pattern or share your user research findings. Discuss the ‘Email addresses’ pattern on GitHub