A materials form accepts sample weights starting at 0.2 grams, measured in half-gram increments. The author sets a maximum of 5 grams, enters 1 gram, and sees an error. One gram is within the bounds, but it is not on that form's allowed sequence. Changing the error wording would leave the underlying rule wrong.
Work out the accepted sequence first
<label for="weight">Sample weight in grams</label>
<input id="weight" name="weight" type="number"
min="0.2" max="5" step="0.5"
aria-describedby="weight-help">
<p id="weight-help">Start at 0.2 g and increase by 0.5 g.</p>
For this example, subtract 0.2 from the proposed value and divide by 0.5. A whole-number result places it on the step grid:
| Proposed value | Calculation | Result |
|---|---|---|
| 0.7 | (0.7 − 0.2) / 0.5 = 1 | On the grid |
| 1.0 | (1.0 − 0.2) / 0.5 = 1.6 | Step mismatch |
| 1.2 | (1.2 − 0.2) / 0.5 = 2 | On the grid |
| 5.2 | (5.2 − 0.2) / 0.5 = 10 | On the grid, above the maximum |
The last row explains why step and bounds need separate checks. The largest permitted value here is 4.7, even though max says 5. If the actual lab accepts every weight to one decimal place, use step="0.1" instead. Write the data rule the lab uses rather than changing submitted values to appease the control.
An omitted step still imposes a rule
For number and range inputs, the default step is 1. A number input without a step attribute does not automatically accept arbitrary decimals. A fractional minimum also shifts the sequence: min="0.2" with the default step permits 0.2, 1.2, 2.2, and later values on that sequence.
If your application accepts any numeric increment, set step="any". That removes the step restriction; it does not remove min, max, or the requirement that the input contain a number. An optional empty field is a separate case; add required only if the task needs a value.
Keep the initial value and the minimum conceptually separate. The step base uses a valid min when present, otherwise the initial value attribute, otherwise the type's default base. The week type has its own base. For the sample-weight form, declaring min explicitly makes its intended anchor visible. Consult the step specification before applying numeric arithmetic directly to date or week strings.
Time steps use seconds
The units depend on the input type. Date steps use days; month steps use months. Time and datetime-local steps use seconds, and their default step is 60 seconds.
<label for="pickup">Pickup time</label>
<input id="pickup" name="pickup" type="time"
min="09:10" max="16:40" step="1800">
This example starts its half-hour grid at 09:10: 09:10, 09:40, 10:10, and so on. step="30" would mean 30 seconds, not 30 minutes. Neither setting makes the control check appointment availability or assign a time zone.
Inspect the failing rule without parsing the browser's message
In the browser console for your actual form:
const field = document.querySelector('#weight');
console.log({
value: field.value,
stepMismatch: field.validity.stepMismatch,
belowMinimum: field.validity.rangeUnderflow,
aboveMaximum: field.validity.rangeOverflow,
badInput: field.validity.badInput
});
These flags distinguish an off-grid weight from a weight above the limit. The human-readable validation message can vary by browser and language. Do not compare its exact text in application logic. HTML form validation covers error messages and accessible field hints.
Before releasing the materials form, check a starting value, one increment, an off-grid value, and an on-grid value above the maximum. Apply the same accepted sequence on the receiving server. Keep the entered weight visible when reporting an error so the person can correct it without guessing what changed.