Excel VBA Progress Bar: How to Build a Responsive One

VBA & macros · Updated November 2, 2026

Excel VBA Progress Bar: How to Build a Responsive One

An Excel VBA progress bar shows how much of a long-running macro has finished. For a visual bar, create a UserForm with a track and a fill label, then update the fill as your code processes items; for a simpler display, use Excel’s status bar with Application.StatusBar.

What an Excel VBA progress bar does

A progress indicator gives users feedback while a macro runs. Without one, Excel may appear to have stopped responding even when VBA is still working. A bar can show an approximate completion percentage, while nearby text can say what the macro is processing, such as “Processing item 75 of 500.”

VBA does not provide a ready-made progress-bar control for ordinary worksheets. The common visual approach is to build a small UserForm and change the width of a label inside a track. The status bar is a built-in alternative that needs no form design. Neither approach makes the macro itself run faster; it communicates progress and, when used with care, gives Excel opportunities to refresh its interface.

Choose a UserForm or the status bar

Choose the display that fits the task rather than adding a visual form automatically. A UserForm is prominent and can include a message or a Cancel button, but it requires a little setup and screen space. The status bar is unobtrusive and quick to implement, though users can miss it and it offers less room for detail.

  • Use a UserForm when the task takes long enough that users need a visible, dedicated indicator, or when you want to show descriptive text.
  • Use the status bar for a lightweight indicator during a batch operation when a percentage or short message is enough.
  • Skip frequent updates for very short macros. Updating the interface too often can add overhead without giving users useful information.

Progress is meaningful only when the macro can estimate the total work. A loop over a known number of rows is straightforward. A task that discovers more work as it runs may need an indeterminate message such as “Working…” rather than a percentage that repeatedly changes its denominator.

Build the visual bar with a UserForm

In Excel for Microsoft 365 on Windows, press Alt+F11 to open the Visual Basic Editor. Choose Insert > UserForm, then set the form’s (Name) property to frmProgress. Add a Frame named fraTrack, a Label named lblFill inside the frame, and a separate Label named lblText for the percentage and message. Set the fill label’s full width in the designer, place it at the left edge of the track, and give it a contrasting BackColor. The example records that designed width, then reduces it to zero.

Double-click the form and put this code in the form’s code module. The percentage is bounded between zero and one, so an accidental value outside the expected range cannot make the fill exceed the track. The text label displays both the percentage and the supplied message.

Option Explicit

Private mBarWidth As Single

Private Sub UserForm_Initialize()
    ' Keep the designed width as the bar's maximum width.
    mBarWidth = lblFill.Width
    lblFill.Width = 0
    lblText.Caption = "Starting..."
End Sub

Public Sub UpdateProgress(ByVal completed As Long, _
                          ByVal total As Long, _
                          Optional ByVal message As String = "")
    Dim fraction As Double

    If total <= 0 Then
        fraction = 0
    Else
        fraction = CDbl(completed) / CDbl(total)
    End If

    If fraction < 0 Then fraction = 0
    If fraction > 1 Then fraction = 1

    lblFill.Width = mBarWidth * fraction
    lblText.Caption = Format$(fraction, "0%") & " " & message
End Sub

Call the form from a macro

This example processes the nonblank range of rows identified by column A on a worksheet named Data. It writes uppercase versions of those values to column B. The work is deliberately simple; replace the example operation with the task your macro actually needs to perform. Before running any macro, save a copy of the workbook: VBA changes cannot be undone with Ctrl+Z.

Option Explicit

Public Sub ProcessRowsWithProgress()
    Dim ws As Worksheet
    Dim progress As frmProgress
    Dim lastRow As Long
    Dim rowNum As Long
    Dim total As Long
    Dim errorText As String

    On Error GoTo CleanFail

    Set ws = ThisWorkbook.Worksheets("Data")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    If lastRow < 2 Then
        MsgBox "There are no data rows to process.", vbInformation
        Exit Sub
    End If

    total = lastRow - 1
    Set progress = New frmProgress
    progress.Show vbModeless
    progress.UpdateProgress 0, total, "Starting..."
    DoEvents

    For rowNum = 2 To lastRow
        ' Example task: copy column A to column B in uppercase.
        ws.Cells(rowNum, "B").Value = _
            UCase$(CStr(ws.Cells(rowNum, "A").Value))

        If (rowNum - 1) Mod 25 = 0 Or rowNum = lastRow Then
            progress.UpdateProgress rowNum - 1, total, _
                "Processing item " & (rowNum - 1) & " of " & total
            DoEvents
        End If
    Next rowNum

    progress.UpdateProgress total, total, "Complete"
    DoEvents
    Unload progress
    Set progress = Nothing
    MsgBox "Processing is complete.", vbInformation
    Exit Sub

CleanFail:
    errorText = Err.Description
    On Error Resume Next
    If Not progress Is Nothing Then Unload progress
    On Error GoTo 0
    MsgBox "The macro stopped: " & errorText, vbExclamation
End Sub

Put the main procedure in a standard module, not in the UserForm’s code module. The form must exist under the name used in the declaration. The sample assumes the first row is a header and that column A identifies the last row to process. If your data has a different layout, adjust the worksheet name, columns, and starting row together.

Update at sensible intervals

The sample refreshes after every 25 items and on the final item. This is a practical compromise for many row-based tasks: updating on every iteration can slow a macro, particularly when there are thousands of rows, while updating only at the end defeats the purpose of a progress indicator. The right interval depends on how expensive each unit of work is. For a slow operation per item, updating every item may be reasonable; for a fast loop, update every several dozen or several hundred items.

DoEvents lets Excel process pending interface events, which can allow the modeless form to repaint. It also lets other events be handled while the procedure is running, so it should not be inserted indiscriminately into code that must not be interrupted or re-entered. Keep the work between updates bounded, and test the macro with realistic data. A progress display is an estimate of completed units, not a guarantee of remaining time: some rows or operations may take much longer than others.

Use the status bar instead

To avoid building a form, write a message to Excel’s status bar. The following standalone example uses the same worksheet and row assumptions. It stores the existing status-bar value and restores it on both normal completion and error. As with the form example, save a copy of the workbook before running it.

Option Explicit

Public Sub ProcessRowsWithStatusBar()
    Dim ws As Worksheet
    Dim lastRow As Long
    Dim rowNum As Long
    Dim total As Long
    Dim previousStatus As Variant
    Dim errorText As String

    previousStatus = Application.StatusBar
    On Error GoTo CleanFail

    Set ws = ThisWorkbook.Worksheets("Data")
    lastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row

    If lastRow < 2 Then GoTo CleanUp
    total = lastRow - 1

    For rowNum = 2 To lastRow
        ' Example task: copy column A to column B in uppercase.
        ws.Cells(rowNum, "B").Value = _
            UCase$(CStr(ws.Cells(rowNum, "A").Value))

        If (rowNum - 1) Mod 25 = 0 Or rowNum = lastRow Then
            Application.StatusBar = "Processing " & _
                Format$((rowNum - 1) / total, "0%") & _
                " - item " & (rowNum - 1) & " of " & total
            DoEvents
        End If
    Next rowNum

CleanUp:
    Application.StatusBar = previousStatus
    Exit Sub

CleanFail:
    errorText = Err.Description
    Application.StatusBar = previousStatus
    MsgBox "The macro stopped: " & errorText, vbExclamation
End Sub

Setting Application.StatusBar to a string temporarily replaces Excel’s normal status-bar message. Restoring the value saved at the start avoids leaving a stale progress message behind. This example exits quietly if there are no data rows; change that behavior if you prefer to display a completion or no-data message.

Common problems and fixes

  • The bar does not move: Check that UpdateProgress is called inside the loop, that the form and controls have the exact names used in the code, and that updates are not so infrequent that the macro finishes between them.
  • The form appears frozen: A modeless form still needs Excel to process interface events. Call DoEvents at appropriate intervals, and avoid a long, uninterrupted block of work between calls.
  • The percentage is wrong: Confirm that total counts the same units as completed. In the example, the header is excluded, so the first data row is item 1, not worksheet row 2.
  • The bar jumps or finishes early: A fixed denominator works only if the total is known. If the macro skips rows, counts multiple phases, or adds work dynamically, calculate progress using the actual completed units and a consistent total.
  • The status bar stays changed: Make sure the procedure restores Application.StatusBar on every exit path, including error handling. Avoid using an abrupt End statement, which can bypass normal cleanup.

Safety, compatibility, and alternatives

VBA macros run in desktop Excel; Excel for the web cannot run VBA macros. The code here targets Excel for Microsoft 365 on Windows. Excel for Mac also supports VBA and UserForms, but the Visual Basic Editor shortcuts and interface can differ; use the Developer tab or Excel’s macro tools to open the editor if the Windows Alt+F11 shortcut does not apply. Form layout and control rendering can also vary across systems, so check the form on the computers where it will be used.

Only enable macros in workbooks you trust. A progress bar does not make a macro safer, and code that edits cells can overwrite data. Save a separate copy before testing or running it, especially if the procedure changes or deletes workbook content. For a process that needs no custom VBA, Excel’s built-in calculation indicators or a worksheet-based status message may be enough; for more complex automation, consider logging completed stages to a worksheet so users can inspect what happened after the macro finishes.

Frequently Asked Questions

Can I make a progress bar without a UserForm?
Yes. Set Application.StatusBar to a short message or percentage and restore its previous value when the macro exits. This is simpler, but it is less prominent and offers less design flexibility than a UserForm.

Why does Excel say “Not Responding” while my macro runs?
VBA usually runs on Excel’s main interface thread. A long stretch of work without yielding can prevent the window from repainting. Periodic, carefully placed DoEvents calls can help the interface update, but they do not make the computation run in parallel.

Can users cancel a macro from the progress bar?
Yes, but cancellation needs deliberate design. You can add a Cancel button to the UserForm, set a public cancellation flag in its click event, and have the main procedure check that flag between work units. Do not assume that closing the form automatically stops the running procedure safely.

Can a progress bar show an accurate time remaining?
Not reliably in every macro. A time estimate can be calculated from elapsed time and the fraction completed, but it assumes the remaining work will take roughly as long per unit as the work already done. Uneven tasks make the estimate fluctuate or mislead.

Will this VBA progress bar work in Excel for the web?
No. Excel for the web does not run VBA macros. A workbook that depends on this code needs to be opened in desktop Excel; web-based automation requires a different approach, such as Office Scripts where supported.

Related

Try it: the Column Letter to Number: aA is 27, XFD is 16,384. Converts both ways.

More in VBA & macros

Sources: Function behaviour and menu paths: Microsoft Support documentation for Excel. Method & sources

Spotted an error in this guide? Let us know.