
A Dribbble Shot, but Ticking
Found a clock illustration by Renat Muratshin on Dribbble — minimal, clean, good shadows, nothing fussy. I wanted to build it as a working clock without touching SVG or canvas. Just CSS and React.
The constraint was the interesting part. No clock library, no SVG hand paths, no canvas. Hand positions calculated from the current time as CSS rotation transforms. If I couldn't pull it off with CSS, the design direction was wrong.
Time as Geometry
Each hand is a div. Its rotation is `(value / max) * 360` degrees, where value is seconds, minutes, or hours from the current Date. The CSS transform-origin sits at the bottom center of each hand element so it rotates around the right point. The clock face is the same idea — 12 hour markers are spans absolutely positioned around a circle, rotated to their positions with transform.
setInterval fires every second, updates state with new Date(), and React re-renders the hands. That's the entire timing mechanism.
Face + Hands + Readout
Three layers. The face is static — markers, tick marks, the circle. The hands layer re-renders every second. Below the clock, a digital readout shows the time in text. Light and dark theme stored in localStorage.
CSS circle with 12 hour markers and 60 minute ticks, each a span rotated to its angle via transform
Three divs (hour, minute, second), rotation angle computed from current time, transform-origin at bottom center
setInterval at 1000ms, updates state with new Date(), triggers re-render of the hands layer
12/24h formatted string below the clock, using date-fns format()
Light/dark toggle in localStorage. All colors as CSS custom properties — one attribute on the root flips the whole palette.
Under the Hood
Rotation angles computed as (value / max) * 360. transform-origin: bottom center on each hand div. No SVG, no canvas.
format() for the digital readout. date-fns is 3KB; moment.js is 67KB. Easy choice.
Every color and shadow is a variable. Theme switching is a single data-theme attribute change on the root element.
Interval set inside useEffect, cleared in the cleanup function. Standard pattern, but matters — without cleanup the interval keeps firing after unmount.
What Made It Hard
- The second hand needed a slight overshoot on each tick to feel like a real watch movement rather than a digital counter. Got there with cubic-bezier(0.4, 2.3, 0.6, 1) on the CSS transition — the 2.3 control point is what creates the overshoot.
- At 11:59:59 the hour hand is at 330°. One second later it's at 0°. A CSS transition between those two values goes backwards — the hand sweeps the long way around the clock face. Fixed by detecting the boundary and briefly disabling the transition for that one tick.
- Getting the shadows and depth of the Dribbble reference right in CSS took a lot of iteration. The illustration has three or four stacked shadows per element. Matching that in code by eye is slower than it sounds.