Repository: home-assistant/frontend
Pull request #24818: Add title and time zone to clock card
Author: login: ENK0DED
Labels: name: cla-signed
name: cherry-picked
Created: 2025-03-27T20:46:07Z | Merged: 2025-03-28T14:32:50Z

## Proposed change
<!--
  Describe the big picture of your changes here to communicate to the
  maintainers why we should accept this pull request. If it fixes a bug
  or resolves a feature request, be sure to link to that issue or discussion
  in the additional information section.
-->
Add the possibility to set a title and an option to select the time zone to the new clock card

![image](https://github.com/user-attachments/assets/13ca8f9a-624d-41c4-b814-9623cde59d1e)

![image](https://github.com/user-attachments/assets/fcd60e43-8852-44e4-a4ed-64201bf78334)

## Type of change
<!--
  What type of change does your PR introduce to the Home Assistant frontend?
  NOTE: Please, check only 1! box!
  If your PR requires multiple boxes to be checked, you'll most likely need to
  split it into multiple PRs. This makes things easier and faster to code review.
-->

- [ ] Dependency upgrade
- [ ] Bugfix (non-breaking change which fixes an issue)
- [x] New feature (thank you!)
- [ ] Breaking change (fix/feature causing existing functionality to break)
- [ ] Code quality improvements to existing code or addition of tests

## Example configuration
<!--
  Supplying a configuration snippet, makes it easier for a maintainer to test
  your PR.
-->

```yaml

```

## Additional information
<!--
  Details are important, and help maintainers processing your PR.
  Please be sure to fill out additional details, if applicable.
-->

- This PR fixes or closes issue: fixes #
- This PR is related to issue or discussion:
- Link to documentation pull request:

## Checklist
<!--
  Put an `x` in the boxes that apply. You can also fill these out after
  creating the PR. If you're unsure about any of them, don't hesitate to ask.
  We're here to help! This is simply a reminder of what we are going to look
  for before merging your code.
-->

- [x] The code change is tested and works locally.
- [x] There is no commented out code in this PR.
- [ ] Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

- [ ] Documentation added/updated for [www.home-assistant.io][docs-repository]

<!--
  Thank you for contributing <3
-->

[docs-repository]: https://github.com/home-assistant/home-assistant.io



Diff:
diff --git a/src/panels/lovelace/cards/hui-clock-card.ts b/src/panels/lovelace/cards/hui-clock-card.ts
index c99498614db8..346ddb9adbaa 100644
--- a/src/panels/lovelace/cards/hui-clock-card.ts
+++ b/src/panels/lovelace/cards/hui-clock-card.ts
@@ -65,7 +65,9 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
       minute: "2-digit",
       second: "2-digit",
       hourCycle: useAmPm(locale) ? "h12" : "h23",
-      timeZone: resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
+      timeZone:
+        this._config?.time_zone ||
+        resolveTimeZone(locale.time_zone, this.hass.config?.time_zone),
     });
 
     this._tick();
@@ -79,7 +81,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
   public getGridOptions(): LovelaceGridOptions {
     if (this._config?.clock_size === "medium") {
       return {
-        min_rows: 1,
+        min_rows: this._config?.title ? 2 : 1,
         rows: 2,
         max_rows: 4,
         min_columns: 4,
@@ -101,7 +103,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
       min_rows: 1,
       rows: 1,
       max_rows: 4,
-      min_columns: 4,
+      min_columns: 3,
       columns: 6,
     };
   }
@@ -160,6 +162,9 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
             ? `size-${this._config.clock_size}`
             : ""}"
         >
+          ${this._config.title !== undefined
+            ? html`<div class="time-title">${this._config.title}</div>`
+            : nothing}
           <div class="time-parts">
             <div class="time-part hour">${this._timeHour}</div>
             <div class="time-part minute">${this._timeMinute}</div>
@@ -182,9 +187,41 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
 
     .time-wrapper {
       display: flex;
-      height: 100%;
+      height: calc(100% - 12px);
       align-items: center;
+      flex-direction: column;
       justify-content: center;
+      padding: 6px 8px;
+      row-gap: 6px;
+    }
+
+    .time-wrapper.size-medium,
+    .time-wrapper.size-large {
+      height: calc(100% - 32px);
+      padding: 16px;
+      row-gap: 12px;
+    }
+
+    .time-title {
+      color: var(--primary-text-color);
+      font-size: 14px;
+      font-weight: 400;
+      line-height: 18px;
+      overflow: hidden;
+      text-align: center;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      width: 100%;
+    }
+
+    .time-wrapper.size-medium .time-title {
+      font-size: 18px;
+      line-height: 21px;
+    }
+
+    .time-wrapper.size-large .time-title {
+      font-size: 24px;
+      line-height: 28px;
     }
 
     .time-parts {
@@ -197,7 +234,10 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
       font-size: 2rem;
       font-weight: 500;
       line-height: 0.8;
-      padding: 16px 0;
+    }
+
+    .time-title + .time-parts {
+      font-size: 1.5rem;
     }
 
     .time-wrapper.size-medium .time-parts {
@@ -242,8 +282,7 @@ export class HuiClockCard extends LitElement implements LovelaceCard {
 
     .time-parts .time-part.second,
     .time-parts .time-part.am-pm {
-      font-size: 12px;
-      font-weight: 500;
+      font-size: 10px;
       margin-left: 4px;
     }
 
diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts
index 5e7e25ed8d7e..30227b5fe341 100644
--- a/src/panels/lovelace/cards/types.ts
+++ b/src/panels/lovelace/cards/types.ts
@@ -349,9 +349,11 @@ export interface MarkdownCardConfig extends LovelaceCardConfig {
 
 export interface ClockCardConfig extends LovelaceCardConfig {
   type: "clock";
+  title?: string;
   clock_size?: "small" | "medium" | "large";
   show_seconds?: boolean | undefined;
   time_format?: TimeFormat;
+  time_zone?: string;
 }
 
 export interface MediaControlCardConfig extends LovelaceCardConfig {
diff --git a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts
index 83a140b55378..cb73a2460d94 100644
--- a/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts
+++ b/src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts
@@ -1,3 +1,4 @@
+import timezones from "google-timezones-json";
 import { html, LitElement, nothing } from "lit";
 import { customElement, property, state } from "lit/decorators";
 import memoizeOne from "memoize-one";
@@ -9,6 +10,7 @@ import {
   literal,
   object,
   optional,
+  string,
   union,
 } from "superstruct";
 import { fireEvent } from "../../../../common/dom/fire_event";
@@ -27,10 +29,12 @@ import { TimeFormat } from "../../../../data/translation";
 const cardConfigStruct = assign(
   baseLovelaceCardConfig,
   object({
+    title: optional(string()),
     clock_size: optional(
       union([literal("small"), literal("medium"), literal("large")])
     ),
     time_format: optional(enums(Object.values(TimeFormat))),
+    time_zone: optional(enums(Object.keys(timezones))),
     show_seconds: optional(boolean()),
   })
 );
@@ -47,6 +51,7 @@ export class HuiClockCardEditor
   private _schema = memoizeOne(
     (localize: LocalizeFunc) =>
       [
+        { name: "title", selector: { text: {} } },
         {
           name: "clock_size",
           selector: {
@@ -61,12 +66,7 @@ export class HuiClockCardEditor
             },
           },
         },
-        {
-          name: "show_seconds",
-          selector: {
-            boolean: {},
-          },
-        },
+        { name: "show_seconds", selector: { boolean: {} } },
         {
           name: "time_format",
           selector: {
@@ -81,11 +81,32 @@ export class HuiClockCardEditor
             },
           },
         },
+        {
+          name: "time_zone",
+          selector: {
+            select: {
+              mode: "dropdown",
+              options: [
+                [
+                  "auto",
+                  localize(
+                    `ui.panel.lovelace.editor.card.clock.time_zones.auto`
+                  ),
+                ],
+                ...Object.entries(timezones as Record<string, string>),
+              ].map(([key, value]) => ({
+                value: key,
+                label: value,
+              })),
+            },
+          },
+        },
       ] as const satisfies readonly HaFormSchema[]
   );
 
   private _data = memoizeOne((config) => ({
     clock_size: "small",
+    time_zone: "auto",
     time_format: "auto",
     show_seconds: false,
     ...config,
@@ -113,6 +134,9 @@ export class HuiClockCardEditor
   }
 
   private _valueChanged(ev: CustomEvent): void {
+    if (ev.detail.value.time_zone === "auto") {
+      delete ev.detail.value.time_zone;
+    }
     if (ev.detail.value.time_format === "auto") {
       delete ev.detail.value.time_format;
     }
@@ -124,6 +148,10 @@ export class HuiClockCardEditor
     schema: SchemaUnion<ReturnType<typeof this._schema>>
   ) => {
     switch (schema.name) {
+      case "title":
+        return this.hass!.localize(
+          "ui.panel.lovelace.editor.card.generic.title"
+        );
       case "clock_size":
         return this.hass!.localize(
           `ui.panel.lovelace.editor.card.clock.clock_size`
@@ -132,6 +160,10 @@ export class HuiClockCardEditor
         return this.hass!.localize(
           `ui.panel.lovelace.editor.card.clock.time_format`
         );
+      case "time_zone":
+        return this.hass!.localize(
+          `ui.panel.lovelace.editor.card.clock.time_zone`
+        );
       case "show_seconds":
         return this.hass!.localize(
           `ui.panel.lovelace.editor.card.clock.show_seconds`
diff --git a/src/translations/en.json b/src/translations/en.json
index ddb3c37d3711..937aae1df309 100644
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -7161,13 +7161,17 @@
                 "large": "Large"
               },
               "show_seconds": "Display seconds",
-              "time_format": "Time format",
+              "time_format": "[%key:ui::panel::profile::time_format::dropdown_label%]",
               "time_formats": {
                 "auto": "Use user settings",
                 "language": "[%key:ui::panel::profile::time_format::formats::language%]",
                 "system": "[%key:ui::panel::profile::time_format::formats::system%]",
                 "24": "[%key:ui::panel::profile::time_format::formats::24%]",
                 "12": "[%key:ui::panel::profile::time_format::formats::12%]"
+              },
+              "time_zone": "[%key:ui::panel::profile::time_zone::dropdown_label%]",
+              "time_zones": {
+                "auto": "Use user settings"
               }
             },
             "media-control": {


Reviews:
user: login: home-assistant[bot]
body: 
Hi @ENK0DED

It seems you haven't yet signed a CLA. Please do so [here](https://home-assistant.io/developers/cla_sign_start/?pr=home-assistant/frontend#24818).

Once you do that we will be able to review and accept this pull request.

Thanks!

state: DISMISSED
author_association: NONE
submitted_at: 2025-03-27T20:46:10Z
user: login: bramkragten
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T08:14:13Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds the ability to set a title and select a time zone for the clock card. Key changes include updating the card configuration schema and interfaces, enhancing the clock card UI logic, and adjusting styling and layout based on the new options.

### Reviewed Changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated no comments.

| File | Description |
| ---- | ----------- |
| src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts | Adds new schema fields for title and time zone and integrates them into the editor’s default data and localization. |
| src/panels/lovelace/cards/types.ts | Updates the ClockCardConfig interface with optional title and time_zone properties. |
| src/panels/lovelace/cards/hui-clock-card.ts | Integrates the title into the card rendering, adjusts grid options based on title presence, and updates time zone resolution and styling. |

<details>
<summary>Files not reviewed (1)</summary>

* **src/translations/en.json**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**src/panels/lovelace/cards/hui-clock-card.ts:83**
* [nitpick] Consider checking for a non-empty title (e.g., using a truthy check or trimming the string) so that an empty string does not trigger grid option changes meant for a meaningful title.
```
if (this._config?.title !== undefined) {
```
**src/panels/lovelace/cards/hui-clock-card.ts:116**
* [nitpick] The adjustment of min_columns from 4 to 3 for non-medium clock sizes may have a layout impact; please verify that this change aligns with the intended UI design.
```
min_columns: 3,
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T10:43:28Z
user: login: mrdarrengriffin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T10:50:42Z
user: login: ENK0DED
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T11:13:22Z
user: login: bramkragten
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T12:16:49Z
user: login: ENK0DED
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T13:18:53Z
user: login: bramkragten
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-28T14:05:17Z
user: login: bramkragten
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-28T14:23:23Z

Review comments:
diff_hunk: @@ -81,12 +81,27 @@ export class HuiClockCardEditor
             },
           },
         },
+        {
+          name: "time_zone",
+          selector: {
+            select: {
+              mode: "dropdown",
+              options: Object.entries(timezones as Record<string, string>).map(
+                ([key, value]) => ({
+                  value: key,
+                  label: value,
+                })
+              ),
+            },
+          },
+        },
       ] as const satisfies readonly HaFormSchema[]
   );
 
   private _data = memoizeOne((config) => ({
     clock_size: "small",
     time_format: TimeFormat.language,
+    time_zone: this.hass?.config?.time_zone,
path: src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts
user: login: bramkragten
body: The default is actually the local time zone of the browser, but depends on the user setting
created_at: 2025-03-28T08:14:03Z
author_association: MEMBER
diff_hunk: @@ -7167,7 +7167,8 @@
                 "system": "[%key:ui::panel::profile::time_format::formats::system%]",
                 "24": "[%key:ui::panel::profile::time_format::formats::24%]",
                 "12": "[%key:ui::panel::profile::time_format::formats::12%]"
-              }
+              },
+              "time_zone": "Time zone"
path: src/translations/en.json
user: login: mrdarrengriffin
body: This should reference the already existing label. Change to `[%key:ui::panel::profile::time_zone::dropdown_label%]`
created_at: 2025-03-28T10:50:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7167,7 +7167,8 @@
                 "system": "[%key:ui::panel::profile::time_format::formats::system%]",
                 "24": "[%key:ui::panel::profile::time_format::formats::24%]",
                 "12": "[%key:ui::panel::profile::time_format::formats::12%]"
-              }
+              },
+              "time_zone": "Time zone"
path: src/translations/en.json
user: login: ENK0DED
body: @mrdarrengriffin I modified both dropdown labels (time format and time zone) to be references to the already existing labels.
created_at: 2025-03-28T11:13:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -81,12 +81,27 @@ export class HuiClockCardEditor
             },
           },
         },
+        {
+          name: "time_zone",
+          selector: {
+            select: {
+              mode: "dropdown",
+              options: Object.entries(timezones as Record<string, string>).map(
+                ([key, value]) => ({
+                  value: key,
+                  label: value,
+                })
+              ),
+            },
+          },
+        },
       ] as const satisfies readonly HaFormSchema[]
   );
 
   private _data = memoizeOne((config) => ({
     clock_size: "small",
     time_format: TimeFormat.language,
+    time_zone: this.hass?.config?.time_zone,
path: src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts
user: login: bramkragten
body: It should use a similar approach as https://github.com/home-assistant/frontend/pull/24819
created_at: 2025-03-28T12:16:49Z
author_association: MEMBER
diff_hunk: @@ -81,12 +81,27 @@ export class HuiClockCardEditor
             },
           },
         },
+        {
+          name: "time_zone",
+          selector: {
+            select: {
+              mode: "dropdown",
+              options: Object.entries(timezones as Record<string, string>).map(
+                ([key, value]) => ({
+                  value: key,
+                  label: value,
+                })
+              ),
+            },
+          },
+        },
       ] as const satisfies readonly HaFormSchema[]
   );
 
   private _data = memoizeOne((config) => ({
     clock_size: "small",
     time_format: TimeFormat.language,
+    time_zone: this.hass?.config?.time_zone,
path: src/panels/lovelace/editor/config-elements/hui-clock-card-editor.ts
user: login: ENK0DED
body: Should be fixed by fe3af02.
created_at: 2025-03-28T13:18:53Z
author_association: CONTRIBUTOR

PR comments:
user: login: home-assistant[bot]
created_at: 2025-03-27T20:46:12Z
body: <!-- ReviewDrafterComment -->
Please take a look at the requested changes, and use the **Ready for review** button when you are done, thanks :+1:

[_Learn more about our pull request process._](https://developers.home-assistant.io/docs/review-process#prs-are-being-drafted-when-changes-are-needed)

author_association: NONE
user: login: MindFreeze
created_at: 2025-03-28T07:48:19Z
body: Thanks for the contribution. It would be easier and quicker to review if you include some screenshots of the changes
author_association: MEMBER
user: login: ENK0DED
created_at: 2025-03-28T09:35:40Z
body: @bramkragten @MindFreeze I was actually thinking of use cases where someone might want multiple clocks with different time zones, just like physical clocks that are able to show multiple time zones at the same time.

I also did some minor changes to spacing and text sizing and added screenshots to the original text of the PR.
author_association: CONTRIBUTOR
user: login: mrdarrengriffin
created_at: 2025-03-28T11:05:58Z
body: @ENK0DED Just to say, thanks for improving this card. Nice to see people iterate upon it
author_association: CONTRIBUTOR