10 React UI Components Every Enterprise Dashboard Needs
Get a summary of this article:
An enterprise dashboard isn’t a page of pretty charts. It’s where operations managers, finance teams and analysts work all day: scanning numbers, filtering data, drilling into records and acting on what they find. That puts very different demands on the React UI components it’s built from than a marketing site or a consumer app.
This guide covers the 10 React UI components nearly every enterprise dashboard needs, what separates a good version of each from a basic one, and how each looks in ReExt, which brings the Ext JS component library into React. At the end, you’ll see all 10 combined into one working dashboard, sharing one data store.
The examples assume ReExt is installed and your app is wrapped in ReExtProvider, as covered in our Ext JS grid in React tutorial. Charts need the charts package enabled, and the pivot grid needs the pivot package (Enterprise plan).

1. Data grid
The grid is the heart of most enterprise dashboards: it’s where users go from “revenue is down in EMEA” to “these 12 orders are why.”
What good looks like:
- Sorting, filtering and grouping with subtotals.
- Smooth scrolling through large or server-side datasets.
- Locked columns, inline editing and export when users need to act on the data.
In ReExt:
<ReExt
xtype="grid"
style={{ height: 400 }}
config={{
title: 'Orders',
store: { data: orders, groupField: 'region' },
features: [{ ftype: 'groupingsummary' }],
plugins: [{ ptype: 'gridfilters' }],
columns: [
{ text: 'Customer', dataIndex: 'customer', flex: 1, filter: 'string' },
{ text: 'Region', dataIndex: 'region', width: 100 },
{ text: 'Amount', dataIndex: 'amount', width: 130, xtype: 'numbercolumn',
format: '$0,000', summaryType: 'sum', filter: 'number' },
{ text: 'Status', dataIndex: 'status', width: 110,
filter: { type: 'list', options: ['Open', 'Shipped', 'Closed'] } },
],
}}
/>
2. Charts
Charts show the shape of the data at a glance: trends, comparisons and outliers that would take minutes to spot in a table.
What good looks like:
- Bar, line, area and pie charts at minimum; gauges and financial charts for some domains.
- Tooltips, legends that toggle series, and zoom for dense time series.
- Live updates when filters or data change, from the same data as the grid.
In ReExt:
<ReExt
xtype="cartesian"
style={{ height: 300 }}
config={{
store: { data: revenue },
legend: { docked: 'bottom' },
interactions: ['itemhighlight'],
axes: [
{ type: 'numeric', position: 'left', grid: true },
{ type: 'category', position: 'bottom' },
],
series: [{ type: 'line', xField: 'month', yField: 'revenue',
title: 'Revenue', marker: true, smooth: true }],
}}
/>
3. KPI cards
KPI cards answer the first question every user has when they open the dashboard: are we on track?
What good looks like:
- A handful of key numbers, each with a label and context such as a trend or target.
- A row that resizes cleanly across screen widths.
- Values that update with the dashboard’s filters.
In ReExt: an hbox container of panels, with each card’s value computed in React:
const total = orders.reduce((sum, o) => sum + o.amount, 0);
const open = orders.filter((o) => o.status === 'Open').length;
<ReExt xtype="container" style={{ height: 110 }}
config={{ layout: { type: 'hbox', align: 'stretch' } }}>
<ReExt childFlex={1} xtype="panel"
config={{ title: 'Revenue', bodyPadding: 12, html: `<h2>$${total.toLocaleString()}</h2>` }} />
<ReExt childFlex={1} xtype="panel"
config={{ title: 'Open orders', bodyPadding: 12, html: `<h2>${open}</h2>` }} />
<ReExt childFlex={1} xtype="panel"
config={{ title: 'Orders', bodyPadding: 12, html: `<h2>${orders.length}</h2>` }} />
</ReExt>
4. Filter bar
Filters turn a static dashboard into an analysis tool. Every enterprise dashboard needs a fast way to narrow the data by date, category and search terms.
What good looks like:
- A date range, multi-select dropdowns and a search box in one bar.
- Filters that apply to the grid, charts and KPI cards together.
- Keyboard-friendly: press Enter to apply.
In ReExt: a toolbar on the grid with date fields, a multi-select tag field and a search box. One function reads them and filters the store:
const applyFilters = (cmp) => {
const bar = cmp.up('toolbar');
const from = bar.down('#from').getValue();
const to = bar.down('#to').getValue();
const regions = bar.down('#regions').getValue() || [];
const search = (bar.down('#search').getValue() || '').toLowerCase();
const store = bar.up('grid').getStore();
store.clearFilter();
store.filterBy((r) =>
(!from || r.get('orderDate') >= from) &&
(!to || r.get('orderDate') <= to) &&
(!regions.length || regions.includes(r.get('region'))) &&
(!search || r.get('customer').toLowerCase().includes(search)));
};
const onEnter = (field, e) => { if (e.getKey() === e.ENTER) applyFilters(field); };
// In the grid config:
tbar: [
{ xtype: 'datefield', itemId: 'from', emptyText: 'From', width: 130 },
{ xtype: 'datefield', itemId: 'to', emptyText: 'To', width: 130 },
{ xtype: 'tagfield', itemId: 'regions', emptyText: 'All regions', width: 220,
store: ['EMEA', 'APAC', 'AMER'], queryMode: 'local' },
{ xtype: 'textfield', itemId: 'search', emptyText: 'Search customer', width: 180,
listeners: { specialkey: onEnter } },
{ text: 'Apply', handler: applyFilters },
],
5. Resizable layout
Dashboards pack a lot onto one screen, and different users care about different parts. A layout with resizable, collapsible regions lets each user make room for what matters to them.
What good looks like:
- Regions for navigation, main content and details, with draggable splitters.
- Collapsible side panels.
- Content, especially grids and charts, that resizes cleanly as regions change.
In ReExt: a border layout, where each child takes a region and childSplit adds a splitter:
<ReExt xtype="panel" style={{ height: 640 }} config={{ layout: 'border' }}>
<ReExt xtype="panel" childRegion="north" childHeight={110} config={{ title: 'KPIs' }} />
<ReExt xtype="panel" childRegion="west" childWidth={220} childSplit={true}
config={{ title: 'Navigation', collapsible: true }} />
<ReExt xtype="panel" childRegion="center" config={{ title: 'Main view' }} />
<ReExt xtype="panel" childRegion="east" childWidth={280} childSplit={true}
config={{ title: 'Details', collapsible: true }} />
</ReExt>
6. Tabs
Tabs let one dashboard hold several views of the same data, such as a table, a chart and a breakdown, without crowding the screen.
What good looks like:
- Views that share the same filters and data.
- Tabs that render only when opened, so the first load stays fast.
- Keyboard navigation between tabs.
In ReExt: a tab panel whose items are other components, here a grid and a chart sharing one store:
<ReExt
xtype="tabpanel"
style={{ height: 420 }}
config={{
items: [
{ title: 'Orders', xtype: 'grid', store, columns },
{ title: 'Revenue', xtype: 'cartesian', store,
axes: [{ type: 'numeric', position: 'left' }, { type: 'category', position: 'bottom' }],
series: [{ type: 'bar', xField: 'region', yField: 'amount' }] },
],
}}
/>
7. Tree navigation
Large dashboards organize content in hierarchies: regions and countries, business units and teams, product lines and SKUs. A tree lets users move through that structure without leaving the screen.
What good looks like:
- Expandable nodes, with child data loaded on demand for large trees.
- Selection that drives the rest of the dashboard.
- Keyboard navigation and search within the tree.
In ReExt: a tree panel whose selection updates React state:
const [section, setSection] = useState('all');
<ReExt
xtype="treepanel"
style={{ height: 400 }}
config={{
title: 'Regions',
rootVisible: false,
store: {
type: 'tree',
root: {
expanded: true,
children: [
{ text: 'All regions', id: 'all', leaf: true },
{ text: 'EMEA', id: 'EMEA', expanded: true, children: [
{ text: 'Germany', id: 'DE', leaf: true },
{ text: 'United Kingdom', id: 'UK', leaf: true },
] },
{ text: 'APAC', id: 'APAC', leaf: true },
{ text: 'AMER', id: 'AMER', leaf: true },
],
},
},
}}
onItemclick={(view, record) => setSection(record.get('id'))}
/>
8. Pivot grid
When managers ask “revenue by region by quarter,” a pivot grid answers inside the dashboard instead of sending them to Excel.
What good looks like:
- Rows and columns chosen from any fields, with sums, counts and averages.
- Users rearranging dimensions themselves, without a developer.
- Totals in every direction, and export to Excel.
In ReExt (Enterprise plan, pivot package enabled):
<ReExt
xtype="pivotgrid"
style={{ height: 360 }}
config={{
store: { data: sales },
plugins: [{ ptype: 'pivotconfigurator' }],
matrix: {
leftAxis: [{ dataIndex: 'region', header: 'Region' }],
topAxis: [{ dataIndex: 'quarter', header: 'Quarter' }],
aggregate: [{ dataIndex: 'revenue', aggregator: 'sum', header: 'Revenue', align: 'right' }],
},
}}
/>
9. Detail form
Dashboards don’t stop at viewing. Users spot a problem and fix it: update a status, correct an amount, reassign an owner. A detail form next to the grid keeps that work on the same screen.
What good looks like:
- Loads the selected record automatically.
- Validates before saving, with clear error messages.
- Updates the grid, charts and KPIs as soon as the change is saved.
In ReExt: a form that loads the selected grid record and writes changes back to it:
const formRef = useRef(null);
const selected = useRef(null);
// On the grid:
onSelectionchange={(selModel, records) => {
selected.current = records[0] || null;
if (selected.current) formRef.current.getForm().loadRecord(selected.current);
}}
// The form:
<ReExt
xtype="form"
ready={(cmp) => { formRef.current = cmp; }}
config={{
title: 'Order details',
bodyPadding: 12,
defaults: { anchor: '100%' },
items: [
{ xtype: 'textfield', name: 'customer', fieldLabel: 'Customer', allowBlank: false },
{ xtype: 'numberfield', name: 'amount', fieldLabel: 'Amount', minValue: 0 },
{ xtype: 'combobox', name: 'status', fieldLabel: 'Status',
store: ['Open', 'Shipped', 'Closed'], forceSelection: true },
],
buttons: [{
text: 'Save', formBind: true,
handler: () => {
if (!selected.current) return;
formRef.current.getForm().updateRecord(selected.current);
Ext.toast('Order updated');
},
}],
}}
/>
10. Toolbar with actions, export and notifications
Every dashboard needs a consistent place for actions: refresh, export, share, and confirmation that something happened.
What good looks like:
- Primary actions always visible; secondary ones in a menu.
- Export to Excel and CSV of exactly what’s on screen.
- Brief, non-blocking notifications for saves, refreshes and errors.
In ReExt: a toolbar with a menu of export options. The exporter plugin, part of the Enterprise plan, provides saveDocumentAs, and Ext.toast shows notifications:
// In the grid config, alongside plugins: [{ ptype: 'gridexporter' }]
tbar: [
{ text: 'Refresh', handler: (b) => { b.up('grid').getStore().reload(); Ext.toast('Data refreshed'); } },
{
text: 'Export',
menu: [
{ text: 'Excel (.xlsx)', handler: (item) =>
item.up('grid').saveDocumentAs({ type: 'excel07', title: 'Orders', fileName: 'orders.xlsx' }) },
{ text: 'CSV', handler: (item) =>
item.up('grid').saveDocumentAs({ type: 'csv', fileName: 'orders.csv' }) },
],
},
],
Putting it together: one dashboard, one data store
Individually, these components are useful. Together, connected to the same data, they make a dashboard. This operations dashboard combines KPI cards, tree navigation, tabs with a grid and a chart, a detail form, a toolbar with export, and notifications, inside a resizable border layout. One store feeds everything, so choosing a region in the tree filters the grid, the chart and the KPIs at once, and saving the form updates all of them.
import { useEffect, useMemo, useRef, useState } from 'react';
import ReExt from '@sencha/reext';
import { orders } from './data';
export default function OperationsDashboard() {
const formRef = useRef(null);
const selected = useRef(null);
const [kpi, setKpi] = useState({ revenue: 0, count: 0, open: 0 });
// One store shared by the grid, chart, form and KPI cards.
const store = useMemo(() => Ext.create('Ext.data.Store', {
fields: [{ name: 'amount', type: 'float' }, { name: 'orderDate', type: 'date' }],
data: orders,
}), []);
// Recalculate KPIs whenever the data is filtered or edited.
useEffect(() => {
const recalc = () => {
let open = 0;
store.each((r) => { if (r.get('status') === 'Open') open += 1; });
setKpi({ revenue: store.sum('amount'), count: store.getCount(), open });
};
recalc();
store.on({ datachanged: recalc, update: recalc });
return () => store.un({ datachanged: recalc, update: recalc });
}, [store]);
const filterRegion = (region) => {
store.clearFilter();
if (region !== 'all') store.filter({ property: 'region', value: region, operator: '=' });
};
const treeConfig = useMemo(() => ({
title: 'Regions',
rootVisible: false,
store: {
type: 'tree',
root: { expanded: true, children: [
{ text: 'All regions', id: 'all', leaf: true },
{ text: 'EMEA', id: 'EMEA', leaf: true },
{ text: 'APAC', id: 'APAC', leaf: true },
{ text: 'AMER', id: 'AMER', leaf: true },
] },
},
}), []);
const tabsConfig = useMemo(() => ({
items: [
{
title: 'Orders',
xtype: 'grid',
store,
plugins: [{ ptype: 'gridexporter' }],
tbar: [
{ text: 'Export to Excel', handler: (b) =>
b.up('grid').saveDocumentAs({ type: 'excel07', title: 'Orders', fileName: 'orders.xlsx' }) },
],
columns: [
{ text: 'Customer', dataIndex: 'customer', flex: 1 },
{ text: 'Region', dataIndex: 'region', width: 100 },
{ text: 'Amount', dataIndex: 'amount', width: 120, xtype: 'numbercolumn', format: '$0,000' },
{ text: 'Status', dataIndex: 'status', width: 100 },
],
listeners: {
selectionchange: (selModel, records) => {
selected.current = records[0] || null;
if (selected.current) formRef.current?.getForm().loadRecord(selected.current);
},
},
},
{
title: 'Revenue chart',
xtype: 'cartesian',
store,
axes: [{ type: 'numeric', position: 'left' }, { type: 'category', position: 'bottom' }],
series: [{ type: 'bar', xField: 'customer', yField: 'amount' }],
},
],
}), [store]);
const formConfig = useMemo(() => ({
title: 'Order details',
bodyPadding: 12,
defaults: { anchor: '100%' },
items: [
{ xtype: 'textfield', name: 'customer', fieldLabel: 'Customer', allowBlank: false },
{ xtype: 'numberfield', name: 'amount', fieldLabel: 'Amount', minValue: 0 },
{ xtype: 'combobox', name: 'status', fieldLabel: 'Status',
store: ['Open', 'Shipped', 'Closed'], forceSelection: true },
],
buttons: [{
text: 'Save', formBind: true,
handler: () => {
if (!selected.current) return;
formRef.current.getForm().updateRecord(selected.current);
Ext.toast('Order updated');
},
}],
}), []);
const card = (title, value) => (
<ReExt childFlex={1} xtype="panel" config={{ title, bodyPadding: 12, html: `<h2>${value}</h2>` }} />
);
return (
<ReExt xtype="panel" style={{ height: 720 }} config={{ layout: 'border', title: 'Operations dashboard' }}>
<ReExt xtype="container" childRegion="north" childHeight={110}
config={{ layout: { type: 'hbox', align: 'stretch' } }}>
{card('Revenue', `$${Math.round(kpi.revenue).toLocaleString()}`)}
{card('Orders', kpi.count)}
{card('Open orders', kpi.open)}
</ReExt>
<ReExt xtype="treepanel" childRegion="west" childWidth={200} childSplit={true}
config={treeConfig}
onItemclick={(view, record) => filterRegion(record.get('id'))}
/>
<ReExt xtype="tabpanel" childRegion="center" config={tabsConfig} />
<ReExt xtype="form" childRegion="east" childWidth={280} childSplit={true}
config={formConfig}
ready={(cmp) => { formRef.current = cmp; }}
/>
</ReExt>
);
}
That’s eight of the 10 components, one store and no state-syncing code between them. Filtering happens once in the store, edits are saved once in the record, and every component that reads the store updates on its own. To complete the set, add the pivot grid as a third tab and the filter bar from component 4 to the grid’s toolbar.
Dashboard component checklist
Use this table to check any React UI component library against what enterprise dashboards need:
| # | Component | Must-have quality | In ReExt |
|---|---|---|---|
| 1 | Data grid | Grouping, filtering, large data, editing | grid |
| 2 | Charts | Common types, interactions, live updates | cartesian, polar |
| 3 | KPI cards | Responsive row, filter-aware values | Panels in an hbox container |
| 4 | Filter bar | Date range, multi-select, search, applied everywhere | Toolbar with datefield, tagfield, textfield |
| 5 | Resizable layout | Splitters, collapsible regions | border layout with childRegion and childSplit |
| 6 | Tabs | Several views of the same data | tabpanel |
| 7 | Tree navigation | Hierarchies that drive the dashboard | treepanel |
| 8 | Pivot grid | User-arranged summaries with totals | pivotgrid (Enterprise) |
| 9 | Detail form | Load, validate and save the selected record | form with loadRecord and updateRecord |
| 10 | Toolbar, export, notifications | Actions, Excel and CSV export, toasts | Toolbar, gridexporter (Enterprise), Ext.toast |
The last column is the real test of a library: whether all 10 come from one place, share one theme and can read the same data.
Frequently asked questions
What components does an enterprise dashboard need?
Most need a data grid, charts, KPI cards, a filter bar, a resizable layout, tabs, tree navigation, a pivot grid for analysis, a detail form for editing, and a toolbar with export and notifications.
What is the best React UI component library for dashboards?
For enterprise dashboards, look for a library where all of these components share one theme and can read the same data. ReExt, which brings Ext JS’s 140+ components into React, is designed for this kind of data-heavy screen.
How do I keep dashboard components in sync?
Use one shared data source. In ReExt, a single store can feed the grid, chart and form, so filters and edits apply everywhere without extra state code.
Do I need a pivot grid in a dashboard?
If managers or analysts regularly ask for summaries across two dimensions, such as region by quarter, a pivot grid saves them exporting to Excel. ReExt’s pivot grid is part of the Enterprise plan.
Can I build a dashboard with ReExt and Next.js?
Yes. ReExt has setup paths for Vite and Next.js. In Next.js, add ‘use client’ to files that use ReExt components.
Build the dashboard from one set of components
Enterprise dashboards succeed when their components work as one: filters that apply everywhere, a grid and chart that agree, a form that updates both, and a layout users can shape to their work. Choosing React UI components one at a time from different libraries makes that harder with every piece.
ReExt gives you all 10 from one library, with one theme and one shared data store, inside your React app.
Next steps: start a 30-day ReExt trial, read React Grid Component: Layout and Data Grids in One Framework, or see UI Component Library vs Component Framework.
Ask a team with a ten-year-old Ext JS application how much of it is covered…
The Ext JS data grid can do almost anything an enterprise screen needs: grouping, summaries,…
The data grid gets most of the attention in enterprise React apps, and for good…




