CSS Styled Date Input

Custom styling for <input type="date"> including the calendar icon via CSS pseudo-elements.

Published May 27, 2026 Beginner 8 min read

Styling <input type="date"> in CSS works on the outside of the control and stops at the calendar. appearance: none clears the native box so border, background, radius and padding all apply, and the field then looks like every other input on the form. The date segments and the little calendar button inside it are a different matter, because they live in the browser's shadow DOM.

WebKit exposes those internals through a set of prefixed pseudo-elements, of which ::-webkit-calendar-picker-indicator is the one worth knowing: it is the calendar button, and on a dark field it needs filter: invert(1) to be visible at all. Firefox exposes nothing equivalent. Two more versions follow: one that replaces the button with an icon that looks the same in every browser, and one that marks a date outside an allowed window.

Two styled date fields

HTML

<div class="date-wrap">
  <div class="date-field">
    <label>Default styling</label>
    <input type="date">
  </div>
  <div class="date-field">
    <label>Accent border</label>
    <input type="date" class="date-accent">
  </div>
</div>

CSS

.date-wrap {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  max-width: 320px;
  margin: 0 auto;
}

.date-field {
  display: flex;
  flex-direction: column;
  gap: .3rem;
}

.date-field label {
  font-size: .82rem;
  font-weight: 500;
  color: #88888f;
}

input[type="date"] {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  padding: .7rem 1rem;
  background: #1c1c1e;
  border: 2px solid #2a2a2d;
  border-radius: 8px;
  color: #f0f0f0;
  font-family: system-ui, sans-serif;
  font-size: .95rem;
  outline: none;
  cursor: pointer;
  transition: border-color .2s;
}

input[type="date"]:hover { border-color: #88888f; }
input[type="date"]:focus { border-color: #b8ff57; }

/* The calendar button: invert it so it reads on a dark field */
input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) opacity(.5);
  cursor: pointer;
}

input[type="date"]::-webkit-calendar-picker-indicator:hover {
  filter: invert(1) opacity(1);
}

input[type="date"]::-webkit-datetime-edit {
  color: #f0f0f0;
}

input[type="date"]::-webkit-datetime-edit-fields-wrapper {
  padding: 0;
}

input[type="date"]::-webkit-inner-spin-button {
  display: none;
}

input[type="date"].date-accent {
  border-color: #b8ff57;
}

Other ways to build it

One icon that looks the same in every browser

The native calendar button is drawn differently by each engine and Firefox will not let you touch its one at all. Stretching ::-webkit-calendar-picker-indicator across the whole control at zero opacity turns the entire field into the button in Chrome, Edge and Safari, and a background-image on the input supplies an icon that renders identically everywhere. Firefox ignores the first rule and keeps its own click target, so both engines end up with a working picker and the same icon.

HTML

<div class="date-wrap">
  <div class="date-field">
    <label for="full">Start date</label>
    <input type="date" id="full" class="date-full" value="2026-09-14">
  </div>
</div>

CSS

.date-full {
  position: relative;
  padding-right: 2.75rem;
  /* the # in a color has to be written %23 inside url() */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2388888f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='18' rx='2'/%3E%3Cline x1='16' y1='2' x2='16' y2='6'/%3E%3Cline x1='8' y1='2' x2='8' y2='6'/%3E%3Cline x1='3' y1='10' x2='21' y2='10'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
}

/* stretched over the whole field, so any click opens the picker */
.date-full::-webkit-calendar-picker-indicator {
  position: absolute;
  inset: 0;
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
  filter: none;
  opacity: 0;
  cursor: pointer;
}

A date outside the allowed window

min and max take ISO dates, and once either is present the field answers to :in-range and :out-of-range. That gives a date already outside the window somewhere to say so, rather than only failing quietly on submit. The two hints are both written into the markup and the stylesheet chooses between them, which is the same approach used for a numeric range on input validation. This field starts in October against a September window.

Any day in September

We only deliver during September

HTML

<div class="date-field date-window">
  <label for="delivery">Delivery date</label>
  <input type="date" id="delivery"
         min="2026-09-01" max="2026-09-30" value="2026-10-15">
  <p class="date-hint date-hint-default">Any day in September</p>
  <p class="date-hint date-hint-out">We only deliver during September</p>
</div>

CSS

.date-hint-out {
  display: none;
}

.date-window input:out-of-range {
  border-color: #ff9040;
}

.date-window input:out-of-range ~ .date-hint-default {
  display: none;
}

.date-window input:out-of-range ~ .date-hint-out {
  display: block;
  color: #ff9040;
}

.date-window input:in-range ~ .date-hint-default {
  color: #57d9a3;
}

How it works

Date inputs take appearance: none to remove the browser defaults. The ::-webkit-calendar-picker-indicator pseudo-element is the calendar button, and filter: invert(1) is what makes it visible on a dark background. ::-webkit-datetime-edit covers the text portion. Support for these internals varies: they are WebKit and Blink only, and Firefox offers no equivalent.

The outer styling is ordinary. Once appearance: none has run, the input takes padding, border, border-radius, background and font-size exactly like a text field, and it can sit in the same rule as the rest of the form. Nothing about the outside of a date input is special, which is why the base demo styles it with the same values used on floating labels and input validation.

Inside is where it diverges. The control is built from a row of editable segments for day, month and year, plus a button that opens the picker, and all of it is shadow DOM the page cannot reach through normal selectors. WebKit and Blink expose a handful of prefixed hooks: ::-webkit-datetime-edit for the whole text area, ::-webkit-datetime-edit-fields-wrapper for its box, and ::-webkit-calendar-picker-indicator for the button. Firefox has no counterpart for any of them.

The calendar button is drawn as a dark icon, so on a dark background it is close to invisible. filter: invert(1) flips it to white, and chaining opacity() into the same filter dims it to sit alongside the placeholder text rather than shouting. Chaining is what lets both run: writing a separate opacity property would work too, but keeping them in one filter means the hover rule only has to change one declaration.

The order of the date segments is not yours to choose. The browser uses the user's locale, so the same markup renders as day then month then year for one reader and month then day then year for another. That is correct behavior and should not be fought. It does mean any fixed-width layout has to survive both, and that a hint saying DD/MM/YYYY may be wrong for half the people reading it.

The picker panel itself is completely off limits. It is drawn by the browser outside the page, in the same way the popup of a styled select is, and no stylesheet reaches it. A design that requires a themed calendar needs a component built from ordinary elements, along with all the keyboard handling and date arithmetic that entails.

CSS properties used

appearance
none clears the native field so border, background and padding apply. Only affects the outside of the control.
::-webkit-calendar-picker-indicator
The calendar button. WebKit and Blink only. Can be recoloured, moved, or stretched to cover the whole field.
::-webkit-datetime-edit
The editable date text as a whole. Useful for setting the color of the segments on a dark field.
filter
invert(1) flips the dark calendar icon to white. Chaining opacity() in the same value dims it at the same time.
:in-range / :out-of-range
Work on date inputs carrying min or max, which is how a date outside a booking window can be marked.
background-image
Carries a replacement calendar icon that renders identically in every browser, unlike the native button.

Browser support

FeatureChromeFirefoxSafariEdge
date and time inputs25NoNo13
appearance838015.483
filter1835679
:in-range / :out-of-range535010.179
data URIs423.179

The first row needs reading carefully. That Can I Use entry covers the whole family of date and time input types, date, time, datetime-local, month and week together, and it only counts a browser as supported when every one of them is implemented. Firefox and Safari both render a perfectly good type="date" field; they read as No because they do not implement the entire group. The ::-webkit- pseudo-elements have no Can I Use entry at all, since they are not standard: they work in Chrome, Edge, Safari and other WebKit or Blink browsers, and do nothing in Firefox.

Accessibility notes

The labels in the first demo have no for attribute and do not wrap their input, which means they label nothing. A screen reader announces each field with no name, and clicking the label does not focus it. Both variants below give the input an id and point the label at it, which is the whole fix.

A native date input is keyboard operable in a way a custom calendar rarely is. Each segment takes typed digits, the up and down arrows step the value, left and right move between segments, and the picker opens with the space bar or the down arrow. All of that survives appearance: none, which is the strongest reason to style the native control rather than replace it.

The base rule sets outline: none and shows focus as a border color change that hover also uses. This page's stylesheet adds a :focus-visible ring with an offset, so keyboard focus is distinguishable from a pointer hovering over the field. Segment level focus inside the control is drawn by the browser and is not something CSS controls.

filter: invert(1) opacity(.5) on the calendar button leaves it at half strength, which is fine against the field's own background but worth checking against the 3 to 1 contrast ratio WCAG asks of interface components. The hover rule raises it to full opacity, and a pointer is not the only way people find that button, so consider whether half strength is dim enough to be a problem at rest.

Never rely on the placeholder format alone to explain the expected order. The segment order follows the reader's locale, so a hint reading DD/MM/YYYY is wrong for anyone whose browser renders month first. Use min and max to constrain what can be entered, and let the control display the date in whatever order the reader expects.

What you can build with it

  • Booking and reservation forms. min and max fence off the dates that can be picked, and :out-of-range explains a rejected one.
  • Date of birth fields. A max of today prevents future dates without any script checking the value.
  • Report and analytics filters. Two date fields for a start and an end, styled to match the rest of the toolbar.
  • Deadline and due date pickers. A min of today, so nothing can be scheduled in the past.
  • Event submission forms. Paired with type="time", which takes the same outer styling and the same prefixed internals.

Mistakes worth avoiding

  • Expecting to style the calendar popup. It is drawn by the browser outside the page and no selector reaches it, the same limitation a styled select has.
  • Leaving the calendar button uninverted on a dark field. The icon is dark by default, so it disappears into the background and the field looks like it has no picker.
  • Assuming a fixed segment order. The browser follows the reader's locale, so a layout sized around DD/MM/YYYY can break for a reader seeing MM/DD/YYYY.
  • Writing a label with no for attribute. The field is announced as unnamed, and clicking the label does nothing.
  • Relying on ::-webkit- pseudo-elements for anything essential. Firefox ignores them entirely, so whatever they achieve has to be optional rather than load bearing.

Frequently asked questions

Why is the calendar icon invisible on my dark date input?
The icon the browser draws is dark, and on a dark field it disappears. input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1) } flips it to white. Firefox draws its own icon and ignores that rule, which is why the first variant on this page replaces the icon with a background image instead.
Can I style the calendar popup of a date input?
No. The picker panel is rendered by the browser outside the document, so no stylesheet reaches it. If a themed calendar is a requirement, it has to be built from ordinary elements, along with the keyboard handling and date logic that come with it.
How do I change the date format shown in the input?
You cannot, and it is better not to. The display order comes from the reader's locale while the value is always ISO format underneath. The value attribute and anything submitted are YYYY-MM-DD regardless of what is shown.
How do I limit a date input to a range of dates?
Set min and max in ISO format. The browser grays out dates outside the window in the picker and blocks submission of one typed in by hand, and CSS can style the field with :out-of-range while an invalid date is showing.
Can clicking anywhere in the field open the date picker?
In Chrome, Edge and Safari, yes. Give ::-webkit-calendar-picker-indicator position: absolute with inset: 0 and zero opacity so it covers the whole control, then draw your own icon as a background image. Firefox already opens its picker from a click on its own icon.