Skip to content

Date & time

Craft renders date and time pickers as plain text inputs with a trailing glyph — the calendar and clock are cues, not native <input type="date"> controls. On touch devices Craft swaps in native date/time inputs instead, so the desktop markup below isn't what mobile users get.

M/D/YYYY

Leave blank to never expire.

M/D/YYYY
{{ forms.dateField({ label: "Post date", name: "postDate", value: entry.postDate }) }}

{{ forms.timeField({ label: "Doors open", name: "doorsAt", value: value }) }}

{{ forms.dateTimeField({
    label: "Expiry",
    instructions: "Leave blank to never expire.",
    name: "expiryDate",
    value: entry.expiryDate,
}) }}

Pass a DateTime (or anything Twig's date() accepts) as value; pass null for an empty field. dateTimeField sets fieldset: true, since it groups two labelled controls.

What gets submitted

A date field doesn't post a single string. It posts an array — the visible value plus the locale and timezone needed to interpret it:

postDate[date]      => "7/16/2026"   // formatted in the user's locale, not ISO
postDate[locale]    => "en-US"
postDate[timezone]  => "America/Los_Angeles"

dateTimeField adds postDate[time], and emits the locale and timezone once rather than per control. Craft's DateTimeHelper normalises this shape for you, so prefer

$date = DateTimeHelper::toDateTime($request->getBodyParam('postDate'));

over reading [date] directly — parsing the localised string yourself will break for any user whose formatting locale isn't yours. Set outputLocaleParam: false or outputTzParam: false to suppress the hidden inputs when you're handling conversion another way.

Note

Mobile is different markup. Craft checks craft.app.request.isMobileBrowser and, on a match, renders a native <input type="date">/type="time" with an .empty-value class when unset — no datepicker JS, no calendar glyph. Don't target .datewrapper input[type=text] in plugin CSS or your styles will silently drop on touch devices.