Repository: vaadin/web-components
Pull request #8951: refactor!: Base time picker styles
Author: login: anezthes
Created: 2025-04-15T10:05:46Z | Merged: 2025-04-24T06:56:57Z

## New and changed base style properties

| Property | Description |
| ------------- | ------------- |
| `--_vaadin-icon-clock` | Data URL for an SVG icon of a clock. Used for the toggle button inside the time picker. |

## New Time Picker base style

![localhost_8000_dev_time-picker html (1)](https://github.com/user-attachments/assets/4e6a1def-1f9f-4129-845b-0527511ec1df)

![localhost_8000_dev_time-picker html](https://github.com/user-attachments/assets/9305628d-53a1-4459-ab3d-72a4138c2585)



Diff:
diff --git a/dev/time-picker.html b/dev/time-picker.html
index 7de9b4a8cfa..426bddf444e 100644
--- a/dev/time-picker.html
+++ b/dev/time-picker.html
@@ -1,19 +1,66 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Time picker</title>
+    <title>Time Picker</title>
     <script type="module" src="./common.js"></script>
+
     <script type="module">
-      import '@vaadin/time-picker';
-      import '@vaadin/tooltip';
+      import '@vaadin/time-picker/src/vaadin-lit-time-picker.js';
+      import '@vaadin/icon/src/vaadin-lit-icon.js';
+      import '@vaadin/vaadin-lumo-styles/vaadin-iconset.js';
+      import '@vaadin/icons';
     </script>
   </head>
+
   <body>
-    <vaadin-time-picker value="15:00" clear-button-visible>
-      <vaadin-tooltip slot="tooltip" text="Evening times are preferred"></vaadin-tooltip>
-    </vaadin-time-picker>
+    <section>
+      <h2>Plain</h2>
+      <vaadin-time-picker value="12:00"></vaadin-time-picker>
+      <vaadin-time-picker placeholder="Placeholder"></vaadin-time-picker>
+    </section>
+
+    <section>
+      <h2>Bells & Whistles</h2>
+      <vaadin-time-picker
+        label="Label"
+        helper-text="Description for this field."
+        value="12:00"
+        clear-button-visible
+        error-message="You need to write something in this field."
+        required
+      >
+        <vaadin-icon icon="vaadin:search" slot="prefix"></vaadin-icon>
+      </vaadin-time-picker>
+    </section>
+
+    <section>
+      <h2>States</h2>
+      <vaadin-time-picker
+        label="Read-only"
+        helper-text="Description for this field."
+        value="12:00"
+        clear-button-visible
+        error-message="You need to write something in this field."
+        required
+        readonly
+      >
+        <vaadin-icon icon="vaadin:search" slot="prefix"></vaadin-icon>
+      </vaadin-time-picker>
+
+      <vaadin-time-picker
+        label="Disabled"
+        helper-text="Description for this field."
+        value="12:00"
+        clear-button-visible
+        error-message="You need to write something in this field."
+        required
+        disabled
+      >
+        <vaadin-icon icon="vaadin:search" slot="prefix"></vaadin-icon>
+      </vaadin-time-picker>
+    </section>
   </body>
 </html>
diff --git a/packages/component-base/src/style-props.js b/packages/component-base/src/style-props.js
index 4ce6788d1c8..384790e0010 100644
--- a/packages/component-base/src/style-props.js
+++ b/packages/component-base/src/style-props.js
@@ -50,6 +50,7 @@ addGlobalThemeStyles(
         --_vaadin-icon-calendar: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5" /></svg>');
         --_vaadin-icon-checkmark: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>');
         --_vaadin-icon-chevron-down: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" /></svg>');
+        --_vaadin-icon-clock: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /></svg>');
         --_vaadin-icon-cross: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>');
         --_vaadin-icon-eye: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>');
         --_vaadin-icon-eye-slash: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" /></svg>');
diff --git a/packages/item/src/vaadin-item-styles.js b/packages/item/src/vaadin-item-styles.js
index 373fa7cc514..3a4d806edf7 100644
--- a/packages/item/src/vaadin-item-styles.js
+++ b/packages/item/src/vaadin-item-styles.js
@@ -19,7 +19,7 @@ export const itemStyles = css`
       padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
     }
 
-    :host(:is(:focus-visible, vaadin-combo-box-item[focused])) {
+    :host([focused]) {
       outline: var(--vaadin-focus-ring-width) solid var(--vaadin-focus-ring-color);
       outline-offset: calc(var(--vaadin-focus-ring-width) / -1);
     }
diff --git a/packages/time-picker/src/vaadin-lit-time-picker-item.js b/packages/time-picker/src/vaadin-lit-time-picker-item.js
index 19a567a7d60..46eaf2d9f77 100644
--- a/packages/time-picker/src/vaadin-lit-time-picker-item.js
+++ b/packages/time-picker/src/vaadin-lit-time-picker-item.js
@@ -3,11 +3,12 @@
  * Copyright (c) 2018 - 2025 Vaadin Ltd.
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
  */
-import { css, html, LitElement } from 'lit';
+import { html, LitElement } from 'lit';
 import { ComboBoxItemMixin } from '@vaadin/combo-box/src/vaadin-combo-box-item-mixin.js';
 import { defineCustomElement } from '@vaadin/component-base/src/define.js';
 import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js';
 import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
+import { itemStyles } from '@vaadin/item/src/vaadin-item-styles.js';
 import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
 
 /**
@@ -25,15 +26,7 @@ export class TimePickerItem extends ComboBoxItemMixin(ThemableMixin(DirMixin(Pol
   }
 
   static get styles() {
-    return css`
-      :host {
-        display: block;
-      }
-
-      :host([hidden]) {
-        display: none !important;
-      }
-    `;
+    return itemStyles;
   }
 
   /** @protected */
diff --git a/packages/time-picker/src/vaadin-lit-time-picker-scroller.js b/packages/time-picker/src/vaadin-lit-time-picker-scroller.js
index f6dfc96bf59..6a8890e27c0 100644
--- a/packages/time-picker/src/vaadin-lit-time-picker-scroller.js
+++ b/packages/time-picker/src/vaadin-lit-time-picker-scroller.js
@@ -23,24 +23,18 @@ export class TimePickerScroller extends ComboBoxScrollerMixin(PolylitMixin(LitEl
   static get styles() {
     return css`
       :host {
+        --vaadin-item-checkmark-display: block;
+        /* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */
+        box-shadow: 0 0 0 white;
         display: block;
         min-height: 1px;
         overflow: auto;
-
         /* Fixes item background from getting on top of scrollbars on Safari */
         transform: translate3d(0, 0, 0);
-
-        /* Enable momentum scrolling on iOS */
-        -webkit-overflow-scrolling: touch;
-
-        /* Fixes scrollbar disappearing when 'Show scroll bars: Always' enabled in Safari */
-        box-shadow: 0 0 0 white;
       }
 
       #selector {
-        border-width: var(--_vaadin-time-picker-items-container-border-width);
-        border-style: var(--_vaadin-time-picker-items-container-border-style);
-        border-color: var(--_vaadin-time-picker-items-container-border-color, transparent);
+        border: var(--vaadin-item-overlay-padding, 4px) solid transparent;
         position: relative;
       }
     `;
diff --git a/packages/time-picker/src/vaadin-lit-time-picker.js b/packages/time-picker/src/vaadin-lit-time-picker.js
index 33bf891d479..ac38d71306c 100644
--- a/packages/time-picker/src/vaadin-lit-time-picker.js
+++ b/packages/time-picker/src/vaadin-lit-time-picker.js
@@ -32,6 +32,19 @@ class TimePicker extends TimePickerMixin(ThemableMixin(ElementMixin(PolylitMixin
     return [
       inputFieldShared,
       css`
+        [part='toggle-button'] {
+          color: var(--_vaadin-color-subtle);
+        }
+
+        [part='toggle-button']::before {
+          background: currentColor;
+          content: '';
+          display: block;
+          height: var(--vaadin-icon-size, 1lh);
+          mask-image: var(--_vaadin-icon-clock);
+          width: var(--vaadin-icon-size, 1lh);
+        }
+
         /* See https://github.com/vaadin/vaadin-time-picker/issues/145 */
         :host([dir='rtl']) [part='input-field'] {
           direction: ltr;
@@ -42,8 +55,10 @@ class TimePicker extends TimePickerMixin(ThemableMixin(ElementMixin(PolylitMixin
           text-align: left;
         }
 
-        [part~='toggle-button'] {
-          cursor: pointer;
+        @media (forced-colors: active) {
+          [part='toggle-button']::before {
+            background: CanvasText;
+          }
         }
       `,
     ];


Reviews:
user: login: web-padawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T11:14:14Z
user: login: web-padawan
body: Rebased the PR on top of the updated `base-styles` branch.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-22T11:47:14Z
user: login: web-padawan
body: Rebased on top of the updated `base-styles` branch to pick up combo-box styles.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T12:49:12Z
user: login: web-padawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T14:24:53Z
user: login: jouni
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T14:32:02Z
user: login: jouni
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T14:33:25Z
user: login: web-padawan
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-23T14:57:13Z
user: login: jouni
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T05:59:44Z
user: login: jouni
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T06:55:24Z
user: login: jouni
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T06:56:44Z

Review comments:
diff_hunk: @@ -32,6 +32,14 @@ class TimePicker extends TimePickerMixin(ThemableMixin(ElementMixin(PolylitMixin
     return [
       inputFieldShared,
       css`
+        [part='toggle-button'] {
+          cursor: default;
path: packages/time-picker/src/vaadin-lit-time-picker.js
user: login: web-padawan
body: I think this shouldn't be needed since we don't define `cursor: pointer` elsewhere (the old styles that are removed in this PR seem to be a leftover from the very early time-picker implementation). 
created_at: 2025-04-15T11:14:13Z
author_association: MEMBER
diff_hunk: @@ -19,7 +19,7 @@ export const itemStyles = css`
       padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
     }
 
-    :host(:is(:focus-visible, vaadin-combo-box-item[focused])) {
+    :host(:is(:focus-visible, vaadin-combo-box-item[focused], vaadin-time-picker-item[focused])) {
path: packages/item/src/vaadin-item-styles.js
user: login: web-padawan
body: We will also need to add `vaadin-multi-select-combo-box-item` here later.
created_at: 2025-04-23T14:24:53Z
author_association: MEMBER
diff_hunk: @@ -19,7 +19,7 @@ export const itemStyles = css`
       padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
     }
 
-    :host(:is(:focus-visible, vaadin-combo-box-item[focused])) {
+    :host(:is(:focus-visible, vaadin-combo-box-item[focused], vaadin-time-picker-item[focused])) {
path: packages/item/src/vaadin-item-styles.js
user: login: jouni
body: I’m not sure if this is actually a good idea (I mean my initial change from `[focused` → `:focus-visible`). Perhaps this should explicitly exclude vaadin-select-item instead, because that’s the one I tried to avoid styling unnecessarily. But when you open the select overlay, the first item is focused, but not visibly indicated, and when you press arrow down, the next item gets focused, which could be unexpected for the user (who might expect the first item in the list to get keyboard focus).
created_at: 2025-04-23T14:32:02Z
author_association: MEMBER
diff_hunk: @@ -19,7 +19,7 @@ export const itemStyles = css`
       padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
     }
 
-    :host(:is(:focus-visible, vaadin-combo-box-item[focused])) {
+    :host(:is(:focus-visible, vaadin-combo-box-item[focused], vaadin-time-picker-item[focused])) {
path: packages/item/src/vaadin-item-styles.js
user: login: jouni
body: So perhaps the behavior of Select should be changed a bit, so that it focuses the overlay first, instead of the first item, when you open the overlay with the mouse/pointer device.
created_at: 2025-04-23T14:33:25Z
author_association: MEMBER
diff_hunk: @@ -19,7 +19,7 @@ export const itemStyles = css`
       padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
     }
 
-    :host(:is(:focus-visible, vaadin-combo-box-item[focused])) {
+    :host(:is(:focus-visible, vaadin-combo-box-item[focused], vaadin-time-picker-item[focused])) {
path: packages/item/src/vaadin-item-styles.js
user: login: web-padawan
body: IMO changing the Select so that overlay is focused when opening on click, but the item is focused when opening on arrow down, could be a bit confusing as well. We should also ensure this works with screen readers.
created_at: 2025-04-23T14:57:13Z
author_association: MEMBER
diff_hunk: @@ -19,7 +19,7 @@ export const itemStyles = css`
       padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
     }
 
-    :host(:is(:focus-visible, vaadin-combo-box-item[focused])) {
+    :host(:is(:focus-visible, vaadin-combo-box-item[focused], vaadin-time-picker-item[focused])) {
path: packages/item/src/vaadin-item-styles.js
user: login: jouni
body: Isn't that how Menu Bar and Context Menu work? Personally I would expect the same behavior in these components. The native `<select>` also works like this. Though in macOS the mouse moves the keyboard focus (aka "focus follows mouse" within the overlay) and when you open the select the current selected item is under the mouse so keyboard focus is there. But when you move the mouse outside the overlay, then keyboard focus is apparently moved to the overlay, and pressing arrow down focuses the first item.
created_at: 2025-04-24T05:59:44Z
author_association: MEMBER
diff_hunk: @@ -19,7 +19,7 @@ export const itemStyles = css`
       padding: var(--vaadin-item-padding, var(--_vaadin-padding-container));
     }
 
-    :host(:is(:focus-visible, vaadin-combo-box-item[focused])) {
+    :host(:is(:focus-visible, vaadin-combo-box-item[focused], vaadin-time-picker-item[focused])) {
path: packages/item/src/vaadin-item-styles.js
user: login: jouni
body: I changed this back to the simple `:host([focused])` version.
created_at: 2025-04-24T06:55:24Z
author_association: MEMBER

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-24T06:55:56Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=vaadin_web-components&pullRequest=8951) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=vaadin_web-components&pullRequest=8951&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=vaadin_web-components&pullRequest=8951&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=vaadin_web-components&pullRequest=8951&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=vaadin_web-components&pullRequest=8951&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=vaadin_web-components&pullRequest=8951&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=vaadin_web-components&pullRequest=8951)


author_association: NONE