Home / PowerApps / PowerApps Component Libraries: Build Once, Use Everywhere
PowerApps

PowerApps Component Libraries: Build Once, Use Everywhere

Master PowerApps component libraries to create reusable UI components, standardize your enterprise apps, and dramatically reduce development time with this c...

What you will learn

Practical execution with concise explanations, real implementation patterns, and production-ready recommendations.

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
  1. 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)
  2. Check permissions:

    • Go to Power Platform Admin Center
    • Select your environment → SettingsUsers + permissions
    • Verify you have "Environment Maker" role or higher

Step 1: Create Your First Component Library

Step 1: Create Your First Component Library

Create the Library

  1. Navigate to PowerApps Studio:

  2. Create Component Library:

    • Click + Create (left sidebar)
    • Select Component library (under "Start from")
    • Name: ContosoDesignSystem
    • Format: Tablet (1366x768 for flexibility)
    • Click Create
  3. Understand the Interface:

Architecture Overview: PowerApps Studio Component Library Mode

Create Your First Component: Custom Button

  1. Add New Component:

    • In Tree View, click + New component
    • Name it: ctButton (ct = custom component naming convention)
    • Set dimensions: Width = 200, Height = 60
  2. 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
      
      
  3. 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

  1. Create Input Properties:

    Property 1: Text (what the button displays)

    • Select component ctButton in 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
  2. 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

  1. Connect Label Text to Custom Property:

    • Select lblButtonText

    • Text property:

      ctButton.Text
      
      
  2. 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 ctButton in 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"

  1. 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
      
      
  2. 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
      
      
  3. Connect Output Properties:

    • Select ctDataTable component

    • SelectedItem output:

      galRows.Selected
      
      
    • SelectedIndex output:

      galRows.Selected.Value
      
      
  4. 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

  1. 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"}
          )
      )
      
      
  2. Insert DataTable Component:

    • Insert ctDataTable on test screen

    • Configure:

      Items: colEmployees
      Columns: colTableColumns
      ShowHeader: true
      AlternateRows: true
      RowHeight: 60
      
      
  3. Add Search Box:

    Name: txtSearch
    HintText: "Search employees..."
    
    // Connect to DataTable:
    ctDataTable1.SearchText: txtSearch.Text
    
    
  4. Display Selected Item:

```powerFx
Name: lblSelectedEmployee
Text: "Selected: " & ctDataTable1.SelectedItem.Name & 
      " (" & ctDataTable1.SelectedItem.Email & ")"

## Step 3: Create Form Component with Validation

![Step 3: Create Form Component with Validation](/images/articles/powerapps/2025-03-17-powerapps-component-libraries-build-once-use-everywhere-ctx-2.svg)

### 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

  1. 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)
      
      
  2. 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

  1. Save the Component Library:

    • Click FileSave
    • Ensure all components are error-free (no red warnings)
  2. 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
  3. Verify Publication:

    • Component library now shows "Published" status
    • Version number displayed (1.0)

Share with Your Organization

  1. Share the Library:

    • Click FileShare
    • 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)
  2. 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
    
    
  3. 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

  1. 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

Step 5: Use Components in Canvas Apps

Import Component Library

  1. Create New Canvas App:

    • make.powerapps.com
    • + CreateCanvas app from blank
    • Name: Employee Portal
    • Format: Tablet
  2. Import Component Library:

    • Click + Insert (left sidebar)
    • Scroll to Get more components (bottom)
    • Click Component libraries tab
    • Search: ContosoDesignSystem
    • Select and click Import
  3. Verify Import:

    • Insert pane now shows "Custom" section
    • Your components listed: ctButton, ctDataTable, ctSmartForm

Build App with Components

  1. 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: 25
    
    

    Add Search:

    Name: txtEmployeeSearch
    HintText: "Search employees..."
    X: 20, Y: 100
    Width: 400
    
    

    Add 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 - 200
    
    

    Load 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"}
        )
    )
    
    
  2. Create Employee Form Screen:

    Add Form Component:

    Component: ctSmartForm (cmpEmployeeForm)
    FormFields: colFormFields
    ShowValidation: varShowValidation
    X: 100, Y: 100
    
    

    Define 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

  1. 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);
    
    
  2. Update Component Library with Theme Support:

    • Open component library

    • Add custom property to each component:

      Property: UseTheme
      - Type: Input
      - Data type: Boolean
      - Default: true
      
      
  3. 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

  1. 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"));
    
    
  2. Create Dark Mode Toggle Button:

Architecture Overview: Component: ctButton

  1. 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

  1. 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
    
    
  2. 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())
    )
    
    
  3. 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))
        )
    
    
  4. 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

  1. Keyboard Navigation:

    // Set TabIndex on all interactive elements
    ctButton.btnBackground.TabIndex: 0
    
    // Logical tab order
    btnSave.TabIndex: 1
    btnCancel.TabIndex: 2
    btnDelete.TabIndex: 3
    
    
  2. 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
    
    
  3. 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"
        )
    )
    
    
  4. 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

  1. 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
    
    
  2. 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)
    
    
  3. 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

  1. Create Test Suite:

    • Open canvas app in edit mode
    • Advanced toolsTest Studio (preview feature)
    • + New test case: "Component Library Tests"
  2. 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

  1. 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]
    
    
  2. Create PowerApps Template:

    • Create sample app with all components
    • Add documentation screen
    • Include code examples
    • Save as template: "Contoso Design System Starter"
  3. 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)

  1. 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
    
    
  2. 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:

  1. ✅ Use semantic naming: ct[ComponentName] for components
  2. ✅ Create comprehensive custom properties (input and output)
  3. ✅ Implement accessibility features (TabIndex, AccessibleLabel)
  4. ✅ Version your library with detailed release notes
  5. ✅ Test components in isolation before publishing
  6. ✅ Document all properties and usage examples
  7. ✅ Implement responsive design (use Parent.Width/Height)
  8. ✅ Use theme variables for colors and spacing
  9. ✅ Create reusable patterns (don't over-customize)
  10. ✅ Monitor performance metrics

DON'T:

  1. ❌ Hardcode values that should be properties
  2. ❌ Create components for single-use scenarios
  3. ❌ Publish without testing in target apps
  4. ❌ Skip accessibility features
  5. ❌ Ignore version control and governance
  6. ❌ Over-engineer simple components
  7. ❌ Forget to document breaking changes
  8. ❌ Use complex formulas that slow performance
  9. ❌ Create duplicate components across libraries
  10. ❌ 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

  1. Component libraries enable enterprise-scale PowerApps - Build once, reuse across hundreds of apps
  2. Custom properties make components flexible - Input properties for configuration, output properties for state
  3. Theming creates consistency - Global variables and component properties enforce design standards
  4. Accessibility is non-negotiable - TabIndex, AccessibleLabel, color contrast, keyboard navigation
  5. Version control prevents chaos - Semantic versioning, release notes, migration guides
  6. Performance requires optimization - Collections, concurrent loading, lazy rendering, debouncing
  7. Documentation drives adoption - Component catalog, usage examples, governance policies
  8. Testing ensures quality - Test matrix, browser testing, automated Test Studio
  9. Governance enables scale - Change process, support channels, contribution guidelines
  10. Real-world patterns accelerate development - DataTables, Forms, Buttons, Navigation components

Additional Resources

Next Steps

  1. Audit existing apps: Identify repeated UI patterns that should become components
  2. Build starter library: Create 5-10 core components (Button, Input, Table, Form, Header)
  3. Establish governance: Define versioning, change process, and support channels
  4. Train makers: Host workshops on using and contributing to component library
  5. Monitor adoption: Track which components are most used, gather feedback
  6. Expand library: Add advanced components based on maker requests
  7. Integrate with ALM: Use Azure DevOps/GitHub for version control and CI/CD
  8. 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