Skip to content

Web Component

@clock-ui/dom also ships the clock as a custom element. Custom elements are consumed natively by Svelte, Solid, Angular, Astro, Qwik, Lit, and plain HTML, so this one element covers every framework that does not have a dedicated Clock UI package.

bash
npm install @clock-ui/dom
js
import "@clock-ui/dom/element";
import "@clock-ui/dom/base.css";
html
<clock-ui smooth-sweep></clock-ui>

Importing the module registers <clock-ui> for you.

From a CDN

No bundler, no build step:

html
<link rel="stylesheet" href="https://unpkg.com/@clock-ui/dom@0.1/dist/element.css" />
<script src="https://unpkg.com/@clock-ui/dom@0.1/dist/element.umd.js"></script>

<clock-ui smooth-sweep style="width: 240px"></clock-ui>

Attributes

Props become kebab-case attributes. Presence means true; write ="false" to turn a default-on option off.

AttributeTypeDescription
smooth-sweepbooleanContinuous sweep instead of ticking.
timezonestringIANA name, e.g. Asia/Tokyo.
tick-durationnumberSwing duration in ms. Defaults to 600; 0 snaps.
hours, minutes, secondsnumberSetting hours and minutes renders a static clock instead of a live one.
use-romanbooleanRoman numerals.
cardinal-onlybooleanOnly 12, 3, 6, 9.
hide-numbers, hide-ticks, hide-major-ticks, hide-minor-ticks, hide-seconds, hide-datebooleanHide the named part.
no-borderbooleanDrop the outer frame.
dual-tonebooleanOn by default; use dual-tone="false" to disable.

Attributes are observed, so changing one re-renders the clock.

html
<!-- live, Tokyo, roman numerals -->
<clock-ui use-roman timezone="Asia/Tokyo"></clock-ui>

<!-- static 10:09, minimal -->
<clock-ui hours="10" minutes="9" cardinal-only no-border></clock-ui>

Attributes or properties

Frameworks disagree about how to hand data to a custom element. Svelte and Astro write attributes; Solid, Lit, Angular's [prop] binding and React 19 write properties. Every attribute above is mirrored as a camelCase property that reflects back to it, so both styles work:

js
el.setAttribute("timezone", "Asia/Tokyo"); // attribute
el.timezone = "Asia/Tokyo"; // property — same result
el.useRoman = true; // sets use-roman

Light DOM, on purpose

<clock-ui> renders into the light DOM rather than a shadow root. That keeps the stylesheet you imported and any inherited --cui-* custom properties working exactly as they do everywhere else — a shadow root would cut both off.

The practical consequence is that theming works the same way as the rest of Clock UI:

css
clock-ui {
  width: 240px;
  --cui-bg-color: #1f1a38;
  --cui-primary-color: #ead7d1;
  --cui-accent-color: #dd99bb;
}

Framework notes

The module is safe to import anywhere, including in a server bundle — it detects the absence of HTMLElement and skips registration rather than throwing. You do not need a dynamic import or a browser guard.

Svelte, Solid, Astro, plain HTML — import the module once and use the tag. Nothing else to do. Runnable examples for each live in examples/.

Angular

Angular's template compiler rejects unknown elements, so add CUSTOM_ELEMENTS_SCHEMA to the component (or module) that uses the tag:

ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import "@clock-ui/dom/element";
import "@clock-ui/dom/base.css";

@Component({
  selector: "app-clock",
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <clock-ui smooth-sweep [timezone]="zone"></clock-ui>
  `,
})
export class ClockComponent {
  zone = "Asia/Tokyo";
}

Angular's [prop] syntax sets a property, which works because every attribute is mirrored as one. Static values can use plain attributes (smooth-sweep) and dynamic ones either [timezone] or [attr.timezone].

Qwik

Qwik renders on the server first. The module import is safe there, but the element only registers in the browser, so the clock appears on hydration:

tsx
import { component$ } from "@builder.io/qwik";
import "@clock-ui/dom/element";
import "@clock-ui/dom/base.css";

export default component$(() => {
  return <clock-ui smooth-sweep style={{ width: "240px" }} />;
});

Because the server renders an empty <clock-ui>, give it a width in CSS so the layout does not jump when the clock upgrades.

Vue

Vue treats unknown tags as components unless told otherwise. If you use the custom element rather than @clock-ui/vue, mark it in your build config:

ts
vue({
  template: {
    compilerOptions: {
      isCustomElement: (tag) => tag === "clock-ui",
    },
  },
});

React

React 19 handles custom elements natively and passes non-string values as properties, which this element supports. On React 18, pass strings as attributes (smooth-sweep=""). Either way, @clock-ui/react is the better fit.

Registering under a different name

If clock-ui collides with something, register your own tag:

js
import { defineClockUI } from "@clock-ui/dom/element";

defineClockUI("analog-clock");

Calling it more than once is safe — a tag that is already defined is skipped.

Released under the MIT License.