Back to Blog
6 min read

Power Apps Cards: Micro-Apps for Microsoft Teams

Power Apps Cards bring micro-app experiences directly into Microsoft Teams conversations. They enable quick interactions without leaving the chat context, perfect for approvals, data entry, and notifications.

What Are Power Apps Cards?

Cards are lightweight, interactive UI components that:

  • Appear inline in Teams conversations
  • Support data input and actions
  • Connect to Dataverse and other data sources
  • Can be triggered from flows or bots

Creating Your First Card

Card Designer

# Card structure
card:
  name: "Expense Approval"
  description: "Quick expense approval card"

  inputs:
    - name: expenseId
      type: text
      required: true
    - name: amount
      type: number
      required: true
    - name: description
      type: text

  layout:
    - type: TextBlock
      text: "Expense Approval Request"
      size: Large
      weight: Bolder

    - type: FactSet
      facts:
        - title: "Amount"
          value: "${amount}"
        - title: "Description"
          value: "${description}"

    - type: ActionSet
      actions:
        - type: Action.Submit
          title: "Approve"
          data:
            action: "approve"
            expenseId: "${expenseId}"
        - type: Action.Submit
          title: "Reject"
          data:
            action: "reject"
            expenseId: "${expenseId}"

Power Fx in Cards

// Card formulas
Card.OnSubmit = Switch(
    Card.SubmitData.action,
    "approve",
        Patch(
            Expenses,
            LookUp(Expenses, ID = Card.SubmitData.expenseId),
            {Status: "Approved", ApprovedBy: User().Email, ApprovedDate: Now()}
        );
        Notify("Expense approved!", NotificationType.Success),
    "reject",
        Patch(
            Expenses,
            LookUp(Expenses, ID = Card.SubmitData.expenseId),
            {Status: "Rejected", RejectedBy: User().Email, RejectedDate: Now()}
        );
        Notify("Expense rejected", NotificationType.Information)
)

Card Types

Approval Card

card:
  name: "Leave Request Approval"

  inputs:
    - name: requestId
      type: text
    - name: employeeName
      type: text
    - name: startDate
      type: date
    - name: endDate
      type: date
    - name: leaveType
      type: text

  layout:
    - type: Container
      style: emphasis
      items:
        - type: TextBlock
          text: "Leave Request"
          size: Large

    - type: ColumnSet
      columns:
        - width: auto
          items:
            - type: Image
              url: "${employeePhoto}"
              size: Small
              style: Person
        - width: stretch
          items:
            - type: TextBlock
              text: "${employeeName}"
              weight: Bolder
            - type: TextBlock
              text: "${leaveType}"
              isSubtle: true

    - type: FactSet
      facts:
        - title: "From"
          value: "${startDate}"
        - title: "To"
          value: "${endDate}"
        - title: "Days"
          value: "${daysCount}"

    - type: ActionSet
      actions:
        - type: Action.Submit
          title: "Approve"
          style: positive
          data: {action: "approve"}
        - type: Action.Submit
          title: "Reject"
          style: destructive
          data: {action: "reject"}
        - type: Action.ShowCard
          title: "Add Comment"
          card:
            type: AdaptiveCard
            body:
              - type: Input.Text
                id: comment
                placeholder: "Enter comment..."
                isMultiline: true
            actions:
              - type: Action.Submit
                title: "Submit with Comment"
                data: {action: "reject_with_comment"}

Data Entry Card

card:
  name: "Quick Ticket"

  layout:
    - type: TextBlock
      text: "Create Support Ticket"
      size: Medium
      weight: Bolder

    - type: Input.Text
      id: subject
      label: "Subject"
      isRequired: true
      errorMessage: "Subject is required"

    - type: Input.ChoiceSet
      id: priority
      label: "Priority"
      style: compact
      value: "medium"
      choices:
        - title: "Low"
          value: "low"
        - title: "Medium"
          value: "medium"
        - title: "High"
          value: "high"
        - title: "Critical"
          value: "critical"

    - type: Input.Text
      id: description
      label: "Description"
      isMultiline: true
      maxLength: 500

    - type: ActionSet
      actions:
        - type: Action.Submit
          title: "Create Ticket"
          style: positive

Status Update Card

card:
  name: "Project Status"
  refresh:
    action:
      type: Action.Execute
      verb: "refresh"
    userIds: [] # Refresh for all users

  layout:
    - type: TextBlock
      text: "Project: ${projectName}"
      size: Large

    - type: ProgressBar
      title: "Completion"
      value: ${completionPercent}
      valueLabel: "${completionPercent}%"

    - type: FactSet
      facts:
        - title: "Status"
          value: "${status}"
        - title: "Due Date"
          value: "${dueDate}"
        - title: "Owner"
          value: "${ownerName}"

    - type: ActionSet
      actions:
        - type: Action.OpenUrl
          title: "View Details"
          url: "${projectUrl}"
        - type: Action.Submit
          title: "Update Status"
          data: {action: "update"}

Triggering Cards from Power Automate

{
    "definition": {
        "triggers": {
            "When_expense_submitted": {
                "type": "ApiConnection",
                "inputs": {
                    "host": {
                        "connectionName": "shared_commondataserviceforapps"
                    },
                    "method": "post",
                    "path": "/v2/datasets/@{encodeURIComponent('default.cds')}/tables/@{encodeURIComponent('expenses')}/onnewitems"
                }
            }
        },
        "actions": {
            "Get_Manager": {
                "type": "ApiConnection",
                "inputs": {
                    "method": "get",
                    "path": "/v2/datasets/@{encodeURIComponent('default.cds')}/tables/@{encodeURIComponent('users')}/items/@{triggerBody()?['_submittedby_value']}"
                }
            },
            "Post_Card_to_Teams": {
                "type": "ApiConnection",
                "inputs": {
                    "method": "post",
                    "path": "/v1.0/teams/@{outputs('Get_Manager')?['body/manager_id']}/channels/@{variables('ApprovalChannelId')}/messages",
                    "body": {
                        "body": {
                            "contentType": "html",
                            "content": "<attachment id=\"expense-approval-card\"></attachment>"
                        },
                        "attachments": [
                            {
                                "id": "expense-approval-card",
                                "contentType": "application/vnd.microsoft.card.adaptive",
                                "content": "@{outputs('Generate_Card_JSON')}"
                            }
                        ]
                    }
                }
            }
        }
    }
}

Handling Card Submissions

// In Power Apps or Power Automate
// Handle the submit action

// Power Automate flow for card submission
When_card_submitted ->
    Switch(triggerBody()?['action'],
        "approve" ->
            // Update record
            Update_Expense_Status("Approved")
            // Send confirmation
            Post_Confirmation_Card()
            // Notify submitter
            Send_Email_Notification(),

        "reject" ->
            Update_Expense_Status("Rejected")
            Post_Rejection_Card()
            Send_Email_Notification(),

        "reject_with_comment" ->
            Update_Expense_With_Comment()
            Post_Rejection_Card()
    )

Card Refresh

Enable automatic card updates:

card:
  name: "Live Status Card"

  # Enable refresh
  refresh:
    mode: automatic
    action:
      type: Action.Execute
      verb: "refreshCard"
    userIds: ["user1@company.com", "user2@company.com"]

  # Card body updates when refreshed
  body:
    - type: TextBlock
      text: "Last Updated: ${lastUpdated}"

    - type: FactSet
      facts:
        - title: "Current Status"
          value: "${currentStatus}"
        - title: "Items Processed"
          value: "${processedCount}"

Best Practices

Design Guidelines

guidelines:
  simplicity:
    - Keep cards focused on single task
    - Limit to 3-4 actions maximum
    - Use clear, concise labels

  accessibility:
    - Provide alt text for images
    - Use sufficient color contrast
    - Support keyboard navigation

  responsiveness:
    - Design for mobile-first
    - Test on different screen sizes
    - Use flexible layouts

  performance:
    - Minimize data fetched
    - Use caching where appropriate
    - Optimize image sizes

Error Handling

// Robust card submission handling
Card.OnSubmit = If(
    IsBlank(Card.SubmitData.requiredField),
    Notify("Required field missing", NotificationType.Error),

    // Try the operation
    With(
        {result: Patch(Table, Record, Updates)},
        If(
            IsError(result),
            Notify("Failed to save: " & FirstError.Message, NotificationType.Error),
            Notify("Saved successfully!", NotificationType.Success);
            RefreshCard()
        )
    )
)

Integration Scenarios

Approval Workflows

1. User submits request
2. Flow sends card to approver in Teams
3. Approver clicks Approve/Reject in card
4. Flow processes response
5. Card updates to show outcome
6. Notifications sent to all parties

Data Collection

1. Bot asks user for information
2. Card displayed for structured input
3. User fills form and submits
4. Data stored in Dataverse
5. Confirmation card shown

Status Dashboards

1. Scheduled flow runs periodically
2. Aggregates current metrics
3. Posts/updates card in channel
4. Users see live status without navigation

Conclusion

Power Apps Cards enable contextual micro-interactions within Microsoft Teams:

  • Approvals without leaving chat
  • Quick data entry
  • Real-time status updates
  • Reduced context switching

They’re perfect for repetitive tasks that don’t warrant a full application.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.