Clock Widget
Live clocks — an analog dial with arabic/roman/no numerals and a multi-city world clock.
Preview
@props([
'numerals' => 'arabic',
])
@php
// Numeral sets. The 12 labels are laid out clockwise starting at the 1
// o'clock position; index 11 (the XII / 12 slot) sits at the top.
$numerals = in_array($numerals, ['arabic', 'roman', 'none'], true) ? $numerals : 'arabic';
$sets = [
'arabic' => ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
'roman' => ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII'],
];
// Pre-compute each numeral's position as a percentage from the dial centre
// so it can ride on the card without a colour or layout dependency.
$marks = [];
if ($numerals !== 'none') {
$labels = $sets[$numerals];
$radius = 40; // percent of the dial radius the labels sit at
foreach ($labels as $i => $label) {
$hour = $i + 1;
$angle = ($hour / 12) * 360;
$radians = deg2rad($angle);
$x = 50 + sin($radians) * $radius;
$y = 50 - cos($radians) * $radius;
$marks[] = ['label' => $label, 'x' => round($x, 4), 'y' => round($y, 4)];
}
}
@endphp
<x-ui.card class="w-full max-w-[15rem] aspect-square flex items-center justify-center">
<div
x-data="{
now: null,
timer: null,
init() {
this.tick();
this.timer = setInterval(() => this.tick(), 1000);
},
destroy() {
clearInterval(this.timer);
},
tick() {
this.now = new Date();
},
get hourDeg() {
if (!this.now) return 0;
const h = this.now.getHours() % 12;
const m = this.now.getMinutes();
return ((h + m / 60) / 12) * 360;
},
get minuteDeg() {
if (!this.now) return 0;
const m = this.now.getMinutes();
const s = this.now.getSeconds();
return ((m + s / 60) / 60) * 360;
},
get secondDeg() {
if (!this.now) return 0;
return (this.now.getSeconds() / 60) * 360;
},
}"
role="img"
aria-label="{{ __('Analog clock') }}"
class="relative aspect-square w-[80%] rounded-full border border-border bg-card"
>
{{-- Numerals --}}
@foreach ($marks as $mark)
<span
class="absolute text-sm font-semibold text-foreground"
style="top: {{ $mark['y'] }}%; inset-inline-start: {{ $mark['x'] }}%; transform: translate(-50%, -50%);"
aria-hidden="true"
>{{ $mark['label'] }}</span>
@endforeach
{{-- Hour hand (thick) --}}
<div
class="absolute bottom-1/2 left-1/2 h-[28%] w-1 origin-bottom rounded-full bg-foreground"
style="transform: translateX(-50%) rotate(0deg);"
:style="`transform: translateX(-50%) rotate(${hourDeg}deg)`"
aria-hidden="true"
></div>
{{-- Minute hand --}}
<div
class="absolute bottom-1/2 left-1/2 h-[38%] w-1 origin-bottom rounded-full bg-foreground"
style="transform: translateX(-50%) rotate(0deg);"
:style="`transform: translateX(-50%) rotate(${minuteDeg}deg)`"
aria-hidden="true"
></div>
{{-- Second hand (thin) --}}
<div
class="absolute bottom-1/2 left-1/2 h-[42%] w-0.5 origin-bottom rounded-full bg-destructive"
style="transform: translateX(-50%) rotate(0deg);"
:style="`transform: translateX(-50%) rotate(${secondDeg}deg)`"
aria-hidden="true"
></div>
{{-- Centre dot --}}
<div
class="absolute top-1/2 left-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-foreground"
aria-hidden="true"
></div>
</div>
</x-ui.card>
Installation
php artisan ui:add blocks/clock-widget-analog
Registry contract
Install confidence
php artisan ui:add blocks/clock-widget-analog
writes only the generated targets below. The CLI validates each file hash before writing and
prompts before replacing local changes unless --force is used.
- Version
- 1.0.2
- License
- open
- Stability
- stable
- Contract
- v1
- Foundation
- ≥ 1.0.0
| Type | Generated target |
|---|---|
| blade | resources/views/blocks/clock-widget-analog.blade.php |
Registry dependencies
Package dependencies
composer: jml/brok:^0.2
npm: alpinejs
Use with AI
A brief for your coding agent: what the block is, the install command, how to render it, its props and the rules. Copy it, or open a prompt about this block in an assistant.
# Brok UI block: Clock Widget Analog (`clock-widget-analog`)
A live-ticking analog clock widget inside a square card: a bordered dial with optional arabic, roman, or no numerals, a thick hour hand, a minute hand, a thin destructive-toned second hand, and a centre dot, all driven from the local time in inline Alpine.
Brok UI is a Laravel Blade registry. `ui:add` copies this block into the app as plain Blade the app owns; it composes installed `<brok:*>` primitives and semantic design tokens.
## Install
```bash
php artisan ui:add blocks/clock-widget-analog
```
## Render it
```blade
<x-blocks.clock-widget-analog />
```
## Props
- `numerals` (string, default `arabic`)
## Use when
- Displaying a live-ticking analog clock face with hour, minute and second hands driven from the visitor's local time.
- Choosing between arabic numerals, roman numerals or a numeral-free dial via the numerals prop.
## Avoid when
- Use clock-widget-world for a multi-city world-clock list instead of a single analog dial.
## Rules
- Render the installed block with `<x-blocks.clock-widget-analog />` and pass data through its props; edit the copied file only for structural changes.
- Keep the semantic design tokens (`bg-background`, `text-muted-foreground`); never swap in raw colour utilities.
- Keep the `data-slot` attributes and the logical (start/end) spacing so the markup still mirrors under `dir="rtl"`.
## Links
- Docs: https://brokui.dev/blocks/clock-widget-analog
- Registry JSON (files, props, contract): https://brokui.dev/r/open/blocks/clock-widget-analog.json
Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.
Guidance
Use when
- Displaying a live-ticking analog clock face with hour, minute and second hands driven from the visitor's local time.
- Choosing between arabic numerals, roman numerals or a numeral-free dial via the numerals prop.
Avoid when
- Use clock-widget-world for a multi-city world-clock list instead of a single analog dial.
Anti-patterns
- Do not replace semantic props with conflicting utility classes.
- Do not remove labels, focus styles, or state attributes.
Usage
Render the block as a component, passing data where useful:
<x-blocks.clock-widget-analog />
Built from primitives
This block composes installed <brok:*> primitives and semantic design
tokens only — it does not reimplement any primitive. Re-theme it (light, dark, admin, customer) by
editing CSS variables; flip the preview to RTL to confirm it mirrors.
Source
The exact, editable file ui:add writes
into your app. The preview above renders this same source — there are no preview-only blocks.
@props([
'numerals' => 'arabic',
])
@php
// Numeral sets. The 12 labels are laid out clockwise starting at the 1
// o'clock position; index 11 (the XII / 12 slot) sits at the top.
$numerals = in_array($numerals, ['arabic', 'roman', 'none'], true) ? $numerals : 'arabic';
$sets = [
'arabic' => ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],
'roman' => ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII'],
];
// Pre-compute each numeral's position as a percentage from the dial centre
// so it can ride on the card without a colour or layout dependency.
$marks = [];
if ($numerals !== 'none') {
$labels = $sets[$numerals];
$radius = 40; // percent of the dial radius the labels sit at
foreach ($labels as $i => $label) {
$hour = $i + 1;
$angle = ($hour / 12) * 360;
$radians = deg2rad($angle);
$x = 50 + sin($radians) * $radius;
$y = 50 - cos($radians) * $radius;
$marks[] = ['label' => $label, 'x' => round($x, 4), 'y' => round($y, 4)];
}
}
@endphp
<x-ui.card class="w-full max-w-[15rem] aspect-square flex items-center justify-center">
<div
x-data="{
now: null,
timer: null,
init() {
this.tick();
this.timer = setInterval(() => this.tick(), 1000);
},
destroy() {
clearInterval(this.timer);
},
tick() {
this.now = new Date();
},
get hourDeg() {
if (!this.now) return 0;
const h = this.now.getHours() % 12;
const m = this.now.getMinutes();
return ((h + m / 60) / 12) * 360;
},
get minuteDeg() {
if (!this.now) return 0;
const m = this.now.getMinutes();
const s = this.now.getSeconds();
return ((m + s / 60) / 60) * 360;
},
get secondDeg() {
if (!this.now) return 0;
return (this.now.getSeconds() / 60) * 360;
},
}"
role="img"
aria-label="{{ __('Analog clock') }}"
class="relative aspect-square w-[80%] rounded-full border border-border bg-card"
>
{{-- Numerals --}}
@foreach ($marks as $mark)
<span
class="absolute text-sm font-semibold text-foreground"
style="top: {{ $mark['y'] }}%; inset-inline-start: {{ $mark['x'] }}%; transform: translate(-50%, -50%);"
aria-hidden="true"
>{{ $mark['label'] }}</span>
@endforeach
{{-- Hour hand (thick) --}}
<div
class="absolute bottom-1/2 left-1/2 h-[28%] w-1 origin-bottom rounded-full bg-foreground"
style="transform: translateX(-50%) rotate(0deg);"
:style="`transform: translateX(-50%) rotate(${hourDeg}deg)`"
aria-hidden="true"
></div>
{{-- Minute hand --}}
<div
class="absolute bottom-1/2 left-1/2 h-[38%] w-1 origin-bottom rounded-full bg-foreground"
style="transform: translateX(-50%) rotate(0deg);"
:style="`transform: translateX(-50%) rotate(${minuteDeg}deg)`"
aria-hidden="true"
></div>
{{-- Second hand (thin) --}}
<div
class="absolute bottom-1/2 left-1/2 h-[42%] w-0.5 origin-bottom rounded-full bg-destructive"
style="transform: translateX(-50%) rotate(0deg);"
:style="`transform: translateX(-50%) rotate(${secondDeg}deg)`"
aria-hidden="true"
></div>
{{-- Centre dot --}}
<div
class="absolute top-1/2 left-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-foreground"
aria-hidden="true"
></div>
</div>
</x-ui.card>
Ownership & lifecycle
Owner, release state, review evidence and adoption for this item.
- Owner
- Platform UI (@JoshJML)
- Current version
-
1.0.2 - Status
- Stable
- License
-
open - Deprecation
- Not deprecated
- Contract
-
v1 - Foundation
-
≥ 1.0.0