What's included
Features
About this UI Snippet
Smartwatch Mockup — CSS Watch Frame with Activity Rings

A smartwatch mockup presents a watch face design inside a wearable device frame — for fitness apps, complications, and wearable concepts. This snippet builds an Apple-Watch-style one in pure HTML and CSS (bands, digital crown, side button, rounded screen) with a live clock and animated activity rings, in vanilla JavaScript with no dependency.
The watch body and bands
The case is a rounded square with a heavily-curved border-radius, an inset box-shadow edge, and a soft drop shadow. The two bands are gradient <span>s positioned above and below the case with z-index:-1 so they tuck behind it, and the digital crown and side button are slivers on the right edge — all CSS, no image, so it scales cleanly and recolors at will.
A live OLED-style face
The screen is pure black (like an always-on OLED), with a green digital time that updates live, an abbreviated date built from a DAYS array, and two complications (heart rate, steps) at the bottom. The bright monospace-numeral time over black is the classic smartwatch look, achieved with font weight and letter-spacing rather than a custom font.
Activity rings with stroke-dasharray
The three concentric rings are the centerpiece. Each is an SVG <circle> whose stroke-dasharray equals its circumference (2πr) and whose stroke-dashoffset starts at full (empty). Animating the offset toward circumference × (1 - percent) fills the ring proportionally, and a 1s transition makes them sweep closed — exactly the technique behind real activity rings. A -90° rotation starts each ring at the top, and stroke-linecap: round gives the soft ends.
Interactive "close your rings"
A toggle animates all three rings from empty to their targets (86%, 72%, 95%) and brightens them, then resets — a satisfying demo of the fill animation and a stand-in for live fitness data. Because each ring's geometry is precomputed, setting a new percentage is a single offset assignment.
Reusing it
Swap the complications, recolor the rings, or replace the face entirely — the frame is a reusable wrapper. Feed real heart-rate or step data into setRings() and the time from a device, and pair it with a phone mockup to present a companion app.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You don't have to work out the ring-fill geometry by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why each ring's stroke-dasharray is set to its own circumference (2 times pi times r) and why stroke-dashoffset at that same value hides the entire stroke, or why the whole SVG is rotated -90 degrees so the fill animation starts at 12 o'clock instead of 3 o'clock. The same assistant can help optimize it, for instance checking whether the setInterval clock tick at 5 seconds is the right tradeoff between accuracy and unnecessary re-renders for a watch face that only displays minutes. It is just as useful for extending the mockup: ask it to add a fourth outer ring for a custom metric, make the complications tappable to cycle through different data views, or drive the rings from a real fitness API response instead of the hardcoded target percentages. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a CSS-and-SVG "smartwatch mockup" with animated activity rings in plain HTML, CSS, and JavaScript — no images, no canvas, no libraries.
Requirements:
- A watch case built entirely from CSS shapes: a heavily rounded rectangular body, two band segments (top and bottom) positioned behind the case using a negative z-index so they appear to pass under it, a thin digital crown sliver, and a side button sliver — all using gradients and border-radius, no bitmap images.
- Inside the case, a rounded rectangular screen rendering a black watch face containing: a live-updating digital time display and abbreviated day/date label driven by JavaScript's Date object on a repeating interval, and two or more small "complications" (e.g. heart rate, step count) as text.
- Three concentric SVG circles as activity rings. For each ring, set its stroke-dasharray to its own circumference (2 * PI * radius) and its initial stroke-dashoffset to that same full circumference so it renders fully hidden. Rotate the whole ring SVG group -90 degrees around its center so ring fills begin at the 12 o'clock position, and use round line caps on the ring strokes.
- A function that accepts a percentage per ring and sets each ring's stroke-dashoffset to circumference times (1 minus that percentage), animated with a CSS transition on stroke-dashoffset (around 1 second, ease timing) so the ring visibly sweeps to its new fill level.
- A toggle control that calls this fill function with a set of target percentages to demonstrate the rings animating from empty to filled, and back to empty when toggled again, also adjusting the ring group's opacity between a dim "inactive" state and a bright "active" state.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Source Code
<div class="sw-stage">
<div class="sw-watch">
<span class="sw-band sw-top"></span>
<span class="sw-band sw-bottom"></span>
<span class="sw-crown"></span>
<span class="sw-btn"></span>
<div class="sw-screen" id="swScreen">
<div class="sw-face" id="swFace">
<div class="sw-time" id="swTime">9:41</div>
<div class="sw-date" id="swDate">MON 29</div>
<div class="sw-rings">
<svg viewBox="0 0 100 100" width="92">
<circle class="sw-bg" cx="50" cy="50" r="42"/><circle class="sw-r r1" cx="50" cy="50" r="42"/>
<circle class="sw-bg" cx="50" cy="50" r="32"/><circle class="sw-r r2" cx="50" cy="50" r="32"/>
<circle class="sw-bg" cx="50" cy="50" r="22"/><circle class="sw-r r3" cx="50" cy="50" r="22"/>
</svg>
</div>
<div class="sw-complications"><span>72<small>bpm</small></span><span>8.2<small>k</small></span></div>
</div>
</div>
</div>
<button type="button" class="sw-toggle" id="swToggle">Close rings</button>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#e2e8f0;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:24px}
.sw-stage{text-align:center}
.sw-watch{position:relative;width:172px;height:200px;background:#0b1220;border-radius:48px;box-shadow:0 24px 44px -20px rgba(15,23,42,.55),inset 0 0 0 2px #1e293b;display:flex;align-items:center;justify-content:center}
.sw-band{position:absolute;left:50%;transform:translateX(-50%);width:104px;background:#475569;z-index:-1}
.sw-top{bottom:calc(100% - 26px);height:70px;border-radius:18px 18px 0 0;background:linear-gradient(#334155,#475569)}
.sw-bottom{top:calc(100% - 26px);height:70px;border-radius:0 0 18px 18px;background:linear-gradient(#475569,#334155)}
.sw-crown{position:absolute;right:-5px;top:64px;width:7px;height:26px;background:#94a3b8;border-radius:3px}
.sw-btn{position:absolute;right:-3px;top:104px;width:4px;height:34px;background:#334155;border-radius:3px}
.sw-screen{width:148px;height:176px;border-radius:40px;overflow:hidden;background:#000;color:#fff;display:flex;align-items:center;justify-content:center}
.sw-face{position:relative;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:14px}
.sw-time{font-size:34px;font-weight:700;letter-spacing:-1px;color:#34d399;font-variant-numeric:tabular-nums;line-height:1}
.sw-date{font-size:11px;font-weight:700;color:#94a3b8;letter-spacing:1px;margin-top:2px}
.sw-rings{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);opacity:.32}
.sw-bg{fill:none;stroke:#1e293b;stroke-width:7}
.sw-r{fill:none;stroke-width:7;stroke-linecap:round;transform:rotate(-90deg);transform-origin:50% 50%;transition:stroke-dashoffset 1s ease}
.r1{stroke:#fb2576}.r2{stroke:#a3e635}.r3{stroke:#22d3ee}
.sw-complications{display:flex;gap:14px;margin-top:auto;font-size:14px;font-weight:800;font-variant-numeric:tabular-nums}
.sw-complications small{font-size:9px;font-weight:700;color:#94a3b8;margin-left:1px}
.sw-toggle{margin-top:22px;background:#0f172a;color:#fff;border:none;border-radius:9px;padding:9px 16px;font-size:12.5px;font-weight:700;cursor:pointer;font-family:inherit}
.sw-toggle:hover{background:#1e293b}var timeEl = document.getElementById('swTime');
var dateEl = document.getElementById('swDate');
var DAYS = ['SUN','MON','TUE','WED','THU','FRI','SAT'];
function tick() {
var d = new Date();
var h = d.getHours() % 12 || 12;
timeEl.textContent = h + ':' + ('0' + d.getMinutes()).slice(-2);
dateEl.textContent = DAYS[d.getDay()] + ' ' + d.getDate();
}
tick();
setInterval(tick, 5000);
// Animate the activity rings using stroke-dasharray, like Apple Watch.
var rings = [
{ el: document.querySelector('.r1'), r: 42, pct: 0 },
{ el: document.querySelector('.r2'), r: 32, pct: 0 },
{ el: document.querySelector('.r3'), r: 22, pct: 0 }
];
rings.forEach(function (ring) {
var c = 2 * Math.PI * ring.r;
ring.circ = c;
ring.el.style.strokeDasharray = c;
ring.el.style.strokeDashoffset = c;
});
var closed = false;
function setRings(targets) {
rings.forEach(function (ring, i) {
ring.el.style.strokeDashoffset = ring.circ * (1 - targets[i]);
});
}
document.getElementById('swToggle').addEventListener('click', function () {
closed = !closed;
document.getElementById('swScreen').querySelector('.sw-rings').style.opacity = closed ? '1' : '.32';
setRings(closed ? [0.86, 0.72, 0.95] : [0, 0, 0]);
this.textContent = closed ? 'Reset rings' : 'Close rings';
});Step by step
How to Use
- 1Paste HTML, CSS, and JSA CSS smartwatch renders with a live face and dim activity rings.
- 2Watch the clockThe time and date update live on the watch face.
- 3Press close ringsThe three rings sweep from empty to their targets.
- 4Reset themToggle again to animate the rings back to empty.
- 5Recolor the ringsChange the stroke colors for your own metrics.
- 6Feed real dataDrive setRings() from live fitness values.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Each ring is an SVG circle whose stroke-dasharray equals its circumference (2πr) and whose stroke-dashoffset starts at the full circumference, hiding the stroke. Setting the offset to circumference × (1 - percent) reveals that fraction, and a 1-second CSS transition makes it sweep. A -90° rotation starts the fill at the top and round line caps soften the ends.
No, it's pure CSS. The case is a heavily-rounded square with an inset edge shadow, the bands are gradient spans tucked behind it with negative z-index, and the crown and button are slivers on the edge. Because there's no photo, the watch stays sharp at any size and every part can be recolored.
Yes. A tick function reads the current Date, formats a 12-hour time, and builds an abbreviated day-and-date label from a day-name array, updating on an interval. The bright green numerals over the black face mimic an always-on OLED display.
Yes. Each ring's circumference is precomputed, so setting a new fill is a single stroke-dashoffset assignment via the setRings() function with three percentages. Pass live move, exercise, and stand values (0 to 1) and the rings animate to match; feed device time into the clock the same way.
Make the watch a component and bind each ring's strokeDashoffset to a percent in state, updating it from your data. Put the clock interval in a mount effect with cleanup. Expose a slot to swap the whole face. In Tailwind, build the case and bands with rounded utilities and gradients, and keep the rings as an inline SVG with bound offsets.




