Skip to content
CloudOps
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 9 min read

Adaptive Card Table Layouts for Dense Teams Dashboards

FactSets fall apart for tabular data. Adaptive Cards 1.5+ has a real Table element with columns and cells — here is how to render dense ops data cleanly in Teams.

  • #microsoft-teams
  • #adaptive-cards
  • #tables
  • #dashboards
  • #json

I spent an embarrassing amount of time trying to render a deployment status table inside an Adaptive Card using ColumnSet and FactSet, fighting alignment, before I realized version 1.5 added an actual Table element. Tabular ops data — service health grids, top-N error tables, environment matrices — looks like garbage in a FactSet and only marginally better hand-built from columns. The Table element is purpose-built for it, and once I switched, my “current incidents” card went from a cramped mess to something people could actually scan at a glance.

This guide covers the Table element: defining columns, building rows and cells, styling, and the layout gotchas. I generate the row data binding with an AI assistant because mapping an array of records into table rows is exactly the kind of repetitive transform it is good at. But the model will cheerfully produce a 40-row card that exceeds the Teams card size limit, and it will not warn you. Treat AI like a fast junior engineer: it builds the structure fast, you review for limits and correctness, and no real tenant data goes into the prompt.

The Table element basics

A Table has columns (defining relative widths) and rows, each row being a TableRow of TableCells. Each cell can contain any card elements, but usually just a TextBlock.

{
  "type": "Table",
  "columns": [
    { "width": 2 },
    { "width": 1 },
    { "width": 1 }
  ],
  "firstRowAsHeaders": true,
  "rows": [
    {
      "type": "TableRow",
      "cells": [
        { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "Service", "weight": "Bolder" }] },
        { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "Status", "weight": "Bolder" }] },
        { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "p95", "weight": "Bolder" }] }
      ]
    },
    {
      "type": "TableRow",
      "cells": [
        { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "checkout-api" }] },
        { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "🟢 healthy", "color": "Good" }] },
        { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "182ms" }] }
      ]
    }
  ]
}

The width values on columns are relative weights, not pixels — 2, 1, 1 means the first column takes half. firstRowAsHeaders applies header styling to the first row automatically.

Styling rows for status

For a health grid, color is the fastest signal. You can style an entire row with style ("good", "warning", "attention", "accent") to tint its background, or color individual cell text. I prefer tinting the whole row when a service is down so the eye jumps straight to it:

{
  "type": "TableRow",
  "style": "attention",
  "cells": [
    { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "payments-api" }] },
    { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "🔴 down" }] },
    { "type": "TableCell", "items": [{ "type": "TextBlock", "text": "—" }] }
  ]
}

There are also table-level controls: gridStyle for the grid line color, showGridLines to toggle borders, and horizontalCellContentAlignment / verticalCellContentAlignment for default cell alignment. Set horizontalCellContentAlignment: "Right" on numeric columns so latency and counts line up properly — left-aligned numbers in a table look broken.

Pro Tip: Combine the Table element with Adaptive Card templating. Define the table once with a ${...} binding over your data array, and let the templating SDK expand the rows. You write one TableRow template and bind it to ${$data.services} — far cleaner than generating every row in code.

Watch the card size limit

Teams enforces a maximum card payload size — roughly 28 KB for the serialized card. A table is the easiest way to blow past it, because each cell carries a fair amount of JSON overhead. A 40-row, 6-column table with styled cells gets large fast. If your data is genuinely large, do not dump it all into one card. Show the top N rows with a “view all” Action.OpenUrl to a real dashboard, or paginate with a Action.Submit that refetches the next page.

This is precisely where AI-generated cards go wrong: ask a model to “render this 200-row table” and it will produce a card that Teams silently rejects for being too big. The card just never appears. So I cap rows server-side and let the model build the row template, not the full payload.

Rendering surfaces and fallbacks

The Table element requires Adaptive Cards 1.5 or later. Most modern Teams clients support it, but set the card version to "1.5" and provide a fallbackText so older or unsupported surfaces show something readable rather than a broken card. For dense data destined for a personal dashboard tab rather than a channel post, you often have more room and fewer rendering constraints, which is a better home for genuinely large tables anyway.

Letting AI do the row mapping

The transform from “array of service records” to “array of TableRows” is mechanical and repetitive, so I let ChatGPT or Claude write the mapping function, then review it for two things: that I cap the row count before building, and that numeric columns are right-aligned. The starter prompts in my prompts library include a table-builder skeleton, and I refine the styling rules in the prompt workspace. I never paste real production health data into the prompt — synthetic rows are plenty to get the structure right.

Conclusion

The Table element is the right tool for tabular ops data in Teams, far better than wrestling ColumnSet and FactSet into a grid. Define relative column widths, style rows for status, right-align numbers, and — most importantly — respect the card size limit by capping rows and linking out for the full view. Let AI build the row mapping while you review for limits and keep real tenant data out of the prompt. More dashboard patterns live in the Microsoft Teams category.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.