Tutorials/Power Apps/Mastering Phone Input Masks in Power Apps: A Flexible Mask System
Power Appsintermediate

Mastering Phone Input Masks in Power Apps: A Flexible Mask System

Create custom input masks for phone numbers and other patterns using a dynamic configuration table and real-time formatting.

NA
Narmer Abader
@narmer · Published June 3, 2026

Standard canvas app text inputs do not support automatic input masking — a feature that prevents invalid characters and inserts separators like hyphens or spaces as the user types. This is especially valuable for phone numbers, where country‑specific patterns (e.g., (###) ###-#### for the US, +## #### ###### for the UK) greatly improve data quality and user experience.

While Power Apps lacks a built‑in mask control, we can build a flexible mask engine using a simple workaround. This article walks through a reusable method that lets you define masks per country and apply formatting in real time.

Scenario: International Partner Contact Form

Your organisation works with suppliers from different regions. You need a single form where the user selects a country and, as they start typing the phone number, the placeholder automatically formats according to that country’s standard. The system must allow easy updates – a non‑developer should be able to change masks later.

Imagine a Partner Details screen with these controls:

ControlNamePurpose
DropdownddlCountryChoose a country (e.g., US, UK, Japan)
Text InputtxtPhoneWhere the formatted phone number appears
SlidersldTrackerHidden control that triggers formatting after each keystroke
LabellblFormatShows the current mask pattern for reference (hidden in production)

We will store the mask patterns in a collection that can be populated from a SharePoint list or environment variable for easy remote updates.

Step 1: Configure the mask patterns

First we create a collection that maps each country code to its desired mask. A mask is a string of characters where # represents a digit and all other characters are inserted verbatim.

Open the App.OnStart property and add the following code.

powerfxApp.OnStart – mask definitions
ClearCollect(
  colMasks,
  {Country: "US",    Mask: "(###) ###-####"},
  {Country: "UK",    Mask: "+## #### ######"},
  {Country: "Japan", Mask: "(##)-####-####"},
  {Country: "India", Mask: "+## ####-######"}
);
Set(
  gblSelectedCountry,
  First(colMasks)
);

In a production app, you might pull this data from a SharePoint list named PhoneMasks and use Collect(colMasks, Filter(PhoneMasks, Active=true)).

Step 2: Build the mask parser

To apply formatting, we need to know, for each digit position in the incoming phone number, what the corresponding visible mask pattern is. We parse the mask string once and store the result in a collection.

Create a new screen and add the controls listed in the scenario above. In the OnSelect property of a button (or directly in App.OnStart), we run the parser whenever the selected country changes.

powerfxMask parser – run after country selection
/* Build a table of mask "chunks": one row per digit position,
 containing the cumulative mask string and the count of # seen so far */
ClearCollect(
  colMaskDigits,
  With(
      {varMask: LookUp(colMasks, Country = ddlCountry.Selected.Value).Mask},
      With(
          {
              varSeq: ForAll(
                  Sequence(Len(varMask)),
                  With(
                      {varLeft: Left(varMask, Value)},
                      {
                          Pos: Value,
                          PartialMask: varLeft,
                          DigitCount: CountRows(MatchAll(varLeft, "#"))
                      }
                  )
              )
          },
          ForAll(
              Sequence(Max(varSeq, DigitCount)),
              LookUp(varSeq, DigitCount = Value)
          )
      )
  )
);

The resulting table colMaskDigits has one row per expected digit position (e.g., for (###) ###-#### there are 10 rows). Each row contains the cumulative mask string PartialMask and the DigitCount up to that point.

Step 3: Real‑time formatting with a slider workaround

Power Apps text inputs only fire OnChange when the control loses focus, not after every keystroke. To format as the user types, we use an invisible slider whose Default property is tied to the length of txtPhone.Text. The slider’s OnChange then fires immediately on each character change.

Set the slider’s Default property:

powerfxSlider Default
Len(txtPhone.Text)

Now write the core formatting logic in the slider’s OnChange:

powerfxSlider OnChange – real‑time formatting
/* Extract only digit characters from the text */
Set(
  gblDigitsOnly,
  Concat(
      MatchAll(txtPhone.Text, Match.Digit),
      FullMatch
  )
);

/* Apply the mask by formatting the number string as a numeric value
 using the mask from the colMaskDigits table for the current length */
Set(
  gblFormatted,
  With(
      {pos: Len(gblDigitsOnly)},
      If(
          pos > 0,
          Text(
              Value(gblDigitsOnly),
              LookUp(colMaskDigits, DigitCount = pos, PartialMask)
          ),
          ""
      )
  )
);

/* Force the text input to show the formatted value */
Reset(txtPhone);

Finally, set txtPhone.Default to gblFormatted. Because we call Reset inside the slider’s OnChange, the text input is re‑evaluated with the new formatted string. This does not cause an infinite loop because the reset clears the input and triggers the slider again only if the length changes, but by then the digits only variable is unchanged if the user hasn’t typed more. The technique works reliably.

Step 4: Limit input length

Set txtPhone.MaxLength to the length of the selected mask string. This prevents the user from exceeding the number of available digit slots.

powerfxMaxLength of txtPhone
Len(LookUp(colMasks, Country = ddlCountry.Selected.Value).Mask)

If you want to enforce that only digits are typed, you can also handle OnChange of the text input to strip non‑digit characters, but the mask already does that – any non‑digit keystroke will be ignored because the slider’s Default doesn’t change (Len stays the same). To be safe, consider adding a quick validation:

powerfxtxtPhone OnChange – optional digit check
If(
  CountRows(MatchAll(Self.Text, Match.Digit)) < Len(Self.Text),
  Notify("Only numbers are allowed", NotificationType.Warning)
)

Step 5: Hide helper controls

In production, the slider, country dropdown, and mask label can be tucked away:

  • sldTracker.Visible = false
  • lblFormat.Visible = false
  • ddlCountry can be kept visible or hidden if the country is determined from another source.

The final screenshot should show the txtPhone control gracefully formatting as numbers are entered.

Performance and delegation notes

  • The mask parser uses ForAll over the length of the mask (usually fewer than 20 characters). This is negligible and does not hit delegation limits.
  • The MatchAll function in the formatting logic processes only the current text input length, which is also trivial.
  • If you store masks in a SharePoint list, be aware that the LookUp inside the slider fires on every keystroke. Keep the mask list small (under 500 items) or cache the selected mask into a variable.
  • For large‑scale apps, consider moving the mask definition to an environment variable to reduce network calls.

Common mistakes and troubleshooting

  • Slider not advancing: Ensure the slider’s Default is bound to Len(txtPhone.Text). If the slider is invisible, test with a visible label to confirm the length is changing.
  • Format resets to empty: The Reset(txtPhone) inside OnChange will clear the input if gblFormatted is empty. Always check that gblDigitsOnly is not blank before formatting.
  • Backspace deletes formatting wrongly: The current method re‑applies the mask based on digit count, so deleting a digit correctly removes the last digit and reformats. That works because Len(gblDigitsOnly) decreases.
  • Country change causes errors: When the user picks a different country, re‑run the parser (Step 2) and also clear gblFormatted and txtPhone.Text to avoid stale digits.

Final recommendation

While this workaround is effective, consider packaging it into a reusable component (a Canvas Component) that encapsulates the slider, the formatting logic, and the mask configuration. Export the component as a solution for reuse across multiple apps. For enterprise scenarios, also consider adding a fallback to a custom control built with PCF if you need full cross‑browser compatibility and advanced features like cursor‑aware editing.

The approach shown here gives a flexible, easy‑to‑maintain input mask system that works for any country or arbitrary pattern – from tax IDs to postal codes. By externalising the mask definitions, you empower non‑developers to manage formatting changes without touching app logic.

References