PowerApps Component Libraries: Build Once, Use Everywhere
- Navigate to PowerApps Portal
- Click gear icon (⚙️) → Plan(s)
- Verify "Power Apps Premium" or "Power Apps per app" is listed
-
Verify environment access:
- In PowerApps portal, check environment dropdown (top-right)
- Ensure you can see and select your target environment
- You should be able to create apps (not just view)
-
Check permissions:
- Go to Power Platform Admin Center
- Select your environment → Settings → Users + permissions
- Verify you have "Environment Maker" role or higher
Step 1: Create Your First Component Library
Create the Library
-
Navigate to PowerApps Studio:
- Go to https://make.powerapps.com
- Select your environment from dropdown
-
Create Component Library:
- Click + Create (left sidebar)
- Select Component library (under "Start from")
- Name:
ContosoDesignSystem - Format: Tablet (1366x768 for flexibility)
- Click Create
-
Understand the Interface:
Architecture Overview: PowerApps Studio Component Library Mode
Create Your First Component: Custom Button
-
Add New Component:
- In Tree View, click + New component
- Name it:
ctButton(ct = custom component naming convention) - Set dimensions: Width = 200, Height = 60
-
Design the Button:
-
Add Rectangle (background):
Properties: - Name: btnBackground - X: 0, Y: 0 - Width: Parent.Width - Height: Parent.Height - Fill: ColorValue("#0078D4") // Microsoft Blue - BorderRadius: 8 -
Add Label (text):
Properties: - Name: lblButtonText - X: 0, Y: 0 - Width: Parent.Width - Height: Parent.Height - Text: "Button" - Color: Color.White - Font: Font.'Segoe UI' - FontWeight: FontWeight.Semibold - Size: 14 - Align: Align.Center
-
-
Add Hover Effect:
-
Select
btnBackground -
Fill property:
If( btnBackground.OnSelect, ColorValue("#005A9E"), // Darker on press If( btnBackground.Hover, ColorValue("#106EBE"), // Slightly darker on hover ColorValue("#0078D4") // Default blue ) )
-
Add Custom Properties
-
Create Input Properties:
Property 1: Text (what the button displays)
- Select component
ctButtonin Tree View - Properties pane → + New custom property
- Display name:
Text - Name:
Text - Description:
The text to display on the button - Property type: Input
- Data type: Text
- Default value:
"Click Me" - Click Create
Property 2: OnSelectAction (what happens when clicked)
- + New custom property
- Display name:
OnSelectAction - Name:
OnSelectAction - Description:
Action to execute when button is clicked - Property type: Input
- Data type: Boolean
- Default value:
false - Click Create
Property 3: ButtonStyle (visual variant)
- + New custom property
- Display name:
Style - Name:
ButtonStyle - Description:
Visual style: Primary, Secondary, or Danger - Property type: Input
- Data type: Text
- Default value:
"Primary" - Click Create
Property 4: IsDisabled (disabled state)
- + New custom property
- Display name:
IsDisabled - Name:
IsDisabled - Description:
Whether the button is disabled - Property type: Input
- Data type: Boolean
- Default value:
false - Click Create
- Select component
-
Create Output Property:
Property: Pressed (signal when clicked)
- + New custom property
- Display name:
Pressed - Name:
Pressed - Description:
True when button is pressed - Property type: Output
- Data type: Boolean
- Click Create
Wire Up the Properties
-
Connect Label Text to Custom Property:
-
Select
lblButtonText -
Text property:
ctButton.Text
-
-
Apply Style Variants:
- Select `btnBackground`
- **Fill** property (replace previous):
```powerFx
With(
{
isPrimary: ctButton.ButtonStyle = "Primary",
isSecondary: ctButton.ButtonStyle = "Secondary",
isDanger: ctButton.ButtonStyle = "Danger",
isPressed: btnBackground.Pressed,
isHovered: btnBackground.Hover,
isDisabled: ctButton.IsDisabled
},
If(
isDisabled,
ColorValue("#E0E0E0"), // Disabled gray
If(
isPressed,
// Pressed colors
If(isPrimary, ColorValue("#005A9E"),
If(isSecondary, ColorValue("#8A8886"),
ColorValue("#A4262C"))), // Danger dark
If(
isHovered,
// Hover colors
If(isPrimary, ColorValue("#106EBE"),
If(isSecondary, ColorValue("#605E5C"),
ColorValue("#C50F1F"))), // Danger hover
// Default colors
If(isPrimary, ColorValue("#0078D4"),
If(isSecondary, ColorValue("#8A8886"),
ColorValue("#D13438"))) // Danger default
)
)
)
)
11. **Update Label Color for Variants**:
```sql
- Select `lblButtonText`
- **Color** property:
```powerFx
If(
ctButton.IsDisabled,
ColorValue("#A0A0A0"), // Gray text when disabled
If(
ctButton.ButtonStyle = "Secondary",
ColorValue("#323130"), // Dark text for secondary
Color.White // White text for primary/danger
)
)
12. **Handle OnSelect and Output**:
```sql
- Select `btnBackground`
- **OnSelect** property:
```powerFx
If(
!ctButton.IsDisabled,
UpdateContext({_componentPressed: true});
ctButton.OnSelectAction;
UpdateContext({_componentPressed: false})
)
-
Select component
ctButtonin Tree View -
Pressed output property formula:
btnBackground.Pressed
13. **Add Accessibility**:
```sql
- Select `btnBackground`
- **AccessibleLabel** property:
```powerFx
ctButton.Text & If(ctButton.IsDisabled, ", disabled", "")
- TabIndex:
0
### Test the Component
14. **Add Test Screen**:
> **Architecture Overview:** Click ** New screen** → **Blank**
15. **Insert Component**:
> **Architecture Overview:** Click ** Insert** → **Custom** → **ctButton**
16. **Configure Test Instances**:
```text
```powerFx
// Primary Button
Component1:
Text: "Primary Button"
ButtonStyle: "Primary"
IsDisabled: false
OnSelectAction: Notify("Primary clicked!", NotificationType.Success)
// Secondary Button
Component2:
Text: "Secondary Button"
ButtonStyle: "Secondary"
OnSelectAction: Notify("Secondary clicked!", NotificationType.Information)
// Danger Button
Component3:
Text: "Delete Item"
ButtonStyle: "Danger"
OnSelectAction: Notify("Danger clicked!", NotificationType.Warning)
// Disabled Button
Component4:
Text: "Disabled Button"
IsDisabled: true
17. **Test Interactions**:
> **Architecture Overview:** Click **▶ Play** (top right)
## Step 2: Build Advanced Component - Data Table
### Create DataTable Component
1. **Add New Component**:
- **+ New component**
- Name: `ctDataTable`
- Width: 800, Height: 600
2. **Add Custom Properties**:
**Input Properties**:
```text
Property: Items
- Type: Input
- Data type: Table
- Description: "Data source for the table"
Property: Columns
- Type: Input
- Data type: Table
- Description: "Column configuration"
- Default: Table({Name: "Column1", Field: "Field1"})
Property: ShowHeader
- Type: Input
- Data type: Boolean
- Default: true
Property: AlternateRows
- Type: Input
- Data type: Boolean
- Default: true
Property: RowHeight
- Type: Input
- Data type: Number
- Default: 50
Output Properties:
Property: SelectedItem
- Type: Output
- Data type: Record
- Description: "Currently selected row"
Property: SelectedIndex
- Type: Output
- Data type: Number
- Description: "Index of selected row"
-
Build Header Section:
-
Add Gallery (horizontal):
Name: galHeader Items: ctDataTable.Columns Template size: Parent.Width / CountRows(ctDataTable.Columns) Height: 50 X: 0, Y: 0 Width: Parent.Width -
Header Label (inside gallery):
Name: lblHeaderColumn Text: ThisItem.Name Fill: ColorValue("#F3F2F1") Color: ColorValue("#323130") Font: Font.'Segoe UI' FontWeight: FontWeight.Bold Size: 14 Align: Align.Center BorderColor: ColorValue("#D2D0CE") BorderThickness: 1
-
-
Build Data Rows:
-
Add Gallery (vertical):
Name: galRows Items: ctDataTable.Items Y: galHeader.Y + galHeader.Height X: 0 Width: Parent.Width Height: Parent.Height - galHeader.Height Template size: ctDataTable.RowHeight -
Row Background:
Name: rectRowBackground Fill: If( galRows.Selected = ThisItem, ColorValue("#DEECF9"), // Selected: light blue If( ctDataTable.AlternateRows && Mod(ThisItem.Value, 2) = 0, ColorValue("#FAF9F8"), // Even: light gray Color.White // Odd: white ) ) -
Add Horizontal Gallery for Cells:
Name: galCells Items: ctDataTable.Columns Template size: Parent.Width / CountRows(ctDataTable.Columns) Height: Parent.Height -
Cell Label (inside galCells):
Name: lblCellValue Text: // Dynamic field lookup Switch( ThisItem.Field, "Field1", galRows.Selected.Field1, "Field2", galRows.Selected.Field2, "Field3", galRows.Selected.Field3, "Field4", galRows.Selected.Field4, // Add more as needed, or use sophisticated lookup "" ) // Better approach using Index/Match pattern: Text: LookUp( ForAll( ctDataTable.Columns, { ColName: Name, ColValue: Switch( Field, "Name", galRows.Selected.Name, "Email", galRows.Selected.Email, "Status", galRows.Selected.Status, // Dynamic field access "" ) } ), ColName = ThisItem.Name ).ColValue
-
-
Connect Output Properties:
-
Select
ctDataTablecomponent -
SelectedItem output:
galRows.Selected -
SelectedIndex output:
galRows.Selected.Value
-
-
Add Search/Filter Capability:
-
Add custom property:
Property: SearchText - Type: Input - Data type: Text - Default: "" -
Update
galRows.Items:If( IsBlank(ctDataTable.SearchText), ctDataTable.Items, Filter( ctDataTable.Items, ctDataTable.SearchText in Name || ctDataTable.SearchText in Email || ctDataTable.SearchText in Status ) )
-
Test DataTable Component
-
Create Test Data Collection:
-
Add button on test screen: "Load Test Data"
-
OnSelect:
ClearCollect( colEmployees, Table( {ID: 1, Name: "John Doe", Email: "john@contoso.com", Status: "Active", Department: "Sales"}, {ID: 2, Name: "Jane Smith", Email: "jane@contoso.com", Status: "Active", Department: "IT"}, {ID: 3, Name: "Bob Johnson", Email: "bob@contoso.com", Status: "Inactive", Department: "HR"}, {ID: 4, Name: "Alice Williams", Email: "alice@contoso.com", Status: "Active", Department: "Marketing"}, {ID: 5, Name: "Charlie Brown", Email: "charlie@contoso.com", Status: "Active", Department: "Finance"} ) ); ClearCollect( colTableColumns, Table( {Name: "Name", Field: "Name"}, {Name: "Email", Field: "Email"}, {Name: "Status", Field: "Status"}, {Name: "Department", Field: "Department"} ) )
-
-
Insert DataTable Component:
-
Insert
ctDataTableon test screen -
Configure:
Items: colEmployees Columns: colTableColumns ShowHeader: true AlternateRows: true RowHeight: 60
-
-
Add Search Box:
Name: txtSearch HintText: "Search employees..." // Connect to DataTable: ctDataTable1.SearchText: txtSearch.Text -
Display Selected Item:
```powerFx
Name: lblSelectedEmployee
Text: "Selected: " & ctDataTable1.SelectedItem.Name &
" (" & ctDataTable1.SelectedItem.Email & ")"
## Step 3: Create Form Component with Validation

### Build Smart Form Component
1. **Create Component**:
- Name: `ctSmartForm`
- Width: 600, Height: 500
2. **Add Custom Properties**:
```text
Input Properties:
- FormFields (Table): Field definitions
Default: Table({FieldName: "Name", FieldType: "Text", Required: true})
- FormData (Record): Current form data
- ShowValidation (Boolean): Show validation errors
Output Properties:
- IsValid (Boolean): All fields valid
- ValidationErrors (Table): List of errors
- FormValues (Record): All field values
-
Build Dynamic Form Layout:
-
Add Vertical Gallery:
Name: galFormFields Items: ctSmartForm.FormFields Template size: 100 -
Field Label:
Text: ThisItem.FieldName & If(ThisItem.Required, " *", "") Color: ColorValue("#323130") FontWeight: FontWeight.Semibold -
Text Input:
Name: txtFieldInput Default: LookUp(ctSmartForm.FormData, Field = ThisItem.FieldName).Value HintText: "Enter " & ThisItem.FieldName BorderColor: If( ctSmartForm.ShowValidation && ThisItem.Required && IsBlank(txtFieldInput.Text), ColorValue("#D13438"), // Red for error ColorValue("#D2D0CE") // Gray default ) -
Validation Message:
Name: lblValidation Text: If( ctSmartForm.ShowValidation && ThisItem.Required && IsBlank(txtFieldInput.Text), ThisItem.FieldName & " is required", "" ) Color: ColorValue("#D13438") Size: 12 Visible: !IsBlank(Self.Text)
-
-
Implement Output Properties:
-
IsValid output:
CountRows( Filter( ctSmartForm.FormFields, Required && IsBlank(LookUp(galFormFields.AllItems, Field = FieldName).txtFieldInput.Text) ) ) = 0 -
ValidationErrors output:
Filter( AddColumns( ctSmartForm.FormFields, "CurrentValue", LookUp(galFormFields.AllItems, Field = FieldName).txtFieldInput.Text, "HasError", Required && IsBlank(LookUp(galFormFields.AllItems, Field = FieldName).txtFieldInput.Text) ), HasError )
-
Step 4: Publish and Share Component Library
Publish the Library
-
Save the Component Library:
- Click File → Save
- Ensure all components are error-free (no red warnings)
-
Publish:
- Click Publish (top-right, next to Save)
- Add version notes: "v1.0 - Initial release with Button, DataTable, and Form components"
- Click Publish this version
-
Verify Publication:
- Component library now shows "Published" status
- Version number displayed (1.0)
Share with Your Organization
-
Share the Library:
- Click File → Share
- Select Component library
- Choose sharing method:
- Everyone in organization (recommended for company-wide design systems)
- Specific people or groups (for department-specific libraries)
- Co-owners (for collaborative development)
-
Set Permissions:
Can use: Users can insert components in their apps Can edit: Users can modify the component library Co-owner: Full control including sharing -
Notify Users:
- Add message: "Contoso Design System v1.0 is now available! Use these components in your apps for consistent UI."
- Click Share
Version Control Best Practices
-
Create Version Documentation:
Component Library: ContosoDesignSystem Version 1.0 (2025-03-17) - Initial release - Components: ctButton, ctDataTable, ctSmartForm - Features: Responsive design, accessibility, theming Version 1.1 (planned) - Add: ctNavigationMenu, ctDialog, ctCard - Enhancement: Dark mode support - Fix: DataTable sorting
Step 5: Use Components in Canvas Apps
Import Component Library
-
Create New Canvas App:
- make.powerapps.com
- + Create → Canvas app from blank
- Name:
Employee Portal - Format: Tablet
-
Import Component Library:
- Click + Insert (left sidebar)
- Scroll to Get more components (bottom)
- Click Component libraries tab
- Search:
ContosoDesignSystem - Select and click Import
-
Verify Import:
- Insert pane now shows "Custom" section
- Your components listed:
ctButton,ctDataTable,ctSmartForm
Build App with Components
-
Create Employee List Screen:
Add Header:
Component: ctButton Text: "🏠 Home" ButtonStyle: "Secondary" X: 20, Y: 20 Title Label: Text: "Employee Directory" Size: 28 FontWeight: Bold X: 300, Y: 25Add Search:
Name: txtEmployeeSearch HintText: "Search employees..." X: 20, Y: 100 Width: 400Add New Employee Button:
Component: ctButton (ctBtnAddEmployee) Text: "+ Add Employee" ButtonStyle: "Primary" X: 440, Y: 100 OnSelectAction: Navigate(scrEmployeeForm, ScreenTransition.Fade); NewForm(frmEmployee)Add DataTable:
Component: ctDataTable Items: colEmployees Columns: colEmployeeColumns SearchText: txtEmployeeSearch.Text ShowHeader: true AlternateRows: true X: 20, Y: 180 Width: Parent.Width - 40 Height: Parent.Height - 200Load Data on Screen Visible:
Screen OnVisible: // Load employee data from SharePoint/Dataverse ClearCollect( colEmployees, 'Employees List' // Your data source ); ClearCollect( colEmployeeColumns, Table( {Name: "Name", Field: "FullName"}, {Name: "Department", Field: "Department"}, {Name: "Email", Field: "Email"}, {Name: "Status", Field: "EmploymentStatus"} ) ) -
Create Employee Form Screen:
Add Form Component:
Component: ctSmartForm (cmpEmployeeForm) FormFields: colFormFields ShowValidation: varShowValidation X: 100, Y: 100Define Form Fields:
Screen OnVisible: ClearCollect( colFormFields, Table( {FieldName: "Full Name", FieldType: "Text", Required: true}, {FieldName: "Email", FieldType: "Email", Required: true}, {FieldName: "Department", FieldType: "Text", Required: true}, {FieldName: "Job Title", FieldType: "Text", Required: true}, {FieldName: "Phone", FieldType: "Phone", Required: false} ) )Save Button:
Component: ctButton Text: "Save Employee" ButtonStyle: "Primary" OnSelectAction: UpdateContext({varShowValidation: true}); If( cmpEmployeeForm.IsValid, // Save to data source Patch( 'Employees List', Defaults('Employees List'), { FullName: LookUp(cmpEmployeeForm.FormValues, Field = "Full Name").Value, Email: LookUp(cmpEmployeeForm.FormValues, Field = "Email").Value, Department: LookUp(cmpEmployeeForm.FormValues, Field = "Department").Value, JobTitle: LookUp(cmpEmployeeForm.FormValues, Field = "Job Title").Value, Phone: LookUp(cmpEmployeeForm.FormValues, Field = "Phone").Value } ); Notify("Employee saved successfully!", NotificationType.Success); Navigate(scrEmployeeList, ScreenTransition.Fade), // Show validation errors Notify("Please fix validation errors", NotificationType.Error) )Cancel Button:
Component: ctButton Text: "Cancel" ButtonStyle: "Secondary" OnSelectAction: UpdateContext({varShowValidation: false}); Navigate(scrEmployeeList, ScreenTransition.Fade)
Step 6: Advanced Patterns - Theming
Implement Global Theme
-
Create Theme Variables (App OnStart):
// Primary colors Set(gblThemePrimary, ColorValue("#0078D4")); // Microsoft Blue Set(gblThemePrimaryDark, ColorValue("#005A9E")); Set(gblThemePrimaryLight, ColorValue("#106EBE")); // Secondary colors Set(gblThemeSecondary, ColorValue("#8A8886")); // Gray Set(gblThemeSecondaryDark, ColorValue("#605E5C")); Set(gblThemeSecondaryLight, ColorValue("#C8C6C4")); // Semantic colors Set(gblThemeSuccess, ColorValue("#107C10")); // Green Set(gblThemeWarning, ColorValue("#FF8C00")); // Orange Set(gblThemeDanger, ColorValue("#D13438")); // Red Set(gblThemeInfo, ColorValue("#0078D4")); // Blue // Neutrals Set(gblThemeTextPrimary, ColorValue("#323130")); // Dark gray Set(gblThemeTextSecondary, ColorValue("#605E5C")); Set(gblThemeBackground, ColorValue("#FFFFFF")); // White Set(gblThemeBackgroundAlt, ColorValue("#FAF9F8")); // Light gray Set(gblThemeBorder, ColorValue("#D2D0CE")); // Border gray // Spacing Set(gblSpacingXS, 4); Set(gblSpacingS, 8); Set(gblSpacingM, 16); Set(gblSpacingL, 24); Set(gblSpacingXL, 32); // Typography Set(gblFontFamily, Font.'Segoe UI'); Set(gblFontSizeSmall, 12); Set(gblFontSizeMedium, 14); Set(gblFontSizeLarge, 18); Set(gblFontSizeXL, 24); // Border radius Set(gblBorderRadiusSmall, 4); Set(gblBorderRadiusMedium, 8); Set(gblBorderRadiusLarge, 12); -
Update Component Library with Theme Support:
-
Open component library
-
Add custom property to each component:
Property: UseTheme - Type: Input - Data type: Boolean - Default: true
-
-
Update Button Component:
btnBackground.Fill: With( { isPrimary: ctButton.ButtonStyle = "Primary", isSecondary: ctButton.ButtonStyle = "Secondary", isDanger: ctButton.ButtonStyle = "Danger", useTheme: ctButton.UseTheme }, If( useTheme, // Use global theme variables If(isPrimary, gblThemePrimary, If(isSecondary, gblThemeSecondary, gblThemeDanger)), // Use hardcoded colors (fallback) If(isPrimary, ColorValue("#0078D4"), If(isSecondary, ColorValue("#8A8886"), ColorValue("#D13438"))) ) )
Dark Mode Support
-
Add Dark Mode Toggle:
// App OnStart - add dark mode variables Set(gblDarkModeEnabled, false); // Define dark mode palette Set(gblDarkThemeBackground, ColorValue("#1F1F1F")); Set(gblDarkThemeBackgroundAlt, ColorValue("#2D2D2D")); Set(gblDarkThemeTextPrimary, ColorValue("#FFFFFF")); Set(gblDarkThemeTextSecondary, ColorValue("#D0D0D0")); Set(gblDarkThemeBorder, ColorValue("#3D3D3D")); -
Create Dark Mode Toggle Button:
Architecture Overview: Component: ctButton
-
Apply to All Screens:
Screen.Fill: gblThemeBackground All Labels.Color: gblThemeTextPrimary All Containers.Fill: If(gblDarkModeEnabled, gblThemeBackgroundAlt, ColorValue("#FAF9F8"))
Step 7: Performance Optimization
Component Performance Best Practices
-
Minimize Complex Formulas:
// ❌ BAD: Recalculates on every interaction Gallery.Items: SortByColumns( Filter( Search( colData, txtSearch.Text, "Name", "Email", "Department" ), Status = "Active" ), "Name", Ascending ) // ✅ GOOD: Use collections with explicit updates Button OnSelect: ClearCollect( colFiltered, SortByColumns( Filter( Search(colData, txtSearch.Text, "Name", "Email", "Department"), Status = "Active" ), "Name", Ascending ) ); Gallery.Items: colFiltered -
Use Concurrent Function:
// Load multiple data sources in parallel App OnStart: Concurrent( ClearCollect(colEmployees, 'Employees List'), ClearCollect(colDepartments, Departments), ClearCollect(colProjects, Projects), Set(gblUserProfile, User()) ) -
Implement Lazy Loading:
// DataTable component - only load visible rows galRows.Items: FirstN( ctDataTable.Items, RoundUp(Parent.Height / ctDataTable.RowHeight, 0) + 5 ) // Scroll to load more galRows.OnScroll: If( galRows.ScrollPosition > 0.8, // Load next batch Collect(colLoadedItems, FirstN(ctDataTable.Items, CountRows(colLoadedItems) + 20)) ) -
Optimize Component Updates:
// Use UpdateContext instead of Set for local state UpdateContext({_localState: newValue}) // ✅ Faster, screen-scoped Set(globalState, newValue) // ❌ Slower, app-scoped // Debounce search input txtSearch.OnChange: UpdateContext({_searchPending: true}); Set(varSearchTimer, Now()); Timer: Duration: 500 // 500ms debounce Repeat: false OnTimerEnd: If( DateDiff(varSearchTimer, Now(), Milliseconds) >= 450, // Execute search ClearCollect(colSearchResults, Search(colData, txtSearch.Text, "Name")) )
Step 8: Accessibility Best Practices
Implement WCAG 2.1 AA Standards
-
Keyboard Navigation:
// Set TabIndex on all interactive elements ctButton.btnBackground.TabIndex: 0 // Logical tab order btnSave.TabIndex: 1 btnCancel.TabIndex: 2 btnDelete.TabIndex: 3 -
Screen Reader Support:
// Accessible labels for all components ctButton.AccessibleLabel: ctButton.Text & If(ctButton.IsDisabled, ", button disabled", ", button") & ", press Enter to activate" ctDataTable.AccessibleLabel: "Data table with " & CountRows(ctDataTable.Items) & " rows, " & CountRows(ctDataTable.Columns) & " columns" // Live regions for dynamic content lblNotification.Live: Live.Polite lblAlert.Live: Live.Assertive -
Color Contrast:
// Ensure WCAG AA contrast ratio (4.5:1 for normal text) // Check contrast with this formula: With( { bg: ColorValue("#0078D4"), // Background fg: ColorValue("#FFFFFF") // Foreground (text) }, // Simplified contrast calculation If( Abs( (Red(bg) * 0.299 + Green(bg) * 0.587 + Blue(bg) * 0.114) - (Red(fg) * 0.299 + Green(fg) * 0.587 + Blue(fg) * 0.114) ) / 255 > 0.5, "Pass", "Fail" ) ) -
Focus Indicators:
// Add visible focus state btnBackground.BorderColor: If( btnBackground.Focused, ColorValue("#000000"), // Black border when focused ColorValue("#D2D0CE") // Normal border ) btnBackground.BorderThickness: If(btnBackground.Focused, 2, 1)
Step 9: Testing and Quality Assurance
Component Testing Checklist
-
Create Test Matrix:
Component: ctButton Test Cases: ✓ Renders with default properties ✓ Accepts custom text property ✓ Hover state changes color ✓ Pressed state triggers OnSelectAction ✓ Disabled state prevents interaction ✓ Disabled state shows gray color ✓ Primary style shows blue ✓ Secondary style shows gray ✓ Danger style shows red ✓ Output property "Pressed" updates correctly ✓ Accessible label announces correctly ✓ Tab navigation works ✓ Enter key activates button ✓ Responsive sizing adapts to parent Edge Cases: ✓ Empty text displays ✓ Very long text wraps correctly ✓ Rapid clicking doesn't cause errors ✓ Multiple instances don't interfere -
Browser Testing:
Test in: - Edge (Windows) - Chrome (Windows, Mac) - Safari (Mac, iOS) - Mobile browsers (iOS Safari, Android Chrome) Features to verify: - Hover effects - Touch interactions - Responsive layout - Performance (< 2s load time) -
Data Testing:
// Test DataTable with various datasets // Empty data ctDataTable.Items: [] // Expected: Show "No data" message // Single row ctDataTable.Items: [{Name: "Test"}] // Expected: Render correctly // Large dataset (1000+ rows) ctDataTable.Items: colLargeDataset // Expected: Virtual scrolling, < 3s render // Special characters ctDataTable.Items: [{Name: "Test <>&\"'"}] // Expected: No XSS, correct display
Automated Testing with Power Apps Test Studio
-
Create Test Suite:
- Open canvas app in edit mode
- Advanced tools → Test Studio (preview feature)
- + New test case: "Component Library Tests"
-
Record Test Cases:
Test Case: Button Click 1. Navigate to test screen 2. Assert button is visible 3. Click button 4. Assert notification appears 5. Assert Pressed output = true Test Case: DataTable Selection 1. Navigate to table screen 2. Assert table has > 0 rows 3. Click row 2 4. Assert SelectedIndex = 2 5. Assert SelectedItem.Name = expected value Test Case: Form Validation 1. Navigate to form screen 2. Click Save without input 3. Assert validation errors visible 4. Assert IsValid = false 5. Enter required fields 6. Assert IsValid = true
Step 10: Documentation and Governance
Create Component Documentation
-
Component Catalog Document:
# Contoso Design System Component Library Version: 1.0 Last Updated: 2025-03-17 ## ctButton ### Description A customizable button component with multiple style variants, hover effects, and accessibility support. ### Properties **Input Properties:** - `Text` (Text): Button label text - `ButtonStyle` (Text): Visual variant - "Primary", "Secondary", "Danger" - `IsDisabled` (Boolean): Disabled state - `OnSelectAction` (Boolean): Action when clicked **Output Properties:** - `Pressed` (Boolean): True when button is being pressed ### Usage Example ```powerFx Component: ctButton Text: "Save Changes" ButtonStyle: "Primary" OnSelectAction: SubmitForm(frmEmployee) ```sql ### Screenshots [Insert component variants screenshots] ### Accessibility - WCAG 2.1 AA compliant - Keyboard navigable (Tab, Enter) - Screen reader compatible - 4.5:1 color contrast --- ## ctDataTable [Similar documentation for each component] -
Create PowerApps Template:
- Create sample app with all components
- Add documentation screen
- Include code examples
- Save as template: "Contoso Design System Starter"
-
Governance Policy:
# Component Library Governance ## Versioning - Semantic versioning: MAJOR.MINOR.PATCH - Major: Breaking changes - Minor: New features, backward compatible - Patch: Bug fixes ## Change Process 1. Submit change request via Teams/SharePoint 2. Design review by UX team 3. Development in separate branch 4. Testing by QA team 5. Approval by component library owner 6. Publish with version notes 7. Notify all app makers ## Support - Primary contact: [email protected] - Teams channel: Design System Support - Office hours: Monday/Wednesday 2-3pm ## Contribution Guidelines - Follow naming convention: ct[ComponentName] - Include accessibility features - Add comprehensive documentation - Test in all supported browsers - Provide usage examples
Real-World Example: Complete Enterprise App
Build Invoice Management App
// Screen 1: Invoice List
Screen: scrInvoiceList
Header:
```yaml
Component: ctButton
Text: "📊 Invoices"
ButtonStyle: "Primary"
Toolbar:
ctButton (btnNew):
Text: "+ New Invoice"
OnSelectAction: Navigate(scrInvoiceForm, ScreenTransition.Fade)
ctButton (btnExport):
Text: "📥 Export"
ButtonStyle: "Secondary"
OnSelectAction:
DownloadAs(
InvoiceDataTable.Items,
"Invoices.xlsx"
)
ctButton (btnRefresh):
Text: "🔄"
ButtonStyle: "Secondary"
OnSelectAction: Refresh('Invoices List')
Filters:
Dropdown (ddStatus):
Items: ["All", "Draft", "Sent", "Paid", "Overdue"]
Default: "All"
DatePicker (dpFrom):
DefaultDate: DateAdd(Today(), -30, Days)
DatePicker (dpTo):
DefaultDate: Today()
DataTable:
Component: ctDataTable
Items:
Filter(
'Invoices List',
(ddStatus.Selected.Value = "All" || Status = ddStatus.Selected.Value) &&
InvoiceDate >= dpFrom.SelectedDate &&
InvoiceDate <= dpTo.SelectedDate
)
Columns:
colInvoiceColumns
SearchText: txtSearch.Text
OnRowSelect:
Navigate(scrInvoiceDetail, ScreenTransition.Cover,
{selectedInvoice: ctDataTable.SelectedItem})
// Screen 2: Invoice Form Screen: scrInvoiceForm
Form:
Component: ctSmartForm
FormFields:
Table(
{FieldName: "Customer", FieldType: "Lookup", Required: true},
{FieldName: "Invoice Date", FieldType: "Date", Required: true},
{FieldName: "Due Date", FieldType: "Date", Required: true},
{FieldName: "Amount", FieldType: "Currency", Required: true},
{FieldName: "Description", FieldType: "MultilineText", Required: false}
)
LineItems:
Component: ctDataTable (Editable variant)
Items: colLineItems
Columns:
Table(
{Name: "Product", Field: "Product", Editable: true},
{Name: "Quantity", Field: "Quantity", Editable: true},
{Name: "Price", Field: "UnitPrice", Editable: true},
{Name: "Total", Field: "LineTotal", Editable: false}
)
ctButton (btnAddLine):
Text: "+ Add Line"
OnSelectAction:
Collect(colLineItems, {Product: "", Quantity: 1, UnitPrice: 0})
Summary:
Label: "Subtotal: " & Text(Sum(colLineItems, LineTotal), "[$-en-US]$#,##0.00")
Label: "Tax (10%): " & Text(Sum(colLineItems, LineTotal) * 0.1, "[$-en-US]$#,##0.00")
Label: "Total: " & Text(Sum(colLineItems, LineTotal) * 1.1, "[$-en-US]$#,##0.00")
Actions:
ctButton (btnSave):
Text: "Save Draft"
ButtonStyle: "Secondary"
OnSelectAction:
Patch('Invoices List', ...)
ctButton (btnSend):
Text: "Send Invoice"
ButtonStyle: "Primary"
OnSelectAction:
If(cmpInvoiceForm.IsValid,
Patch('Invoices List', ..., {Status: "Sent"});
// Send email notification
Office365Outlook.SendEmailV2(...);
Notify("Invoice sent!", NotificationType.Success);
Navigate(scrInvoiceList),
Notify("Please fix validation errors", NotificationType.Error)
)
## Performance Benchmarks
### Component Load Times
> **Architecture Overview:** Test Environment:
## Troubleshooting Guide
### Common Issues and Solutions
1. **Component Not Showing in Insert Menu**:
> **Architecture Overview:** Problem: Imported library, but components not visible
2. **Component Properties Not Updating**:
```sql
Problem: Changing custom property doesn't update component
Solution:
1. Ensure property is set as "Input" type
2. Check formula in component references the property correctly
3. Verify no circular references
4. Try explicitly setting property: Component.Property = value
5. Check for formula errors in component (red underlines)
-
Performance Degradation:
Problem: App slow with many component instances Solution: 1. Reduce components on single screen (< 20 instances) 2. Use virtualization for galleries 3. Implement lazy loading 4. Move complex formulas to OnVisible/OnSelect 5. Use collections instead of direct data source binding 6. Enable Delayed Load for images/media -
Version Conflicts:
Problem: App breaks after library update Solution: 1. Check version notes for breaking changes 2. Update app to use new property names 3. Test in dev environment first 4. Maintain backward compatibility in library 5. Consider keeping old version available 6. Document migration steps
Best Practices Summary
DO:
- ✅ Use semantic naming:
ct[ComponentName]for components - ✅ Create comprehensive custom properties (input and output)
- ✅ Implement accessibility features (TabIndex, AccessibleLabel)
- ✅ Version your library with detailed release notes
- ✅ Test components in isolation before publishing
- ✅ Document all properties and usage examples
- ✅ Implement responsive design (use Parent.Width/Height)
- ✅ Use theme variables for colors and spacing
- ✅ Create reusable patterns (don't over-customize)
- ✅ Monitor performance metrics
DON'T:
- ❌ Hardcode values that should be properties
- ❌ Create components for single-use scenarios
- ❌ Publish without testing in target apps
- ❌ Skip accessibility features
- ❌ Ignore version control and governance
- ❌ Over-engineer simple components
- ❌ Forget to document breaking changes
- ❌ Use complex formulas that slow performance
- ❌ Create duplicate components across libraries
- ❌ Publish untested "work in progress" components
Architecture Decision and Tradeoffs
When designing low-code development solutions with Power Apps, consider these key architectural trade-offs:
| Approach | Best For | Tradeoff |
|---|---|---|
| Managed / platform service | Rapid delivery, reduced ops burden | Less customisation, potential vendor lock-in |
| Custom / self-hosted | Full control, advanced tuning | Higher operational overhead and cost |
Recommendation: Start with the managed approach for most workloads and move to custom only when specific requirements demand it.
Validation and Versioning
- Last validated: April 2026
- Validate examples against your tenant, region, and SKU constraints before production rollout.
- Keep module, CLI, and SDK versions pinned in automation pipelines and review quarterly.
Security and Governance Considerations
- Apply least-privilege access using RBAC roles and just-in-time elevation for admin tasks.
- Store secrets in managed secret stores and avoid embedding credentials in scripts or source files.
- Enable audit logging, data protection policies, and periodic access reviews for regulated workloads.
Cost and Performance Notes
- Define budgets and alerts, then monitor usage and cost trends continuously after go-live.
- Baseline performance with synthetic and real-user checks before and after major changes.
- Scale resources with measured thresholds and revisit sizing after usage pattern changes.
Official Microsoft References
- https://learn.microsoft.com/power-apps/
- https://learn.microsoft.com/power-platform/admin/
- https://learn.microsoft.com/power-platform/guidance/
Public Examples from Official Sources
- These examples are sourced from official public Microsoft documentation and sample repositories.
- Documentation examples: https://learn.microsoft.com/power-apps/
- Sample repositories: https://github.com/microsoft/PowerApps-Samples
- Prefer adapting these examples to your tenant, subscriptions, and governance requirements before production use.
Key Takeaways
- Component libraries enable enterprise-scale PowerApps - Build once, reuse across hundreds of apps
- Custom properties make components flexible - Input properties for configuration, output properties for state
- Theming creates consistency - Global variables and component properties enforce design standards
- Accessibility is non-negotiable - TabIndex, AccessibleLabel, color contrast, keyboard navigation
- Version control prevents chaos - Semantic versioning, release notes, migration guides
- Performance requires optimization - Collections, concurrent loading, lazy rendering, debouncing
- Documentation drives adoption - Component catalog, usage examples, governance policies
- Testing ensures quality - Test matrix, browser testing, automated Test Studio
- Governance enables scale - Change process, support channels, contribution guidelines
- Real-world patterns accelerate development - DataTables, Forms, Buttons, Navigation components
Additional Resources
- PowerApps Component Libraries Documentation
- Canvas Components Best Practices
- Power Fx Formula Reference
- Fluent UI Design System
- PowerApps Community Components
- Microsoft Learn: Component Libraries
Next Steps
- Audit existing apps: Identify repeated UI patterns that should become components
- Build starter library: Create 5-10 core components (Button, Input, Table, Form, Header)
- Establish governance: Define versioning, change process, and support channels
- Train makers: Host workshops on using and contributing to component library
- Monitor adoption: Track which components are most used, gather feedback
- Expand library: Add advanced components based on maker requests
- Integrate with ALM: Use Azure DevOps/GitHub for version control and CI/CD
- Create templates: Build starter apps demonstrating component usage
Ready to transform your PowerApps development? Start by creating a component library with your three most-used UI patterns—you'll see immediate productivity gains and consistency improvements!
Discussion