The Definitive Guide to Building a Reusable Time Picker Component
Overcome the standard control library's shortcomings by crafting a custom, native-feeling time selector in a reusable canvas app component.
The gap between Power Apps' native control library and the expectations of modern touch-based user interfaces is most obvious when you need to select a specific time. Cascading dropdowns for hours, minutes, and periods technically function, but they introduce unnecessary friction for the user. A custom time picker component solves this elegantly, and building one from scratch teaches you the core concepts of canvas app componentization, state management, and the surprising flexibility of the Power Fx formula language.
This guide walks you through creating a polished, reusable Time Picker component. You will learn how to structure three synchronized galleries, handle the edge cases of a 12-hour clock, and package everything into an asset you can reuse across your entire organization.
The Scenario: Shift Scheduling for a Healthcare Team
Imagine you are building a shift management tool for a hospital ward. Nurses log their exact start and end times. The current solution uses three cascading dropdowns for hour, minute, and AM/PM. It is slow, prone to selection errors, and does not look professional on a mobile device.
Your new component, cmp_TimeWheel, will replace these dropdowns. It must:
- Accept a
DefaultTimeinput so the current shift can be pre-populated. - Expose a
SelectedTimeoutput for writing to the data source. - Look and feel like the native scroll-and-tap pickers users expect.
Building the Component Shell
Start in the Components pane in Power Apps Studio. Create a new component and name it cmp_TimeWheel.
Set the following base properties:
- Width:
285 - Height:
400
Create the input and output properties defined in the requirements.
- Input Property:
DefaultTime(Type:Time, Default value:Time(9, 0, 0)).- Critical: Check the Raise OnReset when value changes checkbox for this property. This ensures the galleries react when the host app modifies the default.
- Output Property:
SelectedTime(Type:Time). We will bind this to an internal variable later. - Input Properties for Styling:
HighlightColor(Type:Color, Default:White),HighlightFill(Type:Color, Default:ColorFade(Color.Blue, 50%)).
Internal State Variable
The component needs a private variable to hold the user's active selection. We will use a uniquely named global variable set via the Set() function. While this technically creates a global variable, scoping it within the component with a distinct prefix keeps it isolated.
Add a Set() call inside the component's OnVisible or simply rely on the galleries' OnSelect to initialize it. The SelectedTime output property will simply hold var_TimeWheel_Selected.
Wiring the Three Galleries
A time picker is essentially three synchronized vertical lists. Add three blank vertical galleries to the component.
| Gallery Name | Width | TemplateSize | Items Formula |
|---|---|---|---|
gal_Hour | Parent.Width / 3 | 65 | ForAll(Sequence(12), Text(Value, "[$-en-US]00")) |
gal_Minute | Parent.Width / 3 | 65 | ForAll(Sequence(60, 0, 1), Text(Value, "[$-en-US]00")) |
gal_Period | Parent.Width / 3 | 65 | ["AM", "PM"] |
ForAll(Sequence(12), Text(Value, "[$-en-US]00"))
ForAll(Sequence(60, 0, 1), Text(Value, "[$-en-US]00"))
["AM", "PM"]
Notice the minute gallery uses Sequence(60, 0, 1) which starts at 0, avoiding any off-by-one subtraction logic.
Inside each gallery template, place a single Label control.
Label_Hour:X: 0, Y: 0, Width: Parent.TemplateWidth, Height: Parent.TemplateHeight, Text: ThisItem.Value, Align: CenterLabel_Minute:X: 0, Y: 0, Width: Parent.TemplateWidth, Height: Parent.TemplateHeight, Text: ThisItem.Value, Align: CenterLabel_Period:X: 0, Y: 0, Width: Parent.TemplateWidth, Height: Parent.TemplateHeight, Text: ThisItem.Value, Align: Center
Styling the Selection State
A good time picker provides strong visual feedback for the selected item. We will use the component's input properties to dynamically style the selected row in each gallery.
For the Color property of each label:
If(ThisItem.IsSelected, cmp_TimeWheel.HighlightColor, Color.Black)
For the Fill property of each label:
If(ThisItem.IsSelected, cmp_TimeWheel.HighlightFill, Transparent)
For hover effects, add a subtle color fade:
If(!ThisItem.IsSelected, ColorFade(cmp_TimeWheel.HighlightFill, 70%), cmp_TimeWheel.HighlightFill)
Finally, set ShowScrollbar to false for all three galleries. This prevents the scrollbar from appearing on the narrow lists, creating that smooth, wheel-like visual effect.
The Mathematical Magic: OnSelect and Default Values
This is the crux of the component. We need to convert the three gallery selections into a single Time value.
Assembling the Time (OnSelect)
Hold down the Ctrl key and click each gallery in the tree view to multi-select them. Any property change you make will apply to all three simultaneously.
In the OnSelect property, write the following formula. This ensures the time is calculated the moment any gallery item is tapped.
Set(
var_TimeWheel_Selected,
Time(
Mod(Value(gal_Hour.Selected.Value), 12)
+ If(gal_Period.Selected.Value = "PM", 12, 0),
Value(gal_Minute.Selected.Value),
0
)
)Why this works:
Mod(Value(gal_Hour.Selected.Value), 12): Converts 12 to 0, and keeps 1–11 as is.+ If(Period = "PM", 12, 0): Adds 12 for PM hours (0 becomes 12, 1 becomes 13, etc.).- The result perfectly maps to a 24-hour format hour component for the
Time()function.
Set the component's output property SelectedTime to var_TimeWheel_Selected.
Defaulting the Galleries
When the component loads (or resets), each gallery must snap to the correct value based on the DefaultTime input property.
Default property of gal_Hour:
The 12th hour is a special case. Mod(12, 12) equals 0, which converts to text "00". Since our gallery does not have a "00" item (it has "12"), we must catch this case with an explicit If statement.
With(
{
wCurrent: Coalesce(var_TimeWheel_Selected, cmp_TimeWheel.DefaultTime)
},
LookUp(
Self.AllItems.Value,
Value = Text(
If(Mod(Hour(wCurrent), 12) = 0, 12, Mod(Hour(wCurrent), 12)),
"[$-en-US]00"
)
)
)Default property of gal_Minute:
With(
{
wCurrent: Coalesce(var_TimeWheel_Selected, cmp_TimeWheel.DefaultTime)
},
LookUp(
Self.AllItems.Value,
Value = Text(Minute(wCurrent), "[$-en-US]00")
)
)Default property of gal_Period:
With(
{
wCurrent: Coalesce(var_TimeWheel_Selected, cmp_TimeWheel.DefaultTime)
},
If(Hour(wCurrent) < 12, {Value: "AM"}, {Value: "PM"})
)Resetting the Component
The OnReset behavior of the component should clear the internal state and force the galleries to re-evaluate their Default properties.
Set(var_TimeWheel_Selected, Blank()); Reset(gal_Hour); Reset(gal_Minute); Reset(gal_Period)
Now, if an app screen sets the DefaultTime property of the component instance to Time(14, 30, 0), and then calls Reset(cmp_TimeWheel1), the galleries will correctly snap to 02, 30, PM.
Integrating the Picker into an App
Using the component on a screen is straightforward.
- Add a Text input control. Set its
Defaultproperty to display the selected time.
Text(cmp_TimeWheel1.SelectedTime, ShortTime)
- Add a Button or Icon next to the text input to show or hide the component.
- Add an Overlay label behind the component to capture clicks outside and hide the picker.
// OnSelect of the trigger icon/button Set(varShowPicker, true) // OnSelect of the overlay label Set(varShowPicker, false) // Visible property of cmp_TimeWheel1 varShowPicker
Common Mistakes and How to Fix Them
| Symptom | Likely Cause | Fix |
|---|---|---|
| Galleries do not snap to the correct default value. | Raise OnReset is not checked on the DefaultTime input property, or the Coalesce function is failing because var_TimeWheel_Selected is being set globally outside the component. | Check the Raise OnReset box. Ensure var_TimeWheel_Selected is only set inside the component. |
| 12:00 AM displays as 12:00 PM. | The Mod / If logic in the Default property of the hour gallery is missing the 12-to-0 conversion. | Implement the If(Mod(Hour(wCurrent),12)=0, 12, ...) pattern shown above. |
| The component is sluggish. | Large TemplateSize or animation disabled. | Keep TemplateSize under 70. Ensure the gallery Transition property is set to an appropriate animation (e.g., Pop, Push). |
| The picker looks incomplete or has no border. | The component itself does not have a border. | Add a Label control at the bottom of the tree view with Fill: Transparent, BorderColor: Color.Black, BorderThickness: 2, Height: Parent.Height, Width: Parent.Width. |
| I want to use this component in multiple apps. | It is currently local to a single app. | Publish cmp_TimeWheel to a Component Library. This allows you to import it into any canvas app and maintain a single source of truth for bug fixes and enhancements. |
Performance and Delegation Notes
- Data Sources: The
Itemsproperties for the galleries rely entirely on local formulas (Sequence, static arrays). There is no delegation risk here because we are not querying external data sources. - LookUp Function: The
LookUpfunction used in theDefaultproperties operates solely onSelf.AllItems, which is an in-memory collection built from the localItemsformula. This lookup is instantaneous and fully delegable to the extent that it needs to be (it is all local). - State Management: Using a single component variable (
var_TimeWheel_Selected) for the state is efficient. Avoid creating complex collections or making expensive calls within theOnSelectof the galleries.
Why Build a Reusable Component?
Following the steps above results in more than just a time picker for one screen. It gives you a portable asset:
- Consistency: Every time picker in your organization looks and behaves exactly the same.
- Maintainability: Fix a bug or enhance the styling in one place, and it propagates to every app using the component library.
- Speed: Future apps skip the "build a picker" step and move straight to feature development.
Building this component also deepens your understanding of Power Apps at a platform level—component lifecycles, input/output properties, and the elegant math of time conversion.
Try it out on your next scheduling, booking, or logging app. Your users will appreciate the speed and your teammates will appreciate the reusability.
References
- Matthew Devaney. Make A Time Picker In Power Apps. November 2020.
- Microsoft. Create a canvas app component. Microsoft Learn.
- Microsoft. Sequence function in Power Apps. Microsoft Learn.
- Microsoft. Time function in Power Apps. Microsoft Learn.
Create a professional signature popup using the Pen Input control and save the result to a SharePoint document library with Power Automate.
Practical techniques for building collections that exceed the standard delegation threshold, including concurrent batch loading, dynamic filtering with ForAll, returning large datasets from Power Automate, and importing static Excel data.
Eliminate the login requirement for new team members: use a Power Automate flow to force-sync users and update the Dataverse team when they are added to an Entra security group.