CSS Styled Date Input
Custom styling for <input type="date"> including the calendar icon via CSS pseudo-elements.
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
appearancenoneclears 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.
filterinvert(1)flips the dark calendar icon to white. Chainingopacity()in the same value dims it at the same time.:in-range / :out-of-range- Work on date inputs carrying
minormax, 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
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
date and time inputs | 25 | No | No | 13 |
appearance | 83 | 80 | 15.4 | 83 |
filter | 18 | 35 | 6 | 79 |
:in-range / :out-of-range | 53 | 50 | 10.1 | 79 |
data URIs | 4 | 2 | 3.1 | 79 |
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.
minandmaxfence off the dates that can be picked, and:out-of-rangeexplains a rejected one. - Date of birth fields. A
maxof 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
minof 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
forattribute. 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?
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?
How do I change the date format shown in the input?
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?
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?
::-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.