Custom Styling
Customize the timepicker appearance using CSS custom properties and your own styles
Docs: CSS Variables · CSS Classes · Custom Styling
CSS Custom Properties
Match your site's design
#This picker uses the same paper, ink and type as these docs. One class passed through ui.cssClass sets the picker's custom properties.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-ink' } }).create();56// CSS:7.tp-ui-wrapper.tp-ink {8 --tp-font-family: 'Atkinson Hyperlegible Next', system-ui, sans-serif;9 --tp-shadow: none;10 --tp-border-radius: 2px;11 --tp-radius-sm: 2px;12 --tp-radius-md: 2px;13 --tp-radius-lg: 2px;1415 --tp-bg: #ffffff;16 --tp-surface: #f3f3f3;17 --tp-surface-hover: #e2e2e2;18 --tp-input-bg: #f6f6f6;19 --tp-text: #0d0d0d;20 --tp-text-secondary: #6b6b6b;21 --tp-text-type-time: #474747;22 --tp-primary: #0d0d0d;23 --tp-on-primary: #ffffff;24 --tp-primary-container: #0d0d0d;25 --tp-on-primary-container: #ffffff;26 --tp-am-pm-active: #0d0d0d;27 --tp-am-pm-text-selected: #ffffff;28 --tp-am-pm-text-unselected: #474747;29 --tp-border: #6b6b6b;30 --tp-outline: #6b6b6b;31 --tp-focus-ring: #0d0d0d;32}3334@media (prefers-color-scheme: dark) {35 .tp-ui-wrapper.tp-ink {36 --tp-bg: #111111;37 --tp-surface: #1a1a1a;38 --tp-surface-hover: #2b2b2b;39 --tp-input-bg: #181818;40 --tp-text: #ececec;41 --tp-text-secondary: #8c8c8c;42 --tp-text-type-time: #b8b8b8;43 --tp-primary: #ececec;44 --tp-on-primary: #111111;45 --tp-primary-container: #ececec;46 --tp-on-primary-container: #111111;47 --tp-am-pm-active: #ececec;48 --tp-am-pm-text-selected: #111111;49 --tp-am-pm-text-unselected: #b8b8b8;50 --tp-border: #8c8c8c;51 --tp-outline: #8c8c8c;52 --tp-focus-ring: #ececec;53 }54}
Inspired by popular design systems
#Each picker below gets one class through ui.cssClass, and the library adds it to .tp-ui-wrapper. A rule on .tp-ui-wrapper.<class> sets the colors, corners and type of a well-known design system, so you can copy the CSS and adapt it. The tp-ui-* class names are stable, so you can restyle any part of the picker, including its layout and shapes. The values approximate each system's public design tokens. These are not official themes.
Microsoft Fluent
The time fields look like Fluent inputs: the active one gets a 2px blue bottom stroke instead of a fill. OK is a filled primary button and Cancel an outlined secondary button.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-fluent' } }).create();56// CSS:7.tp-ui-wrapper.tp-fluent {8 --tp-font-family: 'Segoe UI Variable', 'Segoe UI', system-ui, sans-serif;9 --tp-shadow: 0 8px 16px rgba(0, 0, 0, 0.14), 0 0 2px rgba(0, 0, 0, 0.12);10 --tp-border-radius: 8px;11 --tp-radius-sm: 4px;12 --tp-radius-md: 4px;13 --tp-radius-lg: 8px;14 --tp-btn-radius: 4px;1516 --tp-bg: #ffffff;17 --tp-surface: #f5f5f5;18 --tp-surface-hover: #f5f5f5;19 --tp-input-bg: #fafafa;20 --tp-text: #242424;21 --tp-text-secondary: #616161;22 --tp-text-type-time: #424242;23 --tp-primary: #0f6cbd;24 --tp-on-primary: #ffffff;25 --tp-primary-container: #ebf3fc;26 --tp-on-primary-container: #115ea3;27 --tp-am-pm-active: #0f6cbd;28 --tp-am-pm-text-selected: #ffffff;29 --tp-am-pm-text-unselected: #616161;30 --tp-border: #d1d1d1;31 --tp-outline: #d1d1d1;32 --tp-focus-ring: #000000;3334 --tp-state-focus-opacity: 0;35 --tp-ripple-opacity: 0;36}3738.tp-ui-wrapper.tp-fluent .tp-ui-select-time {39 color: var(--tp-text);40 font-size: 14px;41}4243.tp-ui-wrapper.tp-fluent :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {44 border: 1px solid var(--tp-border);45 border-bottom-color: #616161;46 background-color: var(--tp-bg);47}4849.tp-ui-wrapper.tp-fluent :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {50 border-bottom-color: var(--tp-primary);51 box-shadow: inset 0 -1px 0 var(--tp-primary);52}5354.tp-ui-wrapper.tp-fluent .tp-ui-type-mode {55 font-size: 14px;56}5758.tp-ui-wrapper.tp-fluent .tp-ui-wrapper-btn {59 gap: 8px;60}6162.tp-ui-wrapper.tp-fluent :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {63 --tp-state-hover-opacity: 0;64 min-width: 96px;65 margin: 0;66 padding: 5px 12px;67 border: 1px solid var(--tp-border);68 line-height: 20px;69}7071.tp-ui-wrapper.tp-fluent .tp-ui-ok-btn {72 border-color: transparent;73 background-color: var(--tp-primary);74 color: var(--tp-on-primary);75}7677.tp-ui-wrapper.tp-fluent .tp-ui-ok-btn:hover {78 background-color: #115ea3;79}8081.tp-ui-wrapper.tp-fluent .tp-ui-cancel-btn {82 background-color: var(--tp-bg);83 color: var(--tp-text);84}8586.tp-ui-wrapper.tp-fluent .tp-ui-cancel-btn:hover {87 border-color: #c7c7c7;88 background-color: var(--tp-surface-hover);89}
IBM Carbon
Square corners on the panel, fields and buttons. The time fields are grey Carbon inputs with a bottom border, and a 2px blue border marks the active one. AM/PM is a content switcher, and Cancel and OK fill the footer edge to edge as 64px buttons with the label at the top left, like a Carbon modal.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-carbon' } }).create();56// CSS:7.tp-ui-wrapper.tp-carbon {8 --tp-font-family: 'IBM Plex Sans', system-ui, sans-serif;9 --tp-shadow: none;10 --tp-border-radius: 0;11 --tp-radius-sm: 0;12 --tp-radius-md: 0;13 --tp-radius-lg: 0;14 --tp-btn-radius: 0;15 --tp-size-inline-min-height: 0;1617 --tp-bg: #ffffff;18 --tp-surface: #f4f4f4;19 --tp-surface-hover: #e8e8e8;20 --tp-input-bg: #f4f4f4;21 --tp-text: #161616;22 --tp-text-secondary: #525252;23 --tp-text-type-time: #525252;24 --tp-primary: #0f62fe;25 --tp-on-primary: #ffffff;26 --tp-primary-container: #f4f4f4;27 --tp-on-primary-container: #161616;28 --tp-am-pm-active: #161616;29 --tp-am-pm-text-selected: #ffffff;30 --tp-am-pm-text-unselected: #525252;31 --tp-border: #8d8d8d;32 --tp-outline: #c6c6c6;33 --tp-focus-ring: #0f62fe;3435 --tp-state-focus-opacity: 0;36 --tp-ripple-opacity: 0;37}3839.tp-ui-wrapper.tp-carbon .tp-ui-input-ripple-wrapper {40 --tp-state-hover-opacity: 0;41 --tp-state-pressed-opacity: 0;42}4344.tp-ui-wrapper.tp-carbon :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {45 --tp-focus-ring: var(--tp-text);46 border: 0;47 box-shadow: inset 0 -1px 0 var(--tp-border);48 font-weight: 300;49}5051.tp-ui-wrapper.tp-carbon :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {52 box-shadow: inset 0 0 0 2px var(--tp-primary);53}5455.tp-ui-wrapper.tp-carbon .tp-ui-wrapper-type-time {56 border: 1px solid #161616;57 border-radius: 4px;58}5960.tp-ui-wrapper.tp-carbon .tp-ui-type-mode {61 --tp-state-hover-opacity: 0;62 --tp-outline-width-thick: 2px;63 --tp-shadow-focus-ring: inset 0 0 0 3px #ffffff;64 border: 0;65 font-size: 14px;66}6768.tp-ui-wrapper.tp-carbon .tp-ui-am {69 border-radius: 3px 3px 0 0;70}7172.tp-ui-wrapper.tp-carbon .tp-ui-pm {73 border-radius: 0 0 3px 3px;74}7576.tp-ui-wrapper.tp-carbon .tp-ui-type-mode:not(.active):hover {77 background-color: rgba(141, 141, 141, 0.12);78}7980.tp-ui-wrapper.tp-carbon .tp-ui-footer {81 margin: 24px 0 0;82 padding: 0;83}8485.tp-ui-wrapper.tp-carbon .tp-ui-wrapper-btn {86 width: 100%;87}8889.tp-ui-wrapper.tp-carbon :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {90 --tp-state-hover-opacity: 0;91 --tp-outline-width-thick: 2px;92 --tp-outline-offset: -2px;93 --tp-shadow-focus-ring: inset 0 0 0 3px #ffffff;94 flex: 0 1 50%;95 height: 64px;96 margin: 0;97 padding: 14px 64px 32px 16px;98 color: var(--tp-on-primary);99 font-weight: 400;100 line-height: 18px;101 text-align: left;102}103104.tp-ui-wrapper.tp-carbon .tp-ui-cancel-btn {105 background-color: #393939;106}107108.tp-ui-wrapper.tp-carbon .tp-ui-cancel-btn:hover {109 background-color: #474747;110}111112.tp-ui--inline .tp-ui-wrapper.tp-carbon .tp-ui-cancel-btn {113 margin-right: -8px;114}115116.tp-ui-wrapper.tp-carbon .tp-ui-ok-btn {117 background-color: var(--tp-primary);118}119120.tp-ui-wrapper.tp-carbon .tp-ui-ok-btn:hover {121 background-color: #0050e6;122}
Stripe
Time fields look like Stripe form inputs: a light border, and a blurple ring on the active field. AM/PM and OK are pills, and Cancel is a text link that darkens on hover.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-stripe' } }).create();56// CSS:7.tp-ui-wrapper.tp-stripe {8 --tp-font-family: system-ui, -apple-system, sans-serif;9 --tp-shadow: 0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3);10 --tp-border-radius: 12px;11 --tp-radius-sm: 8px;12 --tp-radius-md: 8px;13 --tp-radius-lg: 12px;14 --tp-btn-radius: 999px;1516 --tp-bg: #ffffff;17 --tp-surface: #f6f9fc;18 --tp-surface-hover: #e3e8ee;19 --tp-input-bg: #f6f9fc;20 --tp-text: #0a2540;21 --tp-text-secondary: #425466;22 --tp-text-type-time: #425466;23 --tp-primary: #635bff;24 --tp-on-primary: #ffffff;25 --tp-primary-container: #e9e8ff;26 --tp-on-primary-container: #2e2a8c;27 --tp-am-pm-active: #635bff;28 --tp-am-pm-text-selected: #ffffff;29 --tp-am-pm-text-unselected: #425466;30 --tp-border: #e3e8ee;31 --tp-outline: #e3e8ee;32 --tp-focus-ring: #635bff;3334 --tp-state-focus-opacity: 0;35 --tp-ripple-opacity: 0;36}3738.tp-ui-wrapper.tp-stripe .tp-ui-input-ripple-wrapper {39 border-radius: 8px;40}4142.tp-ui-wrapper.tp-stripe :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {43 border: 1px solid #e0e6eb;44 border-radius: 8px;45 color: var(--tp-text);46}4748.tp-ui-wrapper.tp-stripe :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {49 border-color: var(--tp-primary);50 background-color: var(--tp-bg);51 box-shadow: inset 0 0 0 1px var(--tp-primary);52 color: var(--tp-primary);53}5455.tp-ui-wrapper.tp-stripe .tp-ui-wrapper-type-time {56 gap: 6px;57}5859.tp-ui-wrapper.tp-stripe .tp-ui-type-mode {60 border: 1px solid var(--tp-border);61 border-radius: 999px;62 background-color: var(--tp-input-bg);63 font-size: 14px;64}6566.tp-ui-wrapper.tp-stripe .tp-ui-type-mode.active {67 border-color: var(--tp-am-pm-active);68 background-color: var(--tp-am-pm-active);69}7071.tp-ui-wrapper.tp-stripe .tp-ui-wrapper-btn {72 gap: 8px;73}7475.tp-ui-wrapper.tp-stripe :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {76 --tp-state-hover-opacity: 0;77 margin: 0;78 padding: 8px 18px;79 line-height: 20px;80}8182.tp-ui-wrapper.tp-stripe .tp-ui-ok-btn {83 background-color: var(--tp-primary);84 color: var(--tp-on-primary);85}8687.tp-ui-wrapper.tp-stripe .tp-ui-ok-btn:hover {88 background-color: var(--tp-text);89}9091.tp-ui-wrapper.tp-stripe .tp-ui-cancel-btn:hover {92 color: var(--tp-text);93}
GitHub
A compact picker with smaller digits and 14px text. OK and Cancel copy Primer's green primary button and grey default button.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-github' } }).create();56// CSS:7.tp-ui-wrapper.tp-github {8 --tp-font-family: -apple-system, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;9 --tp-shadow: 0 0 0 1px #d1d9e040, 0 6px 12px -3px #25292e0a, 0 6px 18px 0 #25292e1f;10 --tp-border-radius: 12px;11 --tp-radius-sm: 6px;12 --tp-radius-md: 6px;13 --tp-radius-lg: 12px;14 --tp-btn-radius: 6px;1516 --tp-bg: #ffffff;17 --tp-surface: #f6f8fa;18 --tp-surface-hover: #818b981f;19 --tp-input-bg: #f6f8fa;20 --tp-text: #1f2328;21 --tp-text-secondary: #59636e;22 --tp-text-type-time: #59636e;23 --tp-primary: #0969da;24 --tp-on-primary: #ffffff;25 --tp-primary-container: #ddf4ff;26 --tp-on-primary-container: #0550ae;27 --tp-am-pm-active: #0969da;28 --tp-am-pm-text-selected: #ffffff;29 --tp-am-pm-text-unselected: #59636e;30 --tp-border: #d1d9e0;31 --tp-outline: #d1d9e0;32 --tp-focus-ring: #0969da;3334 --tp-state-focus-opacity: 0;35 --tp-ripple-opacity: 0;36}3738.tp-ui-wrapper.tp-github :is(.tp-ui-select-time, .tp-ui-type-mode) {39 font-size: 14px;40}4142.tp-ui-wrapper.tp-github .tp-ui-input-ripple-wrapper {43 height: 64px;44}4546.tp-ui-wrapper.tp-github :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {47 height: 64px;48 border: 1px solid var(--tp-border);49 font-size: 40px;50}5152.tp-ui-wrapper.tp-github :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {53 border-color: var(--tp-primary);54}5556.tp-ui-wrapper.tp-github .tp-ui-wrapper-btn {57 gap: 8px;58}5960.tp-ui-wrapper.tp-github :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {61 --tp-state-hover-opacity: 0;62 margin: 0;63 padding: 5px 16px;64 border: 1px solid var(--tp-border);65 line-height: 20px;66}6768.tp-ui-wrapper.tp-github .tp-ui-ok-btn {69 border-color: #1f232826;70 background-color: #1f883d;71 color: #ffffff;72}7374.tp-ui-wrapper.tp-github .tp-ui-ok-btn:hover {75 background-color: #1c8139;76}7778.tp-ui-wrapper.tp-github .tp-ui-cancel-btn {79 background-color: #f6f8fa;80 color: #25292e;81}8283.tp-ui-wrapper.tp-github .tp-ui-cancel-btn:hover {84 background-color: #eff2f5;85}
Linear
Linear's Nordic Gray and Mercury White with an indigo accent, set on top of the built-in dark theme. The label and digits are smaller, 1px borders outline the fields and the clock face, and OK is a filled indigo button.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { theme: 'dark', cssClass: 'tp-linear' } }).create();56// CSS:7.tp-ui-wrapper.tp-linear {8 --tp-font-family: system-ui, -apple-system, sans-serif;9 --tp-shadow: 0 0 0 1px #323338, 0 8px 24px rgba(0, 0, 0, 0.5);10 --tp-border-radius: 12px;11 --tp-radius-sm: 6px;12 --tp-radius-md: 6px;13 --tp-radius-lg: 12px;14 --tp-btn-radius: 6px;1516 --tp-bg: #222326;17 --tp-surface: #1b1c1f;18 --tp-surface-hover: #323338;19 --tp-input-bg: #2a2b2f;20 --tp-text: #f4f5f8;21 --tp-text-secondary: #8a8f98;22 --tp-text-type-time: #8a8f98;23 --tp-primary: #5e6ad2;24 --tp-on-primary: #ffffff;25 --tp-primary-container: #2f3250;26 --tp-on-primary-container: #c5c9f5;27 --tp-am-pm-active: #5e6ad2;28 --tp-am-pm-text-selected: #ffffff;29 --tp-am-pm-text-unselected: #8a8f98;30 --tp-border: #323338;31 --tp-outline: #323338;32 --tp-focus-ring: #5e6ad2;33 --tp-btn-text: #7e88db;3435 --tp-state-focus-opacity: 0;36 --tp-ripple-opacity: 0;37}3839.tp-ui-wrapper.tp-linear .tp-ui-select-time {40 font-size: 11px;41}4243.tp-ui-wrapper.tp-linear .tp-ui-input-ripple-wrapper {44 height: 72px;45}4647.tp-ui-wrapper.tp-linear :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {48 height: 72px;49 border: 1px solid var(--tp-border);50 font-size: 44px;51}5253.tp-ui-wrapper.tp-linear :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {54 border-color: var(--tp-primary);55}5657.tp-ui-wrapper.tp-linear .tp-ui-type-mode {58 --tp-state-hover-opacity: 0;59 font-size: 13px;60}6162.tp-ui-wrapper.tp-linear .tp-ui-type-mode:not(.active):hover {63 background-color: #2a2b2f;64 color: var(--tp-text);65}6667.tp-ui-wrapper.tp-linear .tp-ui-clock-face {68 box-shadow: inset 0 0 0 1px var(--tp-border);69}7071.tp-ui-wrapper.tp-linear .tp-ui-wrapper-btn {72 gap: 8px;73}7475.tp-ui-wrapper.tp-linear :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {76 --tp-state-hover-opacity: 0;77 margin: 0;78 padding: 6px 14px;79 font-size: 13px;80 line-height: 20px;81}8283.tp-ui-wrapper.tp-linear .tp-ui-ok-btn {84 background-color: var(--tp-primary);85 color: var(--tp-on-primary);86}8788.tp-ui-wrapper.tp-linear .tp-ui-cancel-btn {89 color: var(--tp-text-secondary);90}9192.tp-ui-wrapper.tp-linear .tp-ui-cancel-btn:hover {93 background-color: #2a2b2f;94 color: var(--tp-text);95}
Restyle the whole picker
#Each example starts from a request and changes layout, shapes and proportions with plain CSS on the tp-ui-* classes, scoped to one class from ui.cssClass.
A desk clock
Someone wants the picker to look like the clock on their desk. The label is hidden visually but stays available to screen readers, the time fields lose their boxes, and the black dial gets one yellow hand. OK reads Set through labels.ok and is a round button.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-desk' }, labels: { ok: 'Set' } }).create();56// CSS:7.tp-ui-wrapper.tp-desk {8 --tp-font-family: system-ui, -apple-system, sans-serif;9 --tp-shadow: 0 1px 2px rgba(27, 27, 27, 0.08), 0 12px 32px rgba(27, 27, 27, 0.12);10 --tp-border-radius: 16px;11 --tp-radius-md: 16px;12 --tp-size-input: 72px;13 --tp-size-inline-min-height: 0;14 min-width: 312px;1516 --tp-bg: #f3efe6;17 --tp-surface: #161616;18 --tp-text: #1b1b1b;19 --tp-text-secondary: #5c5649;20 --tp-primary: #f2b705;21 --tp-on-primary: #161616;22 --tp-on-primary-container: #1b1b1b;23 --tp-am-pm-active: #1b1b1b;24 --tp-am-pm-text-selected: #f3efe6;25 --tp-am-pm-text-unselected: #5c5649;26 --tp-focus-ring: #1b1b1b;2728 --tp-state-focus-opacity: 0;29 --tp-ripple-opacity: 0;30}3132.tp-ui-wrapper.tp-desk .tp-ui-select-time {33 position: absolute;34 width: 1px;35 height: 1px;36 padding: 0;37 margin: -1px;38 overflow: hidden;39 clip-path: inset(50%);40 white-space: nowrap;41 border: 0;42}4344.tp-ui-wrapper.tp-desk .tp-ui-header {45 padding-top: 24px;46}4748.tp-ui-wrapper.tp-desk :is(.tp-ui-input-wrapper, .tp-ui-input-ripple-wrapper) {49 border-radius: 0;50}5152.tp-ui-wrapper.tp-desk .tp-ui-input-ripple-wrapper {53 --tp-state-hover-opacity: 0;54 --tp-state-pressed-opacity: 0;55 height: 64px;56}5758.tp-ui-wrapper.tp-desk :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {59 height: 64px;60 padding: 0;61 border: 0;62 border-radius: 0;63 background-color: transparent;64 color: #6b655a;65 font-size: 44px;66}6768.tp-ui-wrapper.tp-desk :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds):hover {69 color: var(--tp-text);70}7172.tp-ui-wrapper.tp-desk :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {73 color: var(--tp-text);74 box-shadow: inset 0 -2px 0 var(--tp-text);75}7677.tp-ui-wrapper.tp-desk .tp-ui-wrapper-type-time {78 justify-content: center;79 gap: 4px;80}8182.tp-ui-wrapper.tp-desk .tp-ui-type-mode {83 width: auto;84 height: auto;85 padding: 4px 10px;86 border: 0;87 border-radius: 999px;88 font-size: 13px;89}9091.tp-ui-wrapper.tp-desk .tp-ui-clock-face {92 --tp-text: #f3efe6;93 --tp-focus-ring: #f2b705;94}9596.tp-ui-wrapper.tp-desk .tp-ui-footer {97 justify-content: flex-end;98 padding: 16px 24px 24px;99}100101.tp-ui-wrapper.tp-desk .tp-ui-wrapper-btn {102 gap: 8px;103}104105.tp-ui-wrapper.tp-desk :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {106 --tp-state-hover-opacity: 0;107 margin: 0;108}109110.tp-ui-wrapper.tp-desk .tp-ui-ok-btn {111 display: flex;112 align-items: center;113 justify-content: center;114 width: 56px;115 height: 56px;116 padding: 0;117 border-radius: 50%;118 background-color: var(--tp-text);119 color: var(--tp-bg);120}121122.tp-ui-wrapper.tp-desk .tp-ui-ok-btn:hover {123 background-color: #3a3a3a;124}125126.tp-ui-wrapper.tp-desk .tp-ui-cancel-btn {127 color: var(--tp-text-secondary);128}129130.tp-ui-wrapper.tp-desk .tp-ui-cancel-btn:hover {131 color: var(--tp-text);132}
A loud brand
For a bold brand, the picker gets thick black borders and hard offset shadows for a neubrutalist look. The active field and OK are yellow, the active AM/PM is pink, and the buttons press in when you click them.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-loud' } }).create();56// CSS:7.tp-ui-wrapper.tp-loud {8 --tp-font-family: system-ui, -apple-system, sans-serif;9 --tp-shadow: none;10 --tp-border-radius: 8px;11 --tp-radius-md: 8px;12 --tp-btn-radius: 8px;1314 --tp-bg: #fff8e7;15 --tp-surface: #ffffff;16 --tp-surface-hover: #fff1c2;17 --tp-input-bg: #ffffff;18 --tp-text: #111111;19 --tp-text-secondary: #111111;20 --tp-primary: #111111;21 --tp-on-primary: #111111;22 --tp-primary-container: #ffd23f;23 --tp-on-primary-container: #111111;24 --tp-am-pm-active: #ff7aa8;25 --tp-am-pm-text-selected: #111111;26 --tp-am-pm-text-unselected: #111111;27 --tp-border: #111111;28 --tp-focus-ring: #111111;2930 --tp-state-hover-opacity: 0;31 --tp-state-focus-opacity: 0;32 --tp-ripple-opacity: 0;3334 border: 2px solid var(--tp-border);35 filter: drop-shadow(6px 6px 0 var(--tp-border));36}3738.tp-ui-wrapper.tp-loud :is(.tp-ui-input-wrapper, .tp-ui-input-ripple-wrapper) {39 overflow: visible;40}4142.tp-ui-wrapper.tp-loud :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {43 border: 2px solid var(--tp-border);44}4546.tp-ui-wrapper.tp-loud :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {47 box-shadow: 4px 4px 0 var(--tp-border);48}4950.tp-ui-wrapper.tp-loud .tp-ui-wrapper-type-time {51 gap: 6px;52}5354.tp-ui-wrapper.tp-loud .tp-ui-type-mode {55 border: 2px solid var(--tp-border);56 border-radius: 8px;57 background-color: var(--tp-input-bg);58 font-size: 14px;59}6061.tp-ui-wrapper.tp-loud .tp-ui-type-mode:not(.active):hover {62 background-color: var(--tp-surface-hover);63}6465.tp-ui-wrapper.tp-loud .tp-ui-type-mode.active {66 background-color: var(--tp-am-pm-active);67 box-shadow: 3px 3px 0 var(--tp-border);68}6970.tp-ui-wrapper.tp-loud :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds):focus-visible,71.tp-ui-wrapper.tp-loud :is(.tp-ui-type-mode, .tp-ui-clock-face):focus-visible {72 outline-offset: -7px;73}7475.tp-ui-wrapper.tp-loud .tp-ui-clock-face {76 box-shadow: inset 0 0 0 2px var(--tp-border);77}7879.tp-ui-wrapper.tp-loud .tp-ui-circle-hand {80 border: 2px solid var(--tp-border);81 background-color: var(--tp-primary-container);82}8384.tp-ui-wrapper.tp-loud .tp-ui-footer {85 justify-content: flex-end;86}8788.tp-ui-wrapper.tp-loud .tp-ui-wrapper-btn {89 gap: 8px;90}9192.tp-ui-wrapper.tp-loud :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {93 margin: 0;94 padding: 8px 18px;95 border: 2px solid var(--tp-border);96 box-shadow: 3px 3px 0 var(--tp-border);97 color: var(--tp-text);98 line-height: 20px;99}100101.tp-ui-wrapper.tp-loud .tp-ui-ok-btn {102 background-color: var(--tp-primary-container);103}104105.tp-ui-wrapper.tp-loud .tp-ui-cancel-btn {106 background-color: #ffffff;107}108109.tp-ui-wrapper.tp-loud :is(.tp-ui-ok-btn, .tp-ui-cancel-btn):hover {110 box-shadow: 4px 4px 0 var(--tp-border);111 transform: translate(-1px, -1px);112}113114.tp-ui-wrapper.tp-loud :is(.tp-ui-ok-btn, .tp-ui-cancel-btn):active {115 box-shadow: none;116 transform: translate(3px, 3px);117}118119@media (prefers-reduced-motion: no-preference) {120 .tp-ui-wrapper.tp-loud :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {121 transition:122 transform 100ms ease,123 box-shadow 100ms ease;124 }125}
Just the time
When only the time matters, the label is hidden visually and the fields have no boxes, just large light digits with an underline on the active one. AM/PM is small text, the clock face is a thin ring, and the buttons span the full width.
1import { TimepickerUI } from 'timepicker-ui';23const input = document.querySelector('#timepicker');4new TimepickerUI(input, { ui: { cssClass: 'tp-bare' } }).create();56// CSS:7.tp-ui-wrapper.tp-bare {8 --tp-font-family: system-ui, -apple-system, sans-serif;9 --tp-shadow: 0 0 0 1px #e5e5e5, 0 8px 24px rgba(0, 0, 0, 0.06);10 --tp-border-radius: 12px;11 --tp-radius-md: 12px;12 --tp-size-inline-min-height: 0;1314 --tp-bg: #ffffff;15 --tp-surface: transparent;16 --tp-text: #111111;17 --tp-text-secondary: #6b6b6b;18 --tp-primary: #c2410c;19 --tp-on-primary: #ffffff;20 --tp-on-primary-container: #111111;21 --tp-am-pm-active: transparent;22 --tp-am-pm-text-selected: #c2410c;23 --tp-am-pm-text-unselected: #6b6b6b;24 --tp-border: #d4d4d4;25 --tp-focus-ring: #c2410c;2627 --tp-state-focus-opacity: 0;28 --tp-ripple-opacity: 0;29}3031.tp-ui-wrapper.tp-bare .tp-ui-select-time {32 position: absolute;33 width: 1px;34 height: 1px;35 padding: 0;36 margin: -1px;37 overflow: hidden;38 clip-path: inset(50%);39 white-space: nowrap;40 border: 0;41}4243.tp-ui-wrapper.tp-bare .tp-ui-header {44 padding-top: 24px;45}4647.tp-ui-wrapper.tp-bare :is(.tp-ui-input-wrapper, .tp-ui-input-ripple-wrapper) {48 border-radius: 0;49}5051.tp-ui-wrapper.tp-bare .tp-ui-input-ripple-wrapper {52 --tp-state-hover-opacity: 0;53 --tp-state-pressed-opacity: 0;54}5556.tp-ui-wrapper.tp-bare :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds) {57 padding: 0;58 border: 0;59 border-radius: 0;60 background-color: transparent;61 color: var(--tp-text-secondary);62 font-size: 64px;63 font-weight: 300;64}6566.tp-ui-wrapper.tp-bare :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds):hover {67 color: var(--tp-text);68}6970.tp-ui-wrapper.tp-bare :is(.tp-ui-hour, .tp-ui-minutes, .tp-ui-seconds).active {71 color: var(--tp-text);72 box-shadow: inset 0 -2px 0 var(--tp-primary);73}7475.tp-ui-wrapper.tp-bare .tp-ui-wrapper-type-time {76 flex-direction: column;77 justify-content: center;78 gap: 4px;79}8081.tp-ui-wrapper.tp-bare .tp-ui-type-mode {82 --tp-state-hover-opacity: 0;83 width: auto;84 height: auto;85 padding: 6px 8px;86 border: 0;87 border-radius: 4px;88 font-size: 14px;89}9091.tp-ui-wrapper.tp-bare .tp-ui-type-mode:not(.active):hover {92 color: var(--tp-text);93}9495.tp-ui-wrapper.tp-bare .tp-ui-type-mode.active {96 text-decoration-line: underline;97 text-decoration-thickness: 2px;98 text-underline-offset: 4px;99}100101.tp-ui-wrapper.tp-bare .tp-ui-clock-face {102 --tp-text: var(--tp-text-secondary);103 box-shadow: inset 0 0 0 1px var(--tp-border);104}105106.tp-ui-wrapper.tp-bare .tp-ui-clock-hand {107 left: calc(50% - 0.5px);108 width: 1px;109}110111.tp-ui-wrapper.tp-bare .tp-ui-footer {112 margin: 24px 0 0;113 padding: 0;114 border-top: 1px solid #e5e5e5;115}116117.tp-ui-wrapper.tp-bare .tp-ui-wrapper-btn {118 width: 100%;119}120121.tp-ui-wrapper.tp-bare :is(.tp-ui-ok-btn, .tp-ui-cancel-btn) {122 --tp-state-hover-opacity: 0;123 --tp-outline-offset: -3px;124 flex: 1;125 height: 48px;126 margin: 0;127 padding: 0 16px;128 border-radius: 0;129 line-height: 48px;130}131132.tp-ui-wrapper.tp-bare .tp-ui-cancel-btn {133 border-bottom-left-radius: 12px;134 color: var(--tp-text);135}136137.tp-ui-wrapper.tp-bare .tp-ui-ok-btn {138 border-bottom-right-radius: 12px;139 color: var(--tp-primary);140}141142.tp-ui-wrapper.tp-bare :is(.tp-ui-ok-btn, .tp-ui-cancel-btn):hover {143 background-color: #f5f5f5;144}