Skip to main content

Ask users for Dates

Help users enter a known date.

<fieldset id="date" class="ons-fieldset">
  <legend class="ons-fieldset__legend">
    Date of birth
    <span class="ons-fieldset__description ">For example, 31 3 1980</span>
  </legend>
  <div class="ons-field-group">
    <div class="ons-field">
      <label class="ons-label  " for="date-day">Day
      </label>
      <input type="text" id="date-day" class="ons-input ons-input--text ons-input-type__input ons-input-number--w-2" name="day" min="1" max="31" maxlength="2" pattern="[0-9]*" inputmode="numeric" autocomplete="bday-day" />
    </div>
    <div class="ons-field">
      <label class="ons-label  " for="date-month">Month
      </label>
      <input type="text" id="date-month" class="ons-input ons-input--text ons-input-type__input ons-input-number--w-2" name="month" min="1" max="12" maxlength="2" pattern="[0-9]*" inputmode="numeric" autocomplete="bday-month" />
    </div>
    <div class="ons-field">
      <label class="ons-label  " for="date-year">Year
      </label>
      <input type="text" id="date-year" class="ons-input ons-input--text ons-input-type__input ons-input-number--w-4" name="year" min="1000" max="3000" maxlength="4" pattern="[0-9]*" inputmode="numeric" autocomplete="bday-year" />
    </div>
  </div>
</fieldset>
{% from "components/date-input/_macro.njk" import onsDateInput %}

{{
    onsDateInput({
        "id": "date",
        "legendOrLabel": "Date of birth",
        "description": "For example, 31 3 1980",
        "day": {
            "label": {
                "text": "Day"
            },
            "name": "day",
            "attributes": {
                "autocomplete": "bday-day"
            }
        },
        "month": {
            "label": {
                "text": "Month"
            },
            "name": "month",
            "attributes": {
                "autocomplete": "bday-month"
            }
        },
        "year": {
            "label": {
                "text": "Year"
            },
            "name": "year",
            "attributes": {
                "autocomplete": "bday-year"
            }
        }
    })
}}
Name Type Required Description
id string true The base id for the inputs. -day, -month, and -year will be added to this id and applied to each input
classes string false Classes to apply to the fieldset
legendOrLabel string true The legend for the date fieldset. If only a single field is used, the label will be overriden with this property’s value
description string true The hint text for the date field
day DateField false Config for the day field. If this isn’t provided then a day field will not render
month DateField false Config for the month field. If this isn’t provided then a month field will not render
year DateField false Config for the year field. If this isn’t provided then a year field will not render
mutuallyExclusive MutuallyExclusive (ref) false Configuration object if this is a mutually exclusive date
legendClasses string false Classes to apply to the legend
questionMode boolean false Whether to change the visual layout of the input to a survey question
dontWrap boolean false Prevents the date inputs from being wrapped in a fieldset component
legendIsQuestionTitle boolean false Creates a h1 inside the legend further information
error Error (ref) false Configuration for validation errors

DateField

Name Type Required Description
label Label (ref) true Label config for the field
name string true Name of the field
value number false Preset value for the field
attributes object false HTML attributes (for example, data attributes) to add to the input
{% macro onsDateInput(params) %}
    {% from "components/fieldset/_macro.njk" import onsFieldset %}
    {% from "components/mutually-exclusive/_macro.njk" import onsMutuallyExclusive %}
    {% from "components/input/_macro.njk" import onsInput %}
    {% from "components/error/_macro.njk" import onsError %}

    {% set exclusiveClass = " ons-js-exclusive-group-item" if params.mutuallyExclusive else "" %}

    {%- set numberOfFields = 0 -%}

    {%- if params.day -%}
        {%- set numberOfFields = numberOfFields + 1 -%}
    {%- endif -%}

    {%- if params.month -%}
        {%- set numberOfFields = numberOfFields + 1 -%}
    {%- endif -%}

    {%- if params.year -%}
        {%- set numberOfFields = numberOfFields + 1 -%}
    {%- endif -%}

    {% set fields %}
        {% if params.day is defined and params.day %}
            {{ onsInput({
                "id": params.id + "-day",
                "type": "number",
                "name": params.day.name,
                "classes": (" ons-input--error" if params.error else "") + exclusiveClass | default(""),
                "width": "2",
                "min": 1,
                "max": 31,
                "maxLength": 2,
                "attributes": params.day.attributes,
                "label": {
                    "text": params.day.label.text if numberOfFields > 1 else params.legendOrLabel,
                    "description": params.day.label.description if numberOfFields > 1 else params.description
                },
                "value": params.day.value
            }) }}
        {% endif %}

        {% if params.month is defined and params.month %}
            {{ onsInput({
                "id": params.id + "-month",
                "type": "number",
                "name": params.month.name,
                "classes": (" ons-input--error" if params.error else "") + exclusiveClass | default(""),
                "width": "2",
                "min": 1,
                "max": 12,
                "maxLength": 2,
                "attributes": params.month.attributes,
                "label": {
                    "text": params.month.label.text if numberOfFields > 1 else params.legendOrLabel,
                    "description": params.month.label.description if numberOfFields > 1 else params.description
                },
                "value": params.month.value
            }) }}
        {% endif %}

        {% if params.year is defined and params.year %}
            {{ onsInput({
                "id": params.id + "-year",
                "type": "number",
                "name": params.year.name,
                "classes": (" ons-input--error" if params.error else "") + exclusiveClass | default(""),
                "width": "4",
                "min": 1000,
                "max": 3000,
                "maxLength": 4,
                "attributes": params.year.attributes,
                "label": {
                    "text": params.year.label.text if numberOfFields > 1 else params.legendOrLabel,
                    "description": params.year.label.description if numberOfFields > 1 else params.description
                },
                "value": params.year.value
            }) }}
        {% endif %}
    {% endset %}

    {% if numberOfFields > 1 %}
        {% set fields %}
            <div class="ons-field-group">
                {{ fields | safe }}
            </div>
        {% endset %}
    {% endif %}

    {% if params.mutuallyExclusive is defined and params.mutuallyExclusive %}
        {% call onsMutuallyExclusive({
            "id": params.id,
            "legend": params.legendOrLabel,
            "legendClasses": params.legendClasses,
            "description": params.description,
            "classes": params.classes,
            "dontWrap": params.dontWrap if numberOfFields > 1 else true,
            "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
        }) %}
            {{ fields | safe }}
        {% endcall %}
    {% elif numberOfFields == 1 %}
        {% if params.error is defined and params.error %}
            {% call onsError(params.error) %}
                {{ fields | safe }}
            {% endcall %}
        {% else %}
            {{ fields | safe }}
        {% endif %}
    {% else %}
        {% call onsFieldset({
            "id": params.id,
            "legend": params.legendOrLabel,
            "legendClasses": params.legendClasses,
            "description": params.description,
            "classes": params.classes,
            "dontWrap": params.dontWrap,
            "legendIsQuestionTitle": params.legendIsQuestionTitle,
            "error": params.error
        }) %}
            {{ fields | safe }}
        {% endcall %}
    {% endif %}
{% endmacro %}

When to use this pattern

Use the date pattern when you need to ask users for a date they will already know, or can look up without using a calendar. For example, their date of birth.

When not to use this pattern

Don’t use the date pattern if you’re asking the user about an event they are unlikely to know the exact date of.

How to use this pattern

The date pattern provides the user with 3 input fields to enter a day, month, and year.

The 3 input fields are grouped within a fieldset with a <legend> that lets the user know what they need to enter.

A description is used to provide an example of the required format for the date: “For example, 31 3 1980”.

Warning:

Never automatically tab the user between the fields. This can be unexpected and confusing, and may hinder normal keyboard control.

The date fields have a maxlength limit set to the maximum number of digits required for day and month (2), and year (4), to prevent users entering too many digits in a single field. This is to help those users who expect auto-tabbing between the fields by preventing them from entering the full date into the first field.

Each field uses the width: <number> parameter to set the known number of digits required for each field.

Using autocomplete when asking for date of birth

Use the autocomplete attribute when you are asking for a date of birth to help the user fill out a form more quickly.

To do this, set the autocomplete attribute on each of the 3 date input fields to bday-day, bday-month and bday-year. See how to do this in the HTML and Nunjucks code tabs in the examples above.

You will need to use the autocomplete attribute to meet AA WCAG 2.1 accessibility requirements.

How to check dates

To help users enter a valid date, you should:

  • check they have entered something in the date fields
  • check that what they have entered is valid
  • show an error message if they have not entered anything in one of the fields or what they have entered is not valid

Error messages

Use the correct errors pattern and show the error details above the date fields.

<fieldset id="date" class="ons-fieldset">
  <legend class="ons-fieldset__legend">
    Period from:
    <span class="ons-fieldset__description ">For example, 31 3 2019</span>
  </legend>
  <div class="ons-field-group">
    <div class="ons-field">
      <label class="ons-label  " for="date-day">Day
      </label>
      <input type="text" id="date-day" class="ons-input ons-input--text ons-input-type__input ons-input-number--w-2" name="day" value="1" min="1" max="31" maxlength="2" pattern="[0-9]*" inputmode="numeric" />
    </div>
    <div class="ons-field">
      <label class="ons-label  " for="date-month">Month
      </label>
      <input type="text" id="date-month" class="ons-input ons-input--text ons-input-type__input ons-input-number--w-2" name="month" value="1" min="1" max="12" maxlength="2" pattern="[0-9]*" inputmode="numeric" />
    </div>
    <div class="ons-field">
      <label class="ons-label  " for="date-year">Year
      </label>
      <input type="text" id="date-year" class="ons-input ons-input--text ons-input-type__input ons-input-number--w-4" name="year" value="2019" min="1000" max="3000" maxlength="4" pattern="[0-9]*" inputmode="numeric" />
    </div>
  </div>
</fieldset>
<div class="ons-panel ons-panel--error ons-panel--no-title ons-u-mb-s">
  <span class="ons-u-vh">Error: </span>
  <div class="ons-panel__body">
    <p class="ons-panel__error">
      <strong>Enter a date that is after 1 January 2019</strong>
    </p>
    <fieldset id="date" class="ons-fieldset">
      <legend class="ons-fieldset__legend">
        Period to:
        <span class="ons-fieldset__description ">For example, 31 3 2020</span>
      </legend>
      <div class="ons-field-group">
        <div class="ons-field">
          <label class="ons-label  " for="date-day">Day
          </label>
          <input type="text" id="date-day" class="ons-input ons-input--text ons-input-type__input  ons-input--error ons-input-number--w-2" name="day" value="31" min="1" max="31" maxlength="2" pattern="[0-9]*" inputmode="numeric" />
        </div>
        <div class="ons-field">
          <label class="ons-label  " for="date-month">Month
          </label>
          <input type="text" id="date-month" class="ons-input ons-input--text ons-input-type__input  ons-input--error ons-input-number--w-2" name="month" value="12" min="1" max="12" maxlength="2" pattern="[0-9]*" inputmode="numeric" />
        </div>
        <div class="ons-field">
          <label class="ons-label  " for="date-year">Year
          </label>
          <input type="text" id="date-year" class="ons-input ons-input--text ons-input-type__input  ons-input--error ons-input-number--w-4" name="year" value="2018" min="1000" max="3000" maxlength="4" pattern="[0-9]*" inputmode="numeric" />
        </div>
      </div>
    </fieldset>
  </div>
</div>

{% from "components/date-input/_macro.njk" import onsDateInput %}
{{
    onsDateInput({
        "id": "date",
        "legendOrLabel": "Period from:",
        "description": "For example, 31 3 2019",
        "day": {
            "label": {
                "text": "Day"
            },
            "name": "day",
            "value": "1"
        },
        "month": {
            "label": {
                "text": "Month"
            },
            "name": "month",
            "value": "1"
        },
        "year": {
            "label": {
                "text": "Year"
            },
            "name": "year",
            "value": "2019"
        }
    })
}}

{{
    onsDateInput({
        "id": "date",
        "legendOrLabel": "Period to:",
        "description": "For example, 31 3 2020",
        "day": {
            "label": {
                "text": "Day"
            },
            "name": "day",
            "value": "31"
        },
        "month": {
            "label": {
                "text": "Month"
            },
            "name": "month",
            "value": "12"
        },
        "year": {
            "label": {
                "text": "Year"
            },
            "name": "year",
            "value": "2018"
        },
        "error": {
            "text": "Enter a date that is after 1 January 2019",
            "dsExample": isPatternLib
        }
    })
}}
Name Type Required Description
id string true The base id for the inputs. -day, -month, and -year will be added to this id and applied to each input
classes string false Classes to apply to the fieldset
legendOrLabel string true The legend for the date fieldset. If only a single field is used, the label will be overriden with this property’s value
description string true The hint text for the date field
day DateField false Config for the day field. If this isn’t provided then a day field will not render
month DateField false Config for the month field. If this isn’t provided then a month field will not render
year DateField false Config for the year field. If this isn’t provided then a year field will not render
mutuallyExclusive MutuallyExclusive (ref) false Configuration object if this is a mutually exclusive date
legendClasses string false Classes to apply to the legend
questionMode boolean false Whether to change the visual layout of the input to a survey question
dontWrap boolean false Prevents the date inputs from being wrapped in a fieldset component
legendIsQuestionTitle boolean false Creates a h1 inside the legend further information
error Error (ref) false Configuration for validation errors

DateField

Name Type Required Description
label Label (ref) true Label config for the field
name string true Name of the field
value number false Preset value for the field
attributes object false HTML attributes (for example, data attributes) to add to the input
{% macro onsDateInput(params) %}
    {% from "components/fieldset/_macro.njk" import onsFieldset %}
    {% from "components/mutually-exclusive/_macro.njk" import onsMutuallyExclusive %}
    {% from "components/input/_macro.njk" import onsInput %}
    {% from "components/error/_macro.njk" import onsError %}

    {% set exclusiveClass = " ons-js-exclusive-group-item" if params.mutuallyExclusive else "" %}

    {%- set numberOfFields = 0 -%}

    {%- if params.day -%}
        {%- set numberOfFields = numberOfFields + 1 -%}
    {%- endif -%}

    {%- if params.month -%}
        {%- set numberOfFields = numberOfFields + 1 -%}
    {%- endif -%}

    {%- if params.year -%}
        {%- set numberOfFields = numberOfFields + 1 -%}
    {%- endif -%}

    {% set fields %}
        {% if params.day is defined and params.day %}
            {{ onsInput({
                "id": params.id + "-day",
                "type": "number",
                "name": params.day.name,
                "classes": (" ons-input--error" if params.error else "") + exclusiveClass | default(""),
                "width": "2",
                "min": 1,
                "max": 31,
                "maxLength": 2,
                "attributes": params.day.attributes,
                "label": {
                    "text": params.day.label.text if numberOfFields > 1 else params.legendOrLabel,
                    "description": params.day.label.description if numberOfFields > 1 else params.description
                },
                "value": params.day.value
            }) }}
        {% endif %}

        {% if params.month is defined and params.month %}
            {{ onsInput({
                "id": params.id + "-month",
                "type": "number",
                "name": params.month.name,
                "classes": (" ons-input--error" if params.error else "") + exclusiveClass | default(""),
                "width": "2",
                "min": 1,
                "max": 12,
                "maxLength": 2,
                "attributes": params.month.attributes,
                "label": {
                    "text": params.month.label.text if numberOfFields > 1 else params.legendOrLabel,
                    "description": params.month.label.description if numberOfFields > 1 else params.description
                },
                "value": params.month.value
            }) }}
        {% endif %}

        {% if params.year is defined and params.year %}
            {{ onsInput({
                "id": params.id + "-year",
                "type": "number",
                "name": params.year.name,
                "classes": (" ons-input--error" if params.error else "") + exclusiveClass | default(""),
                "width": "4",
                "min": 1000,
                "max": 3000,
                "maxLength": 4,
                "attributes": params.year.attributes,
                "label": {
                    "text": params.year.label.text if numberOfFields > 1 else params.legendOrLabel,
                    "description": params.year.label.description if numberOfFields > 1 else params.description
                },
                "value": params.year.value
            }) }}
        {% endif %}
    {% endset %}

    {% if numberOfFields > 1 %}
        {% set fields %}
            <div class="ons-field-group">
                {{ fields | safe }}
            </div>
        {% endset %}
    {% endif %}

    {% if params.mutuallyExclusive is defined and params.mutuallyExclusive %}
        {% call onsMutuallyExclusive({
            "id": params.id,
            "legend": params.legendOrLabel,
            "legendClasses": params.legendClasses,
            "description": params.description,
            "classes": params.classes,
            "dontWrap": params.dontWrap if numberOfFields > 1 else true,
            "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
        }) %}
            {{ fields | safe }}
        {% endcall %}
    {% elif numberOfFields == 1 %}
        {% if params.error is defined and params.error %}
            {% call onsError(params.error) %}
                {{ fields | safe }}
            {% endcall %}
        {% else %}
            {{ fields | safe }}
        {% endif %}
    {% else %}
        {% call onsFieldset({
            "id": params.id,
            "legend": params.legendOrLabel,
            "legendClasses": params.legendClasses,
            "description": params.description,
            "classes": params.classes,
            "dontWrap": params.dontWrap,
            "legendIsQuestionTitle": params.legendIsQuestionTitle,
            "error": params.error
        }) %}
            {{ fields | safe }}
        {% endcall %}
    {% endif %}
{% endmacro %}

If all fields are empty

Use “Enter [whatever date is being asked for]”.
For example, “Enter your date of birth”.

If one or two of the fields are empty

Use “Enter the [whatever part or parts of the date is missing]”.
For example, “Enter the year you were born” or “Enter the month and year you were born”.

If what is entered in one of the fields is not a number

Use “Enter a number for [invalid field], for example, [appropriate number]”.
For example, “Enter a number for months, for example, 7”.

If what is entered in a field is above the maximum value for that field

Use “Enter a number that is less than [whatever the maximum is for that part of the date]”.
For example, “Enter a number that is less than 12” for months.

If what is entered is not within the required date range

Use “Enter a date that is between [whatever the minimum is] and [whatever the maximum is]”.
For example, “Enter a date that is between 1 April 2019 and 31 March 2020”.

If the date range entered is not valid

Use “Enter a date that is after [whatever the start date is]”.
For example, “Enter a date that is after 1 January 1999”.

Help improve this pattern

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