Date Picker Custom Styling
Restyle @pglejzer/datepicker-ui, the companion of timepicker-ui, with --dp-ui-* CSS variables, dp-ui__* classes and ui.cssClass.
How styling works
#The picker is plain CSS. Colors, sizes, corners and type come from --dp-ui-* custom properties, and every part has a dp-ui__* class. ui.cssClass adds your own class to the surface root, so you can set variables or write rules for one picker.
| Layer | Examples | Use it to |
|---|---|---|
| System tokens | --dp-ui-sys-color-*, --dp-ui-sys-shape-corner-*, --dp-ui-sys-typescale-* | Recolor, reshape or retype everything that reads them. |
| Component tokens | --dp-ui-container-*, --dp-ui-date-*, --dp-ui-text-field-*, --dp-ui-focus-ring-* | Change one part, or one size, without touching the rest. |
| Classes | .dp-ui__date-indicator, .dp-ui__text-button--confirm | Set properties that have no token, such as a cell radius. |
The system tokens follow the Material Design 3 roles and are set with light-dark(). Component tokens derive from them, so changing --dp-ui-sys-color-primary also recolors the selected day, the today outline, the focus ring and the text buttons.
.guide {--dp-ui-sys-color-primary: light-dark(#0b6b3a, #7fdba0);--dp-ui-sys-color-on-primary: light-dark(#ffffff, #00391c);--dp-ui-container-shape-docked: 8px;}
Tokens or classes
#Start with tokens. They survive library updates better than selectors, and they follow light and dark for free when you write them with light-dark(). A component token set on your class wins over the value derived from a system token:
.brand {--dp-ui-sys-color-primary: #a04000;--dp-ui-sys-color-on-primary: #ffffff;}.brand {--dp-ui-date-selected-container-color: #7a2e00;}
Use a class rule only for a property with no token. Write the selector with your class first, so it is more specific than the library rule:
.brand .dp-ui__date-indicator {border-radius: 12px;}.brand .dp-ui__text-button--confirm {text-transform: uppercase;}
The full lists are on the CSS Variables and CSS Classes pages.
Where to set a variable
#The picker declares every token again on the host, on the modal <dialog> and on each docked or inline container. A value set higher up is replaced by the default on those elements:
:root,.dp-ui {--dp-ui-sys-color-primary: #a04000;}
Set tokens on the cssClass element
ui.cssClass. The picker adds it to the surface root, which is the .dp-ui__container element for a docked or inline picker and the <dialog> for a modal.Scope one picker
#A class only reaches the pickers that receive it. Give each picker its own name and the rules stay apart:
new DatepickerUI("#start", { ui: { cssClass: "brand" } }).create();new DatepickerUI("#end", { ui: { cssClass: "plain" } }).create();
To limit a class to one part of the page, put an ancestor selector in front of it:
.checkout :is(.dp-ui__container, .dp-ui__dialog).brand {--dp-ui-sys-color-primary: #a04000;}
This needs the picker inside that ancestor. A docked popover stays where the host is in the DOM. The modal dialog is appended to the host unless you set ui.appendTo, so with appendTo: document.body an ancestor selector no longer matches and the class alone has to carry the rules.
Docked popover and modal dialog
#A docked or inline picker is a .dp-ui__container. A modal is a <dialog class="dp-ui__dialog">, and the container sits inside it. ui.cssClass lands on whichever one is the root. Name both in the selector, so one rule works in every display mode:
:is(.dp-ui__container, .dp-ui__dialog).brand {--dp-ui-sys-color-primary: #a04000;--dp-ui-sys-color-on-primary: #ffffff;}
displayMode auto
ui.displayMode: "auto" the same picker is a modal on a narrow or short screen and docked on a wide one. A rule for only .dp-ui__container would style one and miss the other.Inside a CSS Module, a bare :global(.brand) fails the purity check. Keep one local class in the selector, for example .scope :global(:is(.dp-ui__container, .dp-ui__dialog).brand).
Dark mode and colorScheme
#The system colors use light-dark(), which reads the color-scheme of the element. The picker sets it from ui.colorScheme, and "auto" detects it when the picker opens. Write each color as a pair and your style follows the scheme:
.brand {--dp-ui-sys-color-primary: light-dark(#a04000, #ffb68a);--dp-ui-sys-color-on-primary: light-dark(#ffffff, #4f2300);}
If your design has one fixed look, force the scheme and use plain values. The scheme then also decides the native parts of the page, such as scrollbars:
new DatepickerUI("#date", {ui: { cssClass: "brand", colorScheme: "dark" },}).create();
See Color scheme for how auto picks.
Start from a built-in theme or from main.css
#main.css holds the core and the basic theme. It is the smallest base and the best choice for a fully custom look:
import DatepickerUI from "@pglejzer/datepicker-ui";import "@pglejzer/datepicker-ui/main.css";import "./brand.css";new DatepickerUI("#date", { ui: { cssClass: "brand" } }).create();
To adjust a built-in theme instead, load its file and add your class with ui.cssClass. A theme sets its values on [data-theme], which is more specific than a bare class. Add [data-theme] to your selector so your values win:
:is(.dp-ui__container, .dp-ui__dialog).brand[data-theme] {--dp-ui-sys-color-primary: #a04000;--dp-ui-container-shape-docked: 8px;}
import DatepickerUI from "@pglejzer/datepicker-ui";import "@pglejzer/datepicker-ui/theme-crane.css";import "@pglejzer/datepicker-ui/main.css";import "./brand.css";new DatepickerUI("#date", {ui: { theme: "crane", cssClass: "brand" },}).create();
Only basic and blueprint follow ui.colorScheme. The other themes keep their own scheme, so a light-dark() pair will resolve to one side. See the Themes page for the list.