HTML Input Type Month
Not every date needs a day. A credit card expires in a month. A subscription renews for a month. A report covers a month, and a course starts in a month. For these, asking for a full date is awkward, and a plain text box invites "12/26", "Dec 2026", and "2026-12" all at once.
type="month" asks for exactly what you need: a year and a month, and nothing else. It is a small, friendly input, with one big catch: browser support is not complete. In this lesson, you will learn how to use it, and how to build a reliable fallback.
What You Will Learnβ
- What
type="month"shows, and the value it sends min,max, andstep- Reading the value in JavaScript (and a surprise about
valueAsNumber) - Browser support, and a safe fallback
- Good uses, and cases where a different design is better
- Accessibility and common mistakes
The Basicsβ
- HTML Code
- Browser Output
<label for="billing">Billing month</label>
<input type="month" id="billing" name="billing" />
In browsers that support it (such as Chrome and Edge on desktop, and most mobile browsers), you see a field with a month and a year, and a picker that lists the months. In browsers that do not, you see an ordinary text box. More on this soon.
The Value Formatβ
yyyy-mmA four-digit year, a dash, and a two-digit month, such as 2026-12. It is the same however the field is displayed.
| What the visitor sees | What is sent |
|---|---|
December 2026 | 2026-12 |
diciembre de 2026 (Spanish) | 2026-12 |
2026εΉ΄12ζ (Japanese) | 2026-12 |
March 2027 | 2027-03 |
| (nothing, or incomplete) | an empty string |
billing=2026-12
The starting value in HTML uses the same format:
<input type="month" name="billing" value="2026-12" />
There is no day in the value. If your server needs a full date, it must choose one itself (for example, the first day of the month).
Min, Max, and Stepβ
| Attribute | What it does | Example |
|---|---|---|
min | The earliest month | min="2026-01" |
max | The latest month | max="2027-12" |
step | The gap in months (default 1) | step="3" |
<label for="start">Course starts</label>
<input
type="month"
id="start"
name="start"
min="2026-09"
max="2027-09"
step="3"
required
/>
minandmaxuse theyyyy-mmformat.stepcounts in months, frommin. Withmin="2026-09"andstep="3", valid months are September 2026, December 2026, March 2027, and so on, which suits quarterly intakes.- Months outside the range are greyed out in the picker, and rejected on submit.
Try It: Month Value and Its Formsβ
Pick a month in the field. The panel shows the value that is sent, a friendly name for the month, and the valueAsNumber.
- HTML Code
- Browser Output
<label for="billing">Billing month</label>
<input type="month" id="billing" name="billing" min="2026-01" max="2027-12" value="2026-12" />
<p id="out" role="status"></p>
const input = document.getElementById("billing");
const out = document.getElementById("out");
function update() {
if (!input.value) {
out.textContent = "Choose a month.";
return;
}
const [year, month] = input.value.split("-").map(Number);
const name = new Intl.DateTimeFormat("en-GB", {
month: "long",
year: "numeric",
timeZone: "UTC",
}).format(new Date(Date.UTC(year, month - 1, 1)));
out.textContent =
"Sent: " + input.value + " | " + name +
" | valueAsNumber: " + input.valueAsNumber;
}
input.addEventListener("input", update);
update();
Reading the Value in JavaScriptβ
| Property | What it gives | Example for 2026-12 |
|---|---|---|
input.value | The string yyyy-mm | "2026-12" |
input.valueAsDate | A Date at midnight UTC on the first of the month | 2026-12-01T00:00:00.000Z |
input.valueAsNumber | Months since January 1970 | 683 |
valueAsNumber is not milliseconds hereFor date, time, and datetime-local, valueAsNumber is a count of milliseconds. For month it is the number of months since January 1970. December 2026 is month 683 (that is, (2026 - 1970) * 12 + 11). Mixing the two up leads to some very strange dates.
The most dependable approach is to split the string:
const [year, month] = input.value.split("-").map(Number); // 2026, 12
To show a friendly name, use Intl.DateTimeFormat. Because we build the date in UTC, we format in UTC too (otherwise some visitors would see the previous month):
const text = new Intl.DateTimeFormat(undefined, {
month: "long",
year: "numeric",
timeZone: "UTC",
}).format(new Date(Date.UTC(year, month - 1, 1)));
Browser Support and a Safe Fallbackβ
This is the catch. type="month" is not supported everywhere:
| Browser | Support |
|---|---|
| Chrome, Edge (desktop and Android) | Yes |
| Safari on iPhone and iPad | Yes |
| Firefox (desktop) | No, shows a text box |
| Safari on desktop | No, shows a text box |
Where it is not supported, the field behaves like type="text". The visitor can type anything, and the browser checks nothing, so the value might be Dec 2026. (Support changes over time, so test in the browsers your visitors use.)
You can build a fallback in plain HTML. A pattern is ignored by browsers that do support month, but applied by those that fall back to text:
<label for="billing">Billing month</label>
<input
type="month"
id="billing"
name="billing"
placeholder="yyyy-mm"
pattern="[0-9]{4}-(0[1-9]|1[0-2])"
title="Use the format yyyy-mm, such as 2026-12"
required
/>
<p id="billing-hint">If you see a plain text box, type the year, a dash, and the month, such as 2026-12.</p>
placeholderandpatternare not part of the month input in browsers that support it, so they cause no harm, but they help where it falls back.- The hint text tells people what to type in a plain box.
- Always validate on the server, because a fallback text box can send anything.
A Fully Reliable Alternative: Two Fieldsβ
If your audience uses many browsers, two ordinary fields are more dependable, and often easier to use:
<fieldset>
<legend>Billing month</legend>
<label for="m">Month</label>
<select id="m" name="month" required>
<option value="">Choose</option>
<option value="01">January</option>
<option value="02">February</option>
<option value="03">March</option>
<option value="04">April</option>
<option value="05">May</option>
<option value="06">June</option>
<option value="07">July</option>
<option value="08">August</option>
<option value="09">September</option>
<option value="10">October</option>
<option value="11">November</option>
<option value="12">December</option>
</select>
<label for="y">Year</label>
<input type="number" id="y" name="year" min="2026" max="2035" required />
</fieldset>
The fieldset and legend group them, so screen readers announce "Billing month" first. Month names in a select remove all doubt about order, in any language.
Good Uses and Poor Usesβ
Good use for month | Why |
|---|---|
| A report or statement period | Only the month matters |
| A subscription start or billing month | No exact day is needed |
| A course or program intake | Quarterly or yearly starts, with step |
| A graduation month or start month | A rough date is enough |
| A "month of birth" for an age range | Rarely, and only if truly needed |
| Poor use | Better choice |
|---|---|
| An exact event | date or datetime-local |
| A card expiry on a payment form | Two fields (see below) |
| A birthday | Day, month, and year fields, or date |
| Many years in the past or future | A select for the month, and a number field for the year |
Card Expiry Datesβ
Card expiry looks like a natural fit for type="month", but payment forms have special needs: autofill, and different formats across card providers. The most dependable way is two fields with the standard autofill tokens:
<label for="cc-month">Expiry month</label>
<select id="cc-month" name="cc-month" autocomplete="cc-exp-month"> ... </select>
<label for="cc-year">Expiry year</label>
<select id="cc-year" name="cc-year" autocomplete="cc-exp-year"> ... </select>
Better still, use a payment provider's hosted fields, so card numbers never touch your own server.
Other Attributesβ
| Attribute | What it does | Example |
|---|---|---|
name, id | Sent key, and label link | name="billing" |
value | The starting month (yyyy-mm) | value="2026-12" |
required | Cannot be empty | required |
readonly, disabled | Behavior flags | readonly |
list | Suggestions from a datalist | list="starts" |
On browsers that support it, placeholder, maxlength, minlength, pattern, and size have no effect.
Setting the Current Month as a Defaultβ
HTML cannot know today's month, so use JavaScript, with the local date:
const now = new Date();
const month = String(now.getMonth() + 1).padStart(2, "0");
const thisMonth = now.getFullYear() + "-" + month;
const input = document.getElementById("billing");
input.value = thisMonth;
input.min = thisMonth; // nothing in the past
Avoid new Date().toISOString().slice(0, 7), which uses UTC, and can be a month off near the end or start of a month.
Styling a Month Inputβ
input[type="month"] {
padding: 0.6rem 0.75rem;
border: 1px solid #64748b;
border-radius: 6px;
font: inherit;
font-size: 1rem;
min-height: 2.75rem;
}
input[type="month"]:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 2px;
}
input[type="month"]:invalid {
border-color: #b91c1c;
}
- The picker popup cannot be styled.
- Because some browsers show a plain text box, make sure the field looks fine either way, and keep the help text visible.
Accessibility at a Glanceβ
- Always add a label, such as "Billing month."
- State the format and range in text: "Choose a month from January 2026 to December 2027." Link it with
aria-describedby. - Give a hint for fallback browsers: "Type the year, a dash, and the month: 2026-12."
- Use the native input where supported. Custom pickers are hard to make accessible.
- Allow typing. Many people prefer to type the month and year.
- Give clear errors: "Choose a month between January 2026 and December 2027."
- Write month names in full in text ("March," not "03"), so no one has to guess.
Security and Validation Notesβ
- Re-check on the server. Parse the string strictly as
yyyy-mm, check the month is 01 to 12, and enforce the range. A fallback text box can send anything. - Decide how to turn a month into a date (such as the first day) in one place on the server, and document it.
- Use the server's clock for rules like "no months in the past."
- Be careful with payment data. Never log or store full card details unless you are certified to do so. Prefer a payment provider.
Global Audience Notesβ
- Month names and order follow the visitor's language in the field. Your server still receives
yyyy-mm. - Do not print a bare "12/26" in text. It could mean December 2026 or the 12th of 2026, depending on the country. Write "December 2026."
- Calendars differ, though the value is always in the Gregorian calendar.
- Fiscal years often start in other months (April in India and the UK, October in the US government). If your "month" means a fiscal month, say so in the label.
Common Mistakes to Avoidβ
- Assuming every browser supports
type="month". Firefox and desktop Safari show a text box. - No fallback hint or
patternfor those browsers. - Setting
valuein the wrong format. It must beyyyy-mm. - Expecting a day in the value. There is none.
- Treating
valueAsNumberas milliseconds. It counts months since January 1970. - Showing
valueAsDatein local time, which can show the previous month. - Using
toISOString().slice(0, 7)for "this month," which uses UTC. - Using
monthfor exact events that need a day. - Using
monthfor card expiry instead of two autofill-friendly fields. - Forgetting that
stepcounts months, not days. - No label, or an unclear one.
- Trusting the browser alone and skipping server-side checks.
Practice Timeβ
Try these in your editor:
- Build a labeled
type="month"field and submit it withmethod="get". Read the address. - Open it in Chrome, and then in Firefox, and compare what you see.
- Set
value="2026-12", then tryvalue="12/2026". - Add
min="2026-01"andmax="2027-12", and open the picker. - Use
step="3"withmin="2026-09"to allow quarterly months. - Log
valueAsNumber, and check that December 2026 gives683. - Add
placeholderandpatternas a fallback, and test by changing the type totext. - Format the month name with
Intl.DateTimeFormatandtimeZone: "UTC". - Build the two-field version with a select and a number input in a
fieldset. - Bonus: fill in the current month with JavaScript, using local date parts.
Quick Recapβ
Click a question to check your answer.
1. What format is the value of a month input?
yyyy-mm, such as 2026-12. It has no day, and it is the same however the field is displayed. An incomplete choice gives an empty string.
2. What does step="3" mean on a month input?
Valid months are three months apart, counted from min. With min="2026-09", that allows September, December, March, and so on.
3. What does valueAsNumber give for a month input?
The number of months since January 1970 (not milliseconds). December 2026 is 683.
4. Which common browsers show a plain text box for type="month"?
Firefox and Safari on desktop. Chrome, Edge, and mobile Safari support it. Test in the browsers your visitors use.
5. How can you make a month field work where it is unsupported?
Add a placeholder and a pattern (such as [0-9]{4}-(0[1-9]|1[0-2])), explain the format in text, or use a select for the month and a number field for the year. Always validate on the server.
6. Why is a card expiry better as two separate fields?
Two fields with the cc-exp-month and cc-exp-year autofill tokens work reliably across browsers and card formats, and are easier to fill in. A payment provider's hosted fields are better still.
Key Takeawaysβ
type="month"picks a year and month, and sendsyyyy-mm, with no day.minandmaxuse the same format.stepcounts months, which suits quarterly or yearly starts.valueAsNumberis months since January 1970. Splitting the string is the safest way to read it.- Browser support is incomplete. Give a text fallback with
pattern, a format hint, and server-side checks, or use a select and a number field. - Choose
monthfor periods and billing months, not for exact events, birthdays, or card expiry. - Label the field, state the format and range in text, and always validate on the server.
Next, learn how to pick a week of the year, in Week, or return to the Forms overview.