# Dates in a form

> Why LO-TEK has no date picker, and how to ask for a date that works everywhere.

LO-TEK has no date picker. The browser has one, and it is better than ours was.

## The markup

```html
<lt-field>
  <label class="lt-label" for="deliver-by">Deliver by</label>
  <input class="lt-input" data-variant="date" id="deliver-by"
         name="deliver-by" type="date"
         min="2079-01-01" max="2079-12-31">
  <p class="lt-help-text">Type the date as 31/05/2079, or pick it in the calendar.</p>
</lt-field>
```

If the code that renders the markup can call a builder function, in TypeScript, Kotlin or Java, it writes the attributes itself and does not need `<lt-field>`:

```ts
import { lt } from "@lo-tek/interfaces"


const field = lt.field({ id: "deliver-by", help: true, invalid })
const control = lt.input({ type: "date", state: field.state })
```

## Why there is no component

We had one, `<lt-calendar>`, and removed it. The sums did not add up:

| Task | Who does it |
| - | - |
| Opening the calendar panel | the browser |
| Keyboard navigation in the panel | the browser |
| Translation and first day of the week | the browser, following the user’s own settings |
| Valid range | `min` and `max` |
| ISO value to the server | the browser |
| Wiring the label, help text and error text | `lt.field()` |

What was left for a separate component was nothing, apart from the panel looking the same in every browser. That is the same trade-off we have already made for `<select>`: the dropdown is drawn by the operating system, and nobody misses it looking the same in every browser.

The panel is also transient. It is not part of the page; it opens and closes again.

## Three browser differences you need to know

The first two are tested in Chromium, Firefox and WebKit, not read in a specification, and the third was seen in Safari on an iPhone. The tests used a Norwegian locale (`nb`), where the browser shows the date as 31.05.2079, so the test results below use that format. The help text uses the page’s own format, 31/05/2079.

**WebKit requires the separators.** If the user types `31052079` without full stops, the field stays empty with `validity.badInput`, and the form sends nothing. `31.05.2079` works. So the help text should say how to write the date, with an example in the page’s format, such as “Type the date as 31/05/2079, or pick it in the calendar.”

**Chromium accepts the wrong order as valid.** `20260531` became the year 60531, and `validity.valid` was still `true`. So `min` and `max` should always be set. Without them a typo gets through as a silently wrong date.

**Safari on an iPhone lets the user pick a date outside `min` and `max`.** The picker offers every year, and 25 April 2081 lands in a field limited to 2079. The field gets `rangeOverflow`, so the error message must say which dates are allowed, not that the date does not exist, and the server must check the range too.

## Validating on the server

```ts
const date = formData.get("deliver-by")


if (typeof date !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(date)) {
  return error("Enter the date as day, month and year.")
}


// 2079-02-31 has the right shape, and is no date.
const [year, month, day] = date.split("-").map(Number)
const parsed = new Date(Date.UTC(year, month - 1, day))
if (parsed.getUTCMonth() !== month - 1 || parsed.getUTCDate() !== day) {
  return error("Enter a date that exists.")
}


if (date < "2079-01-01" || date > "2079-12-31") {
  return error("The board only takes jobs for 2079.")
}
```

The field always sends ISO when the browser accepted the date, and ISO dates compare correctly as strings. But `badInput` means the server gets an empty string from someone who meant to type something, and your app has to point out that difference.

## What the system does not do

- **We do not validate the date.** `min` and `max` are the browser’s check, and it can be bypassed. The server has to do it again.
- **We do not translate the error messages.** The browser’s own bubbles (“Please enter a valid value”) come in the browser’s language. If you want them in the page’s language, you have to turn them off with `novalidate` and show `<p class="lt-error-text">` yourself.
- **We do not handle date ranges.** From and to are two fields, and that one has to come before the other is your app’s rule.
- **We have no date picker you can style.** If you need a calendar that looks the same in every browser, that is a separate project in your app, not something that lives here.
