Date range fields
Category: Forms · Tags: date, range, validation, fieldset, booking
How to use it
Use when someone picks a start and an end date that are usually close together, such as a stay, a leave request or a report period. For a single date use one date input; for picking dates by looking at availability, use a calendar grid instead. Change the ids and the wording of the legend and hint, and validate the range again on the server.
Accessibility
Two native <input type="date"> fields sit in a <fieldset> whose <legend> names the range, each with its own <label for>, so every browser supplies its own keyboard-operable date picker. When the end date falls before the start date the end field gets aria-invalid="true" and an error message that is linked to it through aria-describedby and announced by its polite live region; the end field's min also follows the start date. Labels are #0f172a on white (17.85:1), the hint #475569 on white (7.58:1) and the error #b91c1c on white (6.47:1), and both fields show a 2px #1d4ed8 focus outline.
Source (copy-ready)
<fieldset class="dcl-daterange">
<legend class="dcl-daterange__legend">Stay dates</legend>
<p class="dcl-daterange__hint" id="dcl-daterange-hint">Check-out must be on or after check-in.</p>
<div class="dcl-daterange__fields">
<div class="dcl-daterange__field">
<label class="dcl-daterange__label" for="dcl-daterange-start">Check-in</label>
<input class="dcl-daterange__input dcl-daterange__start" type="date" id="dcl-daterange-start" name="start" value="2027-03-12" aria-describedby="dcl-daterange-hint">
</div>
<div class="dcl-daterange__field">
<label class="dcl-daterange__label" for="dcl-daterange-end">Check-out</label>
<input class="dcl-daterange__input dcl-daterange__end" type="date" id="dcl-daterange-end" name="end" value="2027-03-15" min="2027-03-12" aria-describedby="dcl-daterange-hint dcl-daterange-error">
</div>
</div>
<p class="dcl-daterange__error" id="dcl-daterange-error" aria-live="polite"></p>
</fieldset>
<style>
.dcl-daterange { max-width: 30rem; min-width: 0; margin: 0; padding: 1rem; border: 1px solid #cbd5e1; border-radius: 0.5rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-daterange__legend { padding: 0 0.35rem; font-weight: 700; color: #0f172a; background-color: #ffffff; }
.dcl-daterange__hint { margin: 0 0 0.75rem; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-daterange__fields { display: flex; flex-wrap: wrap; gap: 0.9rem; }
.dcl-daterange__field { display: flex; flex-direction: column; gap: 0.3rem; flex: 1 1 10rem; min-width: 0; }
.dcl-daterange__label { font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-daterange__input { box-sizing: border-box; width: 100%; max-width: 100%; padding: 0.45rem 0.6rem; border: 1px solid #64748b; border-radius: 6px; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-daterange__input[aria-invalid="true"] { border: 2px solid #b91c1c; }
.dcl-daterange__input:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 1px; }
.dcl-daterange__error { margin: 0.6rem 0 0; font-weight: 600; font-size: 0.9rem; color: #b91c1c; background-color: #ffffff; }
</style>
<script>
(function () {
document.querySelectorAll(".dcl-daterange").forEach(function (root) {
if (root.dataset.dclBound) { return; }
root.dataset.dclBound = "true";
var start = root.querySelector(".dcl-daterange__start");
var end = root.querySelector(".dcl-daterange__end");
var error = root.querySelector(".dcl-daterange__error");
if (!start || !end) { return; }
function check() {
if (start.value) { end.min = start.value; } else { end.removeAttribute("min"); }
// ISO yyyy-mm-dd strings compare correctly as text.
var bad = Boolean(start.value && end.value && end.value < start.value);
if (bad) { end.setAttribute("aria-invalid", "true"); } else { end.removeAttribute("aria-invalid"); }
if (error) { error.textContent = bad ? "Check-out (" + end.value + ") is before check-in (" + start.value + "). Choose a later check-out date." : ""; }
}
start.addEventListener("change", check);
end.addEventListener("change", check);
check();
});
})();
</script>
Was this useful? Send feedback.