NewPower Apps · canvas componentData display✓ Tested in StudioMIT License
Data table
Your rows as a table, cards or a list, with coloured status pills, segmented progress, sortable headers, checkboxes with bulk actions, a row menu, and loading and empty states. Select a row to open it. Built on a gallery, so it looks the way you want.
- Name in Studio
- lcsDataTable
- Version
- 0.2.0
- Properties
- 18 inputs · 6 outputs · 5 events · 1 action
Variations
As it is when you paste it, with every input at its default.
- #1001Avery BrooksIn progress2/4Mar 3High
- #1002Jordan LeeCompleted5/5Mar 5Medium
- #1003Priya NairOn hold1/3Mar 9Low
- #1004Mateo AlvarezNot started0/4Mar 12High
- #1005Chloé MartinIn progress3/5Mar 14Medium
- #1006Kenji WatanabeCompleted4/4Mar 16Low
- #1007Amara OkaforIn progress1/4Mar 18High
- #1008Liam WalshNot started0/3Mar 20Medium
- #1009Noah FischerOn hold2/5Mar 22Low
- #1010Sam RiveraIn progress4/5Mar 24Medium
Formulas on this screen
Set the same ones in Studio to try it the same way; there, each Notify shows its event in a banner, which this preview leaves out so nothing covers the component. The instance names are the ones Studio gives the first copy on a screen.
Screen1.OnVisible = ClearCollect(colOrders, {OrderNumber: 1001, Customer: "Avery Brooks", Status: "In progress", StepsDone: 2, StepsTotal: 4, DueDate: Date(2026, 3, 3), Priority: "High"}, {OrderNumber: 1002, Customer: "Jordan Lee", Status: "Completed", StepsDone: 5, StepsTotal: 5, DueDate: Date(2026, 3, 5), Priority: "Medium"}, {OrderNumber: 1003, Customer: "Priya Nair", Status: "On hold", StepsDone: 1, StepsTotal: 3, DueDate: Date(2026, 3, 9), Priority: "Low"}, {OrderNumber: 1004, Customer: "Mateo Alvarez", Status: "Not started", StepsDone: 0, StepsTotal: 4, DueDate: Date(2026, 3, 12), Priority: "High"}, {OrderNumber: 1005, Customer: "Chloé Martin", Status: "In progress", StepsDone: 3, StepsTotal: 5, DueDate: Date(2026, 3, 14), Priority: "Medium"}, {OrderNumber: 1006, Customer: "Kenji Watanabe", Status: "Completed", StepsDone: 4, StepsTotal: 4, DueDate: Date(2026, 3, 16), Priority: "Low"}, {OrderNumber: 1007, Customer: "Amara Okafor", Status: "In progress", StepsDone: 1, StepsTotal: 4, DueDate: Date(2026, 3, 18), Priority: "High"}, {OrderNumber: 1008, Customer: "Liam Walsh", Status: "Not started", StepsDone: 0, StepsTotal: 3, DueDate: Date(2026, 3, 20), Priority: "Medium"}, {OrderNumber: 1009, Customer: "Noah Fischer", Status: "On hold", StepsDone: 2, StepsTotal: 5, DueDate: Date(2026, 3, 22), Priority: "Low"}, {OrderNumber: 1010, Customer: "Sam Rivera", Status: "In progress", StepsDone: 4, StepsTotal: 5, DueDate: Date(2026, 3, 24), Priority: "Medium"})
lcsDataTable_1.Rows = ForAll(Switch(lcsDataTable_1.SortColumn, 2, SortByColumns(colOrders, Customer, If(lcsDataTable_1.SortDescending, SortOrder.Descending, SortOrder.Ascending)), 5, SortByColumns(colOrders, DueDate, If(lcsDataTable_1.SortDescending, SortOrder.Descending, SortOrder.Ascending)), colOrders), {Id: Text(OrderNumber), C1: "#" & OrderNumber, C2: Customer, C3: Status, C4: StepsDone & "/" & StepsTotal, C5: Text(DueDate, "mmm d"), C6: Priority})
lcsDataTable_1.OnRowSelect = Notify("Open order #" & RowId)
lcsDataTable_1.OnRowAction = Notify(Action & " order #" & RowId)
A web replica that behaves as the component does in Power Apps. In Studio it uses Microsoft's controls, which can look slightly different.
Add it in five steps
- In Power Apps Studio, open Settings > Updates and check that Modern controls and themes and Enhanced component properties are on.
- Open the Components tab in the tree view and select New component.
- Select an empty part of Studio, so the new component isn't selected.
- Copy the YAML on this page and press Ctrl+V.
lcsDataTableappears with all its properties. - On a screen, insert it from Insert > Custom >
lcsDataTable.
What it needs
- No premium licence: standard controls, no connectors
- Modern controls and themes: on
- Enhanced component properties: on
- Tested in Power Apps Studio 3.26094.9 on October 17, 2026
- Version 0.2.0
Properties (30)
Read from the component's own YAML, so they always match what you paste.
| Property | Kind | Type | Default |
|---|---|---|---|
ColumnsThe columns, in order, up to six: Label, Width in pixels, and Kind: Text, Strong (bold), Badge (a coloured pill) or Progress (a segmented bar from text such as 2/4). Column 1 shows C1 from Rows, and so on. | Input | Table | Table(
{Label: "Order", Width: 110, Kind: "Strong"},
{Label: "Customer", Width: 200, Kind: "Text"},
{Label: "Status", Width: 140, Kind: "Badge"},
{Label: "Progress", Width: 190, Kind: "Progress"},
{Label: "Due", Width: 110, Kind: "Text"},
{Label: "Priority", Width: 110, Kind: "Badge"}
) |
RowsThe rows: Id, and the text for each column in C1 to C6. Sort, filter and page them in your app. | Input | Table | Table(
{Id: "1001", C1: "#1001", C2: "Avery Brooks", C3: "In progress", C4: "2/4", C5: "Mar 3", C6: "High"},
{Id: "1002", C1: "#1002", C2: "Jordan Lee", C3: "Completed", C4: "5/5", C5: "Mar 5", C6: "Medium"},
{Id: "1003", C1: "#1003", C2: "Priya Nair", C3: "On hold", C4: "1/3", C5: "Mar 9", C6: "Low"},
{Id: "1004", C1: "#1004", C2: "Mateo Alvarez", C3: "Not started", C4: "0/4", C5: "Mar 12", C6: "High"}
) |
BadgeStylesThe colours of Badge pills by their text: Value, Fill and Ink. Text with no style is grey. | Input | Table | Table(
{Value: "In progress", Fill: RGBA(219, 234, 254, 1), Ink: RGBA(30, 64, 175, 1)},
{Value: "Completed", Fill: RGBA(220, 252, 231, 1), Ink: RGBA(22, 101, 52, 1)},
{Value: "On hold", Fill: RGBA(254, 243, 199, 1), Ink: RGBA(146, 64, 14, 1)},
{Value: "Not started", Fill: RGBA(243, 244, 246, 1), Ink: RGBA(75, 85, 99, 1)},
{Value: "High", Fill: RGBA(254, 226, 226, 1), Ink: RGBA(153, 27, 27, 1)},
{Value: "Medium", Fill: RGBA(254, 243, 199, 1), Ink: RGBA(146, 64, 14, 1)},
{Value: "Low", Fill: RGBA(220, 252, 231, 1), Ink: RGBA(22, 101, 52, 1)}
) |
ViewThe view to start with: Table, Cards or List. | Input | Text | "Table" |
ShowViewToggleShows the Table, Cards and List switch. | Input | Boolean | true |
TitleColumnThe column that names a row: the title of a card or list item, and the name screen readers hear. | Input | Number | 2 |
SubtitleColumnThe column shown small above a card's title and under a list item's. | Input | Number | 1 |
SortableColumn headers can be selected to sort. The table runs OnSort; your app sorts Rows. | Input | Boolean | true |
SelectableShows a checkbox on each row, and one to select them all in table view. | Input | Boolean | true |
BulkActionsButtons for the selected rows, separated by commas, such as Approve,Reject. Up to three. Leave empty for none. | Input | Text | "" |
RowActionsThe row menu, separated by commas, such as View,Edit,Delete. Up to three. Leave empty for no menu. | Input | Text | "View,Edit,Delete" |
DensityComfortable (60 tall rows, easy to touch) or Compact (44). | Input | Text | "Comfortable" |
LoadingShows grey placeholder rows while your app loads the data. | Input | Boolean | false |
EmptyTitleThe heading when there are no rows. | Input | Text | "No items found" |
EmptyTextThe line under it. | Input | Text | "Try changing your search or filters." |
AccentColorYour brand colour, for progress, checkboxes and the selected rows. | Input | Color | RGBA(15, 108, 189, 1) |
ThemeLight, or Dark for a dark screen. | Input | Text | "Light" |
MotionPlays short animations: progress segments fill in one after another, a highlight sweeps once across the loading rows, and the empty state's icon pops in. None plays for people who've asked their device for reduced motion. | Input | Boolean | true |
| Property | Kind | Type | Default |
|---|---|---|---|
CurrentViewThe view shown now: Table, Cards or List. | Output | Text | — |
SortColumnThe column sorted by, from 1; 0 for none. | Output | Number | — |
SortDescendingTrue when the sort runs from Z to A or high to low. | Output | Boolean | — |
SelectedIdsThe selected rows' Ids, separated by semicolons. | Output | Text | — |
SelectedCountHow many rows are selected, including any no longer in Rows. | Output | Number | — |
SelectedRowsThe selected rows that are in Rows now. | Output | Table | — |
| Property | Kind | Type | Default |
|---|---|---|---|
OnRowSelect(RowId)Runs in your app when the user selects a row, a card or a list item, such as to open it.
| Event | — | false |
OnRowAction(Action, RowId)Runs in your app when the user chooses from a row's menu.
| Event | — | false |
OnBulkAction(Action, RowIds)Runs in your app when the user selects a button for the selected rows.
| Event | — | false |
OnSort(ColumnNumber, Descending)Runs in your app when the user selects a column header. Sort Rows by that column.
| Event | — | false |
OnViewChange(NewView)Runs in your app when the user switches between Table, Cards and List.
| Event | — | false |
| Property | Kind | Type | Default |
|---|---|---|---|
ClearSelection()Clears every checkbox, such as after a bulk action. | Action | — | — |
Guide
Use lcsDataTable for any list of records people scan, open and act on: requests, orders, cases, tasks. A plain gallery needs all of this built by hand. Microsoft's modern Data grid control can't be styled this way. This component gives you:
- Three views from one switch: a Table, Cards that wrap two or three across, and a List with an initial and pills. The user picks; you can start them on any view.
- Select a row to open it. The whole row, card or list item is the button, and it runs
OnRowSelect(RowId). - Columns from a table you set: up to six, each with a label, a width and a kind. Text, Strong (bold, for IDs), Badge (a coloured pill) or Progress (a segmented bar from text such as
2/4). - Pills coloured by their text, from your
BadgeStylestable: In progress is blue, Completed green, High red. Text with no style is grey. - Sortable headers that tell your app which column and which way (
OnSort), so you sort the data source itself. - Checkboxes and bulk actions: select rows, then Approve or Reject them together (
OnBulkAction(Action, RowIds)). - A row menu that opens inside the row (View, Edit, Delete) and runs
OnRowAction(Action, RowId). - Loading and empty states, two densities, your
AccentColorand aDarktheme.
The examples call it dtOrders on your screen.
Give it your rows. Each row needs an Id and the text for each column in C1 to C6, in the order of Columns:
// dtOrders.Rows
ForAll(
colOrders,
{
Id: Text(ID),
C1: "#" & OrderNumber,
C2: Customer,
C3: Status,
C4: StepsDone & "/" & StepsTotal,
C5: Text(DueDate, "mmm d"),
C6: Priority
}
)
Describe the columns:
// dtOrders.Columns
Table(
{ Label: "Order", Width: 110, Kind: "Strong" },
{ Label: "Customer", Width: 200, Kind: "Text" },
{ Label: "Status", Width: 140, Kind: "Badge" },
{ Label: "Progress", Width: 190, Kind: "Progress" },
{ Label: "Due", Width: 110, Kind: "Text" },
{ Label: "Priority", Width: 110, Kind: "Badge" }
)
Open the record the user selects:
// dtOrders.OnRowSelect
Navigate(OrderScreen, ScreenTransition.None, { varOrder: LookUp(Orders, Text(ID) = RowId) })
Act from the row menu:
// dtOrders.RowActions
"View,Edit,Delete"
// dtOrders.OnRowAction
Switch(
Action,
"View", Navigate(OrderScreen, ScreenTransition.None, { varOrder: LookUp(Orders, Text(ID) = RowId) }),
"Edit", Navigate(EditScreen, ScreenTransition.None, { varOrder: LookUp(Orders, Text(ID) = RowId) }),
"Delete", Remove(Orders, LookUp(Orders, Text(ID) = RowId))
)
Approve several at once:
// dtOrders.BulkActions
"Approve,Reject"
// dtOrders.OnBulkAction
ForAll(
Split(RowIds, ";") As picked,
Patch(Orders, LookUp(Orders, Text(ID) = picked.Value), { Status: If(Action = "Approve", "Approved", "Rejected") })
);
dtOrders.ClearSelection()
Sort when a header is selected. Sorting the data source keeps it delegable:
// colOrders, where you load it, sorted by dtOrders.SortColumn
Switch(
dtOrders.SortColumn,
2, SortByColumns(Orders, Customer, If(dtOrders.SortDescending, SortOrder.Descending, SortOrder.Ascending)),
5, SortByColumns(Orders, DueDate, If(dtOrders.SortDescending, SortOrder.Descending, SortOrder.Ascending)),
Orders
)
Show grey rows while you load, and page long lists with the Pagination component:
// dtOrders.Loading
locLoading
// dtOrders.Rows, with Pagination named pgOrders
LastN(FirstN(colOrderRows, pgOrders.LastRow), pgOrders.RowsOnPage)
Three things differ from the examples in Use it:
- The row's id. A Dataverse row's unique identifier is a column named after the table, such as
Orderin Orders, notID. Turn it into text forId, and back withGUIDto find the row. - Choice columns show their label with
Text, and are set with their own values, such as'Status (Orders)'.Approved. - Lookup columns show a column of the related row, such as
Customer.'Account Name'.
// dtOrders.Rows
ForAll(
colOrders,
{
Id: Text(Order),
C1: "#" & 'Order Number',
C2: Customer.'Account Name',
C3: Text(Status),
C4: 'Steps Done' & "/" & 'Steps Total',
C5: Text('Due Date', "mmm d"),
C6: Text(Priority)
}
)
// dtOrders.OnRowSelect
Navigate(OrderScreen, ScreenTransition.None, { varOrder: LookUp(Orders, Order = GUID(RowId)) })
// dtOrders.OnBulkAction
ForAll(
Split(RowIds, ";") As picked,
Patch(
Orders,
LookUp(Orders, Order = GUID(picked.Value)),
{ Status: If(Action = "Approve", 'Status (Orders)'.Approved, 'Status (Orders)'.Rejected) }
)
);
dtOrders.ClearSelection()
colOrders holds at most 2,000 rows, so load it with a filter that Dataverse runs (by status, owner or date) rather than the whole table.
- Each row, card and list item is one button, named by its title column, so screen readers say what it opens.
- Checkboxes are named "Select" and the row's title. The menu button says "Actions for" the row, and each action says what it does and to which row ("Edit Avery Brooks").
- Headers say which column they sort and how it's sorted now. Sorted columns also show an arrow.
- Pills always show their text, so colour is never the only signal.
- Choose
BadgeStylescolours with enough contrast; the defaults meet WCAG AA. - With
Motionon (the default), progress segments fill in one after another, a highlight sweeps once across the loading rows, and the empty state's icon pops in, each within about a second and a half. None plays for people who've asked their device for reduced motion, andMotionset to false shows them at once, as in 0.1.0.
- Up to six columns, as text. Turn numbers and dates into text in
Rows(Text(DueDate, "mmm d")). - Keep the column widths within the component's width; columns past the edge are cut off.
- Cards show the first two Badge columns, the first Progress column and one more text column. The list shows the first two Badge columns.
- Progress bars show up to ten segments.
- The row menu opens inside the row, so it can't hang below the last row.
- Checkboxes use the modern Checkbox control. If your environment doesn't have it, set
Selectableto false. - For very large tables with thousands of rows on screen, Microsoft's modern Data grid control virtualises its rows; this component draws the rows you give it, so page them.
- 0.2.0: motion: progress segments fill in, a highlight sweeps once across the loading rows, and the empty state's icon pops in (skipped for reduced motion); a
Motioninput turns it off. WithMotionoff, it's exactly 0.1.0. - 0.1.0: first version: table, card and list views; Text, Strong, Badge and Progress columns; badge styles; sorting through
OnSort; checkboxes and bulk actions; the row menu; loading and empty states; two densities.
Did it work for you?
1 rating so far. The average shows from 3 ratings.Worked in 1 reader's app.
Sign in to rate it or to say it worked in your app.
Comments
No comments yet. Share how you used it, or ask something.
Sign in to comment or reply. Comments show under a display name you choose, never your email.