Excel UserForm in VBA: How to Create, Show, Load and Close One, With Working Code

An Excel UserForm is a custom dialog box you build in the VBA editor to collect and check input. This guide builds an invoice picker that corrects table rows, and covers Load versus Show, events, tab order, closing cleanly, and 64-bit, Mac and browser limits.

Last checked: 16 September 2026. Code targets 64-bit Microsoft 365 Excel for Windows. To add new rows with full validation, see how to create a data entry form in Excel, which uses the same register. New to macros? Start with what a macro is and how to enable macros safely.

How to create a UserForm in Excel VBA: the short version

  1. Save as .xlsm and press Alt+F11 to open the Visual Basic Editor.
  2. Choose Insert > UserForm and set its (Name) and Caption in the Properties window (F4).
  3. Drag controls from the Toolbox and name each one, such as txtNet.
  4. Right-click the form, choose View Code, and fill lists and defaults in UserForm_Initialize.
  5. Write Click code for the buttons: check entries, write to the table, close.
  6. In a standard module, add a macro that shows the form and attach it to a sheet button.

Microsoft's Create a User Form page covers step 2. The Developer tab is needed only for the sheet button in step 6 (show the Developer tab).

What is an Excel UserForm?

A UserForm is a window or dialog box that forms part of your macro's interface (UserForm object). Its code module responds to events like a click. A form earns its place when people enter or correct records away from the sheet; if a table with a few drop-down lists does the job, skip it.

UserForm controls in the Toolbox

The Toolbox offers the 14 standard Microsoft Forms controls below, each with an ID such as Forms.TextBox.1 (Add method).

ControlTypical finance usePrefix
LabelCaptions, status messageslbl
TextBoxInvoice number, amounttxt
ComboBoxSupplier, VAT ratecbo
ListBoxList of invoiceslst
CheckBoxVAT included, paidchk
OptionButtonDebit or creditopt
ToggleButtonAn on/off switchtgl
FrameGrouping related controlsfra
CommandButtonSave, Closebtn
MultiPageHeader and lines pagesmpg
TabStripTabs over shared controlstab
SpinButtonStepping a period numberspn
ScrollBarScrolling through valuesscr
ImageA company logoimg

The example form used in this guide

Every snippet assumes these names:

UserForm design basics: tab order, alignment and keyboard access

Make the form work from the keyboard:

How to show a UserForm in Excel VBA

Call the form's Show method from a macro in a standard module (Insert > Module). If the form isn't loaded yet, VBA loads it first (Show method).

Option Explicit

Public Sub OpenInvoiceEditSimple()
    frmInvoiceEdit.Show
End Sub

This uses the form's default instance, fine for a quick test. For a sheet button, choose Developer > Insert > Button (Form Control), draw it and assign the macro. A UserForm can't sit on a sheet itself.

How to load, show, hide and unload a UserForm

Use Load to put a form in memory, Show to display (and if needed load) it, Hide to take it off screen while keeping its values, and Unload to remove it.

StatementWhat happensEvents that runValues afterwards
Load frmInvoiceEditIn memory, not visible (Load)InitializeFresh defaults
frmInvoiceEdit.ShowLoads if needed, then displaysInitialize (if not loaded), ActivateAs set by Initialize
Me.HideOff screen, still loaded (Hide)No load or close eventsKept, readable by code
Unload MeRemoved from memory (Unload)QueryClose; Terminate once nothing refers to itGone

The classic trap: a hidden form keeps its old entries when shown again, because Initialize doesn't re-run. Hide only when the calling macro still needs the values.

How to initialize a UserForm in VBA

Put setup code in UserForm_Initialize, which runs after the form loads and before it appears (Initialize event). This goes at the top of the form's code module:

Option Explicit

Private m_Row As Long          ' table row being edited (1 = first data row, 0 = none)
Private m_SavedCount As Long

Public Property Get SavedCount() As Long
    SavedCount = m_SavedCount
End Property

Private Sub UserForm_Initialize()
    Dim lo As ListObject
    Set lo = ThisWorkbook.Worksheets("Lists").ListObjects("tblSupplierList")
    Select Case lo.ListRows.Count
        Case 0
            ' no suppliers yet: leave the list empty
        Case 1
            Me.cboSupplier.AddItem CStr(lo.DataBodyRange.Cells(1, 1).Value)
        Case Else
            Me.cboSupplier.List = lo.ListColumns(1).DataBodyRange.Value
    End Select
    Me.txtInvoiceNo.Locked = True  ' the invoice number identifies the row
    LoadInvoiceList
End Sub

Assigning a column to .List loads it in one step (List property); a single cell isn't an array, hence AddItem. A locked box can't be edited. SavedCount tells the calling macro what happened.

Initialize runs once per load, unlike Activate. Don't call Me.Show inside Initialize: the form appears before setup finishes, and a modal form pauses the rest of Initialize until it closes.

Filling a multi-column ListBox

Set ColumnCount and assign a block of cells. The first four table columns are Date, Supplier, Invoice No and Net:

Private Sub LoadInvoiceList()
    Dim lo As ListObject
    Set lo = ThisWorkbook.Worksheets("Register").ListObjects("tblPurchaseRegister")
    m_Row = 0
    Me.btnSave.Enabled = False     ' nothing to save until a row is picked
    Me.lstInvoices.Clear
    If lo.ListRows.Count = 0 Then Exit Sub
    With Me.lstInvoices
        .ColumnCount = 4
        .ColumnWidths = "70 pt;140 pt;80 pt;70 pt"
        .List = lo.DataBodyRange.Resize(, 4).Value
    End With
End Sub

List rows and columns count from zero, so Me.lstInvoices.List(Me.lstInvoices.ListIndex, 2) is the selected invoice number. ListIndex is -1 when nothing is selected (ListIndex).

Editing the selected record

Selecting a row runs the ListBox's Click event (Click event). The list was loaded straight from the table, so list row 0 is table row 1:

Private Sub lstInvoices_Click()
    Dim lo As ListObject
    Dim r As Range
    If Me.lstInvoices.ListIndex = -1 Then Exit Sub

    Set lo = ThisWorkbook.Worksheets("Register").ListObjects("tblPurchaseRegister")
    m_Row = Me.lstInvoices.ListIndex + 1
    Set r = lo.ListRows(m_Row).Range
    Me.txtInvoiceNo.Value = CStr(r.Cells(1, lo.ListColumns("Invoice No").Index).Value)
    Me.txtDate.Value = CStr(r.Cells(1, lo.ListColumns("Date").Index).Value)
    SelectSupplier CStr(r.Cells(1, lo.ListColumns("Supplier").Index).Value)
    Me.txtNet.Value = CStr(r.Cells(1, lo.ListColumns("Net").Index).Value)
    Me.txtVAT.Value = CStr(r.Cells(1, lo.ListColumns("VAT").Index).Value)
    Me.btnSave.Enabled = True
End Sub

Private Sub SelectSupplier(ByVal supplierName As String)
    Dim i As Long
    Me.cboSupplier.ListIndex = -1
    For i = 0 To Me.cboSupplier.ListCount - 1
        If StrComp(Me.cboSupplier.List(i, 0), supplierName, vbTextCompare) = 0 Then
            Me.cboSupplier.ListIndex = i
            Exit For
        End If
    Next i
End Sub

If a supplier has since left tblSupplierList, SelectSupplier leaves the box empty for the user to pick again. Because the form is modal, nobody can sort the table meanwhile.

Modal vs modeless UserForms

A modal form (the default) blocks the rest of Excel and pauses the calling macro until it is hidden or unloaded. A modeless form lets people keep working.

QuestionModal (vbModal, default)Modeless (vbModeless)
How to set itfrm.Show or frm.Show vbModalfrm.Show vbModeless, or ShowModal = False in the Properties window (read-only at run time)
Does the calling macro wait?Yes, until hidden or unloadedNo, code after Show runs at once
Good forData entry, editing recordsA lookup panel beside a ledger
Watch out forNothing after Show runs until it closesNo taskbar entry; data can be lost if the project recompiles; must save its own work

Sources: Show method, ShowModal property.

UserForm events: which one to use when

Typical order: Initialize, Activate, control events, QueryClose, Terminate.

EventWhen it runsUse it to
UserForm_InitializeAfter loading, before showingFill lists, set defaults
UserForm_ActivateWhen the visible form becomes activeRefresh totals
lstInvoices_ClickWhen the user selects a rowLoad the record
btnSave_ClickWhen clicked, or Enter on the Default buttonValidate and save
txtNet_BeforeUpdateBefore changed data is committed, ahead of AfterUpdate and ExitField checks
UserForm_QueryCloseBefore the form closes, whatever the causeHandle the X button
UserForm_TerminateAfter unloadingFinal clean-up

Sources: BeforeUpdate, QueryClose, Terminate. End stops Terminate from running, so close forms with Unload, never End.

Events for controls added in code: the WithEvents wiring

VBA links a handler such as btnSave_Click automatically only for controls placed in the designer. A control created with Me.Controls.Add needs a WithEvents variable:

Private WithEvents m_btnExport As MSForms.CommandButton

Private Sub UserForm_Initialize()
    Set m_btnExport = Me.Controls.Add("Forms.CommandButton.1", "btnExport")
    With m_btnExport
        .Caption = "Export"
        .Left = 12: .Top = 180: .Width = 72: .Height = 24
    End With
End Sub

Private Sub m_btnExport_Click()
    MsgBox "Export clicked", vbInformation
End Sub

All three parts must exist: the module-level WithEvents variable, the Set, and a handler named variable_Event. Miss one and the button does nothing. WithEvents works only in class modules (form modules count), not with New and not as an array (Dim statement).

In the example form, put the WithEvents line at the top of the module (declarations below a procedure won't compile) and merge the Set and With lines into the existing UserForm_Initialize.

How to validate UserForm entries before saving

Check entries when the user clicks Save: explain the first problem, put the cursor there and stop. For duplicate checks and appending rows, see building a validated data entry form.

Private Function InputIsValid() As Boolean
    If m_Row = 0 Then
        MsgBox "Pick an invoice from the list first.", vbExclamation, "Invoice edit"
    ElseIf Not IsDate(Me.txtDate.Value) Then
        ShowProblem Me.txtDate, "Enter a valid invoice date."
    ElseIf Me.cboSupplier.ListIndex = -1 Then
        ShowProblem Me.cboSupplier, "Pick a supplier from the list."
    ElseIf Not IsAmount(Me.txtNet.Value, False) Then
        ShowProblem Me.txtNet, "Enter a net amount above zero and below 1 trillion."
    ElseIf Not IsAmount(Me.txtVAT.Value, True) Then
        ShowProblem Me.txtVAT, "Enter VAT of zero or more, below 1 trillion."
    Else
        InputIsValid = True
    End If
End Function

Private Function IsAmount(ByVal entry As String, ByVal allowZero As Boolean) As Boolean
    Dim n As Double
    If Not IsNumeric(entry) Then Exit Function
    n = CDbl(entry)
    If n >= 1000000000000# Then Exit Function
    IsAmount = (n > 0) Or (allowZero And n = 0)
End Function

Private Sub ShowProblem(ByVal ctl As MSForms.Control, ByVal message As String)
    MsgBox message, vbExclamation, "Check this entry"
    ctl.SetFocus
End Sub

For instant feedback on one field, BeforeUpdate can keep focus in the box until it is fixed. Use it sparingly. While the entry is invalid, focus cannot move to other controls, so the user can't click away until it is fixed.

Private Sub txtNet_BeforeUpdate(ByVal Cancel As MSForms.ReturnBoolean)
    If Len(Me.txtNet.Value) > 0 And Not IsNumeric(Me.txtNet.Value) Then
        Cancel = True
        Me.lblStatus.Caption = "Net must be a number."
    Else
        Me.lblStatus.Caption = vbNullString
    End If
End Sub

How to write UserForm data back to a table

ListRows(n).Range returns row n of the table, counting from 1 (ListRows.Item). Writing by column name survives inserted columns; the calculated Gross column is left alone. A pivot table picks up the change when you refresh the pivot table.

Private Sub btnSave_Click()
    Dim lo As ListObject
    Dim r As Range
    Dim oldFormulas As Variant
    Dim failMsg As String

    On Error GoTo SaveFailed
    If Not InputIsValid() Then Exit Sub

    Set lo = ThisWorkbook.Worksheets("Register").ListObjects("tblPurchaseRegister")
    Set r = lo.ListRows(m_Row).Range
    oldFormulas = r.Formula                  ' snapshot for undo
    r.Cells(1, lo.ListColumns("Date").Index).Value = CDate(Me.txtDate.Value)
    r.Cells(1, lo.ListColumns("Supplier").Index).Value = Me.cboSupplier.Value
    r.Cells(1, lo.ListColumns("Net").Index).Value = _
        Application.WorksheetFunction.Round(CDbl(Me.txtNet.Value), 2)
    r.Cells(1, lo.ListColumns("VAT").Index).Value = _
        Application.WorksheetFunction.Round(CDbl(Me.txtVAT.Value), 2)

    LoadInvoiceList                          ' show the corrected values
    m_SavedCount = m_SavedCount + 1
    Exit Sub

SaveFailed:
    failMsg = Err.Description
    Resume RestoreRow
RestoreRow:
    On Error Resume Next
    If Not IsEmpty(oldFormulas) Then r.Formula = oldFormulas
    On Error GoTo 0
    MsgBox "The change was not saved: " & failMsg, vbExclamation, "Invoice edit"
End Sub

If anything fails part-way, the handler restores the row's original values and formulas. After a save, Save stays disabled until a row is picked again. To add rows instead, use ListRows.Add.

How to close a UserForm in VBA

Use Unload Me when the form does all its own work, or Me.Hide when a calling macro still needs its values. Either way, handle the title-bar X: it raises QueryClose, whose CloseMode says why the form is closing (QueryClose event).

CloseMode constantValueCause
vbFormControlMenu0The user clicked X or the Control menu's Close
vbFormCode1Code ran an Unload statement
vbAppWindows2Windows is shutting down
vbAppTaskManager3Task Manager is closing Excel
Private Sub btnClose_Click()
    Me.Hide
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = vbFormControlMenu Then
        Cancel = True        ' keep the form in memory...
        Me.Hide              ' ...and treat X exactly like Close
    End If
End Sub

As Integer is the event's required signature. Routing X through Close gives the calling macro one outcome.

Reusing a form: let the calling macro own it

Because this form hides itself, the default-instance macro would show the old selection next time. Instead, create a new instance with New (UserForm object), show it, read its properties, then unload it.

Option Explicit

Public Sub OpenInvoiceEdit()
    Dim frm As frmInvoiceEdit
    On Error GoTo Failed

    Set frm = New frmInvoiceEdit    ' Initialize runs here
    frm.Show vbModal                ' the macro waits until the form hides
    If frm.SavedCount > 0 Then
        MsgBox frm.SavedCount & " invoice change(s) saved.", vbInformation, "Invoice edit"
    End If
    Unload frm
    Exit Sub

Failed:
    MsgBox "Invoice edit stopped: " & Err.Description, vbExclamation, "Invoice edit"
    Resume CleanUp                  ' leave error-handling mode first
CleanUp:
    On Error Resume Next
    If Not frm Is Nothing Then Unload frm
End Sub

Replace OpenInvoiceEditSimple with this (keep one Option Explicit per module) and attach OpenInvoiceEdit to the sheet button. If someone renames the Lists sheet, the macro shows a message instead of stopping in the editor (with VBA's default error trapping).

64-bit Excel and UserForms

64-bit has been the default install since Office 2019 and Microsoft 365 (64-bit VBA overview). The code above makes no Windows API calls, so it needs no changes. Older form code trips on two things:

#If VBA7 Then
    Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
        (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
#Else
    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
        (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
#End If

Do UserForms work on a Mac, in Excel for the web or with ActiveX disabled?

Not in Excel for the web. On a Mac they generally run, but community experts say you can't design them there. Microsoft's ActiveX change doesn't mention UserForms.

Office Scripts isn't a drop-in replacement: scripts reach only the workbook and run when a user or Power Automate starts them (Office Scripts vs VBA). See VBA vs Office Scripts vs Python.

How HISAB 360 builds and checks UserForms

HISAB 360's AI builds UserForms in Windows desktop Excel as a guided multi-step workflow in chat, not a one-click generator.

The limits, plainly:

Not for: anyone who wants to lay out controls by hand in the VBA editor, or teams whose IT blocks VBA project access. For a one-off macro, try the free VBA macro generator, or see building VBA macros and a ribbon from chat.

Frequently asked questions

What is an Excel UserForm?

A UserForm is a custom dialog box built in Excel's VBA editor. It holds controls such as text boxes, drop-downs and buttons, plus a code module that responds to events like clicks. Finance teams use them to enter or correct records such as supplier invoices, checking each entry.

How do I show a UserForm in Excel VBA?

Add a macro to a standard module that calls the form's Show method, such as frmInvoiceEdit.Show, and attach it to a Form Control button on the sheet. Show loads the form first if needed. For reusable forms, create an instance with New, show it, then unload it.

How do I load a UserForm in Excel VBA?

Use Load frmInvoiceEdit to put the form in memory without showing it; its UserForm_Initialize event runs at that point. Show loads a form automatically, so most macros skip Load. Use Load only when code must set controls before the form appears, and unload the form when finished.

How do I initialize a UserForm in VBA?

Put setup code in the UserForm_Initialize event in the form's code module. It runs after the form loads and before it appears, so use it to fill lists and set defaults. Don't call Me.Show inside Initialize; let the calling macro show the form.

How do I close a UserForm in VBA?

Use Unload Me when the form has finished; it clears the form from memory. Use Me.Hide when a calling macro still needs the entries, then unload the form from that macro. Handle the title-bar X in UserForm_QueryClose, and never close forms with End, which skips Terminate.

How do I put a UserForm on an Excel sheet?

You can't embed a UserForm in a worksheet; it always opens as its own window. Place a Form Control button on the sheet (Developer, Insert, Button) and assign it a macro that shows the form. For controls on the sheet itself, use Form Controls or data validation.

Do UserForms work on a Mac or in Excel for the web?

Not in Excel for the web, which can't run or edit VBA macros, although the code stays in the file. On a Mac, community experts say forms built on Windows run but can't be designed there, and ActiveX controls aren't available. Build forms on Windows and test them on a Mac.

Try HISAB 360 on your own workbook

HISAB 360 is an AI assistant inside Windows desktop Excel for accountants and finance teams. It builds and checks VBA macros and UserForms, and connects two-way to QuickBooks, Xero, Zoho Books, Odoo and FreshBooks. The 15-day trial is the full product, no card required.

Start free → See pricing