HTML Min, Max, and Step Attributes
Plenty of fields have a sensible range. A quantity must be at least 1. A rating goes from 1 to 5. A booking cannot be in the past. A volume slider runs from 0 to 100. HTML gives you three attributes for this: min and max set the smallest and largest allowed value, and step controls which values in between are allowed.
They work on numbers, dates, times, months, weeks, and sliders. This lesson shows how each behaves, how the step rule surprises people, and how to read the validity flags when something is out of range.
What You Will Learnβ
- What
min,max, andstepdo, and which input types support them - How the browser reacts to a value that is too small, too large, or off-step
- The
stepbase rule, andstep="any" - Limits for dates and times, including "today" and linked date fields
- The
rangeUnderflow,rangeOverflow, andstepMismatchflags - Pitfalls with decimals and money
- Why the server must check ranges again
The Basicsβ
- HTML Code
- Browser Output
<form action="/order" method="post">
<label for="qty">Quantity (1 to 10)</label>
<input type="number" id="qty" name="qty" min="1" max="10" step="1" value="1" required />
<button type="submit">Add to cart</button>
</form>
The up and down arrows stay between 1 and 10. Type 0 or 11 and press Add to cart: the browser blocks the form with a message such as "Value must be greater than or equal to 1."
| Attribute | Meaning |
|---|---|
min | The smallest allowed value |
max | The largest allowed value |
step | The spacing between allowed values. Valid values are min plus a whole number of steps |
Where They Workβ
| Input type | Value format for min and max | Default step |
|---|---|---|
number | A number, like 1 or 0.5 | 1 |
range | A number (defaults: min 0, max 100) | 1 |
date | yyyy-mm-dd, like 2026-12-25 | 1 day |
month | yyyy-mm, like 2026-12 | 1 month |
week | yyyy-Www, like 2026-W52 | 1 week |
time | HH:mm, like 09:30 | 60 seconds |
datetime-local | yyyy-mm-ddTHH:mm | 60 seconds |
text, email, url, tel, password, search | Not supported. Use minlength and maxlength | |
checkbox, radio, file, select, textarea | Not supported |
Formats are always the machine formats shown above, no matter how the visitor's browser displays them. See the lessons on Date, Time, Month, Week, and Number.
What the Browser Doesβ
- Spinner arrows and pickers stay within the range. A date picker greys out days outside
minandmax. - Typing is not blocked. A visitor can still type
99into a field withmax="10". The check happens when the form is submitted, with a message. - An empty field passes.
minandmaxonly check values that exist. Addrequiredif a value is mandatory. - The validity flags tell your script what went wrong (below).
Try It: Watch the Flagsβ
Type values into both fields (try 0, 5, 11, 2.5, 12, abc), and watch which flags switch on.
- HTML Code
- Browser Output
<label for="qty">Quantity (1 to 10, whole numbers)</label>
<input type="number" id="qty" name="qty" min="1" max="10" step="1" required />
<label for="pct">Discount (0 to 50, in steps of 5)</label>
<input type="number" id="pct" name="discount" min="0" max="50" step="5" />
<pre id="out"></pre>
const fields = [document.getElementById("qty"), document.getElementById("pct")];
const out = document.getElementById("out");
function show() {
out.textContent = fields
.map((field) => {
const v = field.validity;
return field.name + ": underflow=" + v.rangeUnderflow +
", overflow=" + v.rangeOverflow + ", stepMismatch=" + v.stepMismatch;
})
.join("\n");
}
fields.forEach((field) => field.addEventListener("input", show));
| Flag | true when | Example |
|---|---|---|
rangeUnderflow | The value is below min | 0 with min="1" |
rangeOverflow | The value is above max | 11 with max="10" |
stepMismatch | The value is not on a valid step | 12 with step="5" |
badInput | The browser cannot read what was typed | Letters in a number field |
valueMissing | Empty and required | No value |
Understanding stepβ
step is the part that surprises people. Valid values are the step base plus a whole number of steps. The step base is:
- the
minvalue, if there is one, - otherwise the
valueattribute, if there is one, - otherwise
0(or the type's usual starting point).
| Markup | Valid values |
|---|---|
step="5" (no min) | 0, 5, 10, 15... and -5, -10... |
min="1" step="5" | 1, 6, 11, 16... (not 5 or 10) |
min="0" max="1" step="0.25" | 0, 0.25, 0.5, 0.75, 1 |
step="any" | Any number, with no step check at all |
So min="1" step="5" surprises people: the step counts from min, not from zero.
step="any"β
By default, type="number" accepts whole numbers only (step 1). A decimal like 2.5 gives a stepMismatch error. Two ways to allow decimals:
<input type="number" step="0.1" /> <!-- one decimal place -->
<input type="number" step="any" /> <!-- any decimal at all -->
Steps for dates and timesβ
| Type | Unit of step | Example |
|---|---|---|
date | Days | step="7" allows every 7th day from the step base |
month | Months | step="3" for quarters |
week | Weeks | step="2" |
time | Seconds | step="900" allows every 15 minutes |
datetime-local | Seconds | step="1800" for half-hour slots |
For a time field, setting step to less than 60 (such as step="1") also shows the seconds box.
Dates: "From Today" and Linked Fieldsβ
A very common need is "no dates in the past," and "the end must not be before the start." The page has to calculate the date, because HTML has no keyword for "today."
Try it. The arrival date is limited to the next 60 days, and the departure picker moves its minimum to match the arrival.
- HTML Code
- Browser Output
<form action="/book" method="post">
<label for="arrive">Arrival</label>
<input type="date" id="arrive" name="arrive" required />
<label for="leave">Departure</label>
<input type="date" id="leave" name="leave" required />
<button type="submit">Book</button>
</form>
const arrive = document.getElementById("arrive");
const leave = document.getElementById("leave");
// Format a Date as yyyy-mm-dd in the visitor's local time
function toInputDate(date) {
const pad = (n) => String(n).padStart(2, "0");
return date.getFullYear() + "-" + pad(date.getMonth() + 1) + "-" + pad(date.getDate());
}
const now = new Date();
const last = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 60);
arrive.min = toInputDate(now);
arrive.max = toInputDate(last);
leave.min = toInputDate(now);
leave.max = toInputDate(last);
// The departure can never be before the arrival
arrive.addEventListener("change", () => {
if (arrive.value) leave.min = arrive.value;
});
toISOString() for "today"new Date().toISOString().slice(0, 10) gives today's date in UTC, which can be yesterday or tomorrow for the visitor. Build the string from the local year, month, and day, as in the example above.
Times that cross midnightβ
For a time field, a min that is later than max is allowed and means an overnight range. For example, min="22:00" max="06:00" accepts late evening and early morning. For every other type, a min larger than max makes the field impossible to fill in correctly, so avoid it. (See Time.)
Decimals and Moneyβ
Computers store decimals in binary, so 0.1 + 0.2 is 0.30000000000000004. With step, browsers allow for this, but your own code may not.
- Money:
type="number" step="0.01"works for typing. But in your code and on the server, store money as whole cents (or the smallest unit), not as floating-point numbers. - Do not compare decimals with
===in JavaScript. Round first, or work in whole units. - Currencies differ. Some have no minor unit (the Japanese yen), and some have three decimal places (the Kuwaiti dinar). Do not hard-code two decimals if you serve several currencies.
Slider Rangesβ
type="range" always has a value, so min, max, and step define the whole slider. The defaults are 0, 100, and 1.
<label for="volume">Volume</label>
<input type="range" id="volume" name="volume" min="0" max="10" step="1" value="5" />
A slider cannot be left empty, so it never gives valueMissing. (See Range.)
Stylingβ
input:out-of-range {
border-color: #b91c1c;
}
input:in-range {
border-color: #15803d;
}
:in-range and :out-of-range match fields that have min or max and a value inside or outside it. As always, add a text message too. Colour alone is not enough. For fields that should not look red before the visitor has typed, :user-invalid is gentler (see Form Validation).
Accessibility at a Glanceβ
- Say the range in the label or a hint: "Quantity (1 to 10)". Screen readers also announce
minandmaxfor numbers and sliders. - Write helpful messages: "Choose a date from today to 60 days ahead," not "Out of range."
- Use the right input type. A date picker is more accessible than three number fields, and gives keyboard users arrows and shortcuts.
- Keep the picker's range and your message in sync when
minormaxchange by script. - Do not make a range impossible (for example, a departure
minthat is after itsmax). - Tell visitors why a date is unavailable ("Fully booked"), if you block dates in other ways.
- Offer a text alternative for dates and times if the browser's picker is hard to use for your audience.
Security Notesβ
min,max, andstepare not security. A visitor can edit them in the developer tools, or send a request with any value by hand.- Check every range on the server, including prices, quantities, discounts, and dates. Never trust a number the browser sends: a
quantityof-5or adiscountof100can cause real harm. - Do not take the price from the form. Look up the price on the server by product ID.
- Check business limits the browser cannot know, such as stock left, booking availability, or the maximum order per customer.
- Watch for huge and special numbers: very large values,
NaN, and scientific notation (1e9) can cause trouble in code. Convert and range-check them.
See Secure Form Design.
Global Audience Notesβ
- Displays differ, values do not. A visitor in one country sees
25/12/2026, another sees12/25/2026, and both are sent as2026-12-25. Setminandmaxin the machine format. - Number formats differ. Some visitors type
1,5for one and a half, and the browser handles it when the input type isnumber. Read the value on the server as a plain number, not as text. - Weeks start on different days, and the
weektype uses ISO weeks. Explain this if it matters. - Time zones: "today" depends on where the visitor is. Check on the server with a clear time zone rule, and store dates in UTC or with their zone.
- Calendars: some visitors use calendars other than the Gregorian one. The value is always Gregorian, even when the picker displays another calendar.
- Browser support for
monthandweekis incomplete, so plan a fallback.
Common Mistakes to Avoidβ
- Trusting
minandmaxas security. The server must check ranges. - Using them on text inputs, where they do nothing. Use
minlengthandmaxlength. - Forgetting that
stepcounts frommin. Withmin="1" step="5",5is not valid. - Getting
stepMismatchon decimals becausestepis1. Usestep="0.01"or"any". - Writing dates in the wrong format, instead of
yyyy-mm-dd. - Using
toISOString()for "today," which uses UTC. - Setting
minlarger thanmax, so no value is valid (except for time). - Expecting typing to be blocked. The check happens on submit.
- Expecting an empty field to fail. Add
required. - Storing money as floating-point numbers.
- Taking prices or totals from the form.
- Not telling visitors the range, so errors are a surprise.
Practice Timeβ
Try these in your editor:
- Make a number field with
min="1",max="10", and test both ends. - Type a decimal into it and see
stepMismatch. Then addstep="0.5". - Try
min="1" step="5"and find which values are valid. - Switch to
step="any"and compare. - Build a
datefield that cannot be before today, set by script. - Add a second date that cannot be before the first.
- Make a
timefield that only allows every 15 minutes. - Make a slider from 0 to 10 and show its value with
<output>. - Read
validity.rangeUnderflowandrangeOverflowin the console, and write your own messages. - Bonus: write the server-side check for a quantity from 1 to 10 and a booking date within 60 days.
Quick Recapβ
Click a question to check your answer.
1. What do min, max, and step do?
min and max set the smallest and largest allowed value, and step sets the spacing between valid values. They work on number, range, date, month, week, time, and datetime-local inputs.
2. Do they work on text inputs?
No. For text, use minlength and maxlength, which limit the number of characters.
3. Where does step count from?
From the step base: the min value if there is one, otherwise the value attribute, otherwise zero. So min="1" step="5" allows 1, 6, 11, and so on.
4. How do you allow any decimal in a number field?
Use step="any", or a specific step such as step="0.01". The default step of 1 allows whole numbers only.
5. How do you stop visitors choosing a past date?
Set min to today's date, in yyyy-mm-dd format, using the visitor's local date (not toISOString()). HTML has no built-in "today" keyword, so a little script is needed.
6. Are min and max enough to protect a price or quantity?
No. Anyone can bypass them. The server must check every number and date, and should not accept prices from the form at all.
Key Takeawaysβ
min,max, andsteplimit the value of number, range, date, month, week, time, and datetime-local inputs.- Out-of-range values give
rangeUnderfloworrangeOverflow, and off-step values givestepMismatch. - The
stepbase ismin, somin="1" step="5"allows 1, 6, 11. Usestep="any"or a decimal step for decimals. - The check runs on submit, not while typing, and empty fields pass unless
required. - Set date limits by script in
yyyy-mm-ddformat using local time, and link start and end fields. - Store money as whole cents, and never take prices from the form.
- These attributes are a convenience. The server must check every range again.
Next, learn how browsers fill in forms for visitors, in Autocomplete, or return to the Forms overview.