webdevpuneet.com

Mobile Workout In-Progress Screen — Free Snippet

Mobile Workout In-Progress Screen · Mobile · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="wip-phone">
  <div class="wip-screen">
    <div class="wip-status"><span>9:41</span><span class="wip-batt"><i></i></span></div>

    <header class="wip-head">
      <button class="wip-close" id="wipClose" aria-label="End workout">&times;</button>
      <div class="wip-head-mid">
        <b>Upper Body Strength</b>
        <span id="wipExerciseCount">Exercise 1 of 5</span>
      </div>
      <span class="wip-spacer"></span>
    </header>

    <div class="wip-progress-track"><div class="wip-progress-fill" id="wipProgressFill"></div></div>

    <div class="wip-body">
      <p class="wip-exercise-name" id="wipExerciseName">Barbell Bench Press</p>

      <div class="wip-set-ring">
        <svg viewBox="0 0 120 120" width="150" height="150">
          <circle cx="60" cy="60" r="52" fill="none" stroke="#1e293b" stroke-width="10"/>
          <circle cx="60" cy="60" r="52" fill="none" stroke="#22c55e" stroke-width="10" stroke-linecap="round"
            id="wipRingProgress" stroke-dasharray="326.7" stroke-dashoffset="326.7" transform="rotate(-90 60 60)"/>
        </svg>
        <div class="wip-set-ring-label">
          <b id="wipSetNum">1</b>
          <span>of <span id="wipSetTotal">4</span> sets</span>
        </div>
      </div>

      <div class="wip-set-input">
        <div class="wip-field">
          <label>Reps</label>
          <div class="wip-stepper">
            <button id="wipRepsMinus" aria-label="Decrease reps">&minus;</button>
            <span id="wipRepsVal">8</span>
            <button id="wipRepsPlus" aria-label="Increase reps">+</button>
          </div>
        </div>
        <div class="wip-field">
          <label>Weight (lb)</label>
          <div class="wip-stepper">
            <button id="wipWeightMinus" aria-label="Decrease weight">&minus;</button>
            <span id="wipWeightVal">135</span>
            <button id="wipWeightPlus" aria-label="Increase weight">+</button>
          </div>
        </div>
      </div>

      <button class="wip-log-btn" id="wipLogBtn">Log Set</button>

      <div class="wip-rest" id="wipRest" hidden>
        <p class="wip-rest-label">Rest</p>
        <b class="wip-rest-timer" id="wipRestTimer">1:30</b>
        <div class="wip-rest-actions">
          <button id="wipRestMinus15">&minus;15s</button>
          <button id="wipRestSkip">Skip rest</button>
          <button id="wipRestPlus15">+15s</button>
        </div>
      </div>

      <div class="wip-log-list" id="wipLogList"></div>
    </div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

SVG progress ring fills based on completed sets out of total sets, not a generic spinner
Shared adjust() function drives both reps and weight steppers with clamped min/max ranges
Logging a set simultaneously records history, advances the set counter, and starts rest
Real 90-second rest countdown with working +15s / -15s and skip controls
Automatic exercise-complete state disables logging once the final set is reached
Most-recent-set-first log list for fast mid-workout scanning
Phone-frame mockup with status bar, close button, and exercise progress header
Zero dependencies, vanilla JavaScript only

About this UI Snippet

Mobile Workout In-Progress Screen — Set Ring, Rest Timer & Live Log

Screenshot of the Mobile Workout In-Progress Screen snippet rendered live

A workout screen used mid-set has one requirement above everything else: it has to be operable with sweaty thumbs between reps, not read like a static exercise database entry. This snippet builds the in-progress state of a strength workout — a current-set progress ring, quick +/- steppers for reps and weight, a Log Set button, and a real countdown rest timer — inside a phone-frame mockup, with every control doing something real rather than sitting there for decoration.

A ring that shows completed sets, not a generic loading spinner

updateRing() computes fraction = (currentSet - 1) / totalSets and sets the SVG circle's stroke-dashoffset from a precomputed circumference, so the ring visually fills in as sets are completed rather than representing time or a vague percentage. Reading currentSet and totalSets directly out of the same variables that drive the "of 4 sets" label underneath keeps the ring and the label from ever showing conflicting numbers.

Reps and weight as steppers, not a text input

adjust(varName, delta, min, max) handles both the reps and weight fields through one shared function, clamped to a sane range (1–30 reps, 0–500 lb) so a mis-tap can't produce a nonsensical logged value. Steppers were chosen deliberately over a free-text number input — during an actual set, tapping a large plus/minus button is far more reliable than typing on a small keyboard.

Logging a set drives three things at once

logBtn's click handler appends a row to #wipLogList with the exact reps and weight from state, then either advances currentSet and calls updateRing() plus startRest(), or — once currentSet reaches totalSets — disables the button and relabels it "Exercise Complete." This means logging is the single action that both records history and advances the workout state machine, rather than two separate steps a user could get out of sync.

A real rest countdown with quick adjustments

startRest() sets a 90-second countdown via setInterval, formatted with fmtTime(), and reveals a rest panel with ±15-second adjustment buttons and a "Skip rest" control that calls endRest() to clear the interval immediately. This matches how real training apps handle rest — a fixed default that respects a lifter's actual pace, adjustable in either direction without waiting for the whole timer to finish or restart.

A running log, most recent set first

Each completed set is prepended (not appended) to #wipLogList, so the most recently logged set is always the first one visible without needing to scroll down through the whole session — useful mid-workout when the only thing worth checking is "what did I just lift."

Wiring it to persistent workout data

Replace the in-memory reps/weight/currentSet variables with state synced to your workout-tracking backend (saving each logged set via an API call inside the log button's handler), and pull totalSets, starting reps, and starting weight from the user's programmed workout plan or their last session's data instead of the hardcoded defaults.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA workout screen renders with set 1 of 4, a green progress ring, and reps/weight steppers.
  2. 2
    Adjust reps and weightTap the +/- buttons on each stepper to set the values for the set you are about to log.
  3. 3
    Tap "Log Set"The set is added to the log list, the ring advances, and a 90-second rest timer starts automatically.
  4. 4
    Adjust or skip the rest timerUse +15s / -15s to adjust, or "Skip rest" to end it immediately and move on.
  5. 5
    Complete all setsAfter the final set is logged, the button relabels to "Exercise Complete" and disables itself.
  6. 6
    Wire it to real dataPersist each logged set to your workout-tracking backend and load totalSets/starting values from the user's program.

Real-world uses

Common Use Cases

Fitness and strength-training apps
Model the exact in-progress screen a lifter interacts with between sets, distinct from a workout summary or history view.
Personal training and coaching apps
Adapt the set-ring and rest-timer pattern for coach-programmed workouts with prescribed rep and weight targets per set.
Wearable-companion mobile apps
Pair with a Mobile Fitness Screen dashboard as the detail screen a user drills into mid-workout.
Teaching SVG progress-ring and stepper patterns
A concrete reference for computing stroke-dashoffset from application state and building clamped increment/decrement controls.
Related: Mobile Medication Reminder Screen
See the Mobile Medication Reminder Screen for a related timed mobile-tracking pattern worth comparing against this one.
Related: Mobile Fitness Screen
See the Mobile Fitness Screen for a related fitness dashboard pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

updateRing() computes fraction = (currentSet - 1) / totalSets and sets the SVG circle's stroke-dashoffset to circumference * (1 - fraction), so the ring visually represents completed sets out of the total rather than a generic or time-based percentage.

No — the shared adjust() function clamps both fields with Math.max/Math.min against a min and max passed in for each field (1 to 30 for reps, 0 to 500 for weight), so repeated taps cannot push either value outside a sane range.

The log button's click handler checks currentSet >= totalSets before advancing. On the final set, it relabels the button to "Exercise Complete", disables it, and skips starting another rest timer, instead of looping back or logging an extra set.

startRest() sets restSeconds to 90 and runs a setInterval that decrements it once per second, updating the displayed mm:ss text via fmtTime() and formatting it. The +15s/-15s buttons adjust restSeconds directly, and "Skip rest" calls endRest() to clear the interval and hide the panel immediately.

During an active workout, the most useful information is what was just lifted. Prepending keeps the newest entry at the top of the visible log without the user needing to scroll down past earlier sets to find it.

Yes. Track currentSet, reps, weight, and restSeconds in state, derive the ring's stroke-dashoffset from currentSet and totalSets in a computed value, and run the rest countdown inside a useEffect-managed interval that is cleared on skip, completion, or unmount.