Mastering Conditional Cell Styling and Formatting in AG-Grid React
Data grids are essential for displaying large datasets, but raw data can often be overwhelming. This is where conditional cell styling and formatting in AG-Grid React comes to the rescue. By visually highlighting key information, warning signs, or trends, you can transform a plain table into an intuitive and actionable dashboard.
In this installment of our AG-Grid React series, we'll dive deep into three powerful techniques:
cellClassRules: Applying dynamic CSS classes based on cell values.cellStyle: Applying dynamic inline CSS styles directly to cells.valueFormatter: Changing how a cell's value is displayed without altering the underlying data.
Let's explore how to leverage these features to create more readable and impactful data grids.
Enhancing Readability with cellClassRules
cellClassRules is your go-to property when you want to apply CSS classes to cells dynamically based on their data. This is particularly useful for implementing traffic-light indicators, status labels, or any visual cues that benefit from predefined styles.
How it Works
You define cellClassRules as an object within your Column Definition. The keys of this object are the CSS class names you want to apply, and the values are JavaScript expressions or functions that return true or false. If the expression evaluates to true, the corresponding CSS class is applied to the cell.
Example: Performance Metrics
Imagine you have a grid displaying performance scores, and you want to color-code them: green for excellent, amber for good, and red for poor.
Column Definition:
const columnDefs = [
{
field: 'score',
headerName: 'Performance Score',
width: 180,
cellClassRules: {
// Apply 'rag-green' class if score > 75
'rag-green': params => params.value > 75,
// Apply 'rag-amber' class if score is between 25 and 75 (inclusive)
'rag-amber': params => params.value <= 75 && params.value > 25,
// Apply 'rag-red' class if score <= 25
'rag-red': params => params.value <= 25,
// Apply 'bold-text' class if score is very high
'bold-text': params => params.value > 90
}
},
// ... other column definitions
];
In the rules, params is an object that AG-Grid passes to the function, containing useful properties like params.value (the cell's value), params.data (the row's data object), params.colDef, etc. For simpler expressions, you can even use a string expression like 'x.value > 75' where x internally maps to the params object.
Corresponding CSS:
You would define these classes in your global CSS file (e.g., App.css or index.css):
.rag-green {
background-color: #d4edda; /* Light green */
color: #155724; /* Dark green text */
font-weight: bold;
}
.rag-amber {
background-color: #fff3cd; /* Light amber */
color: #856404; /* Dark amber text */
}
.rag-red {
background-color: #f8d7da; /* Light red */
color: #721c24; /* Dark red text */
font-weight: bold;
}
.bold-text {
font-weight: 900; /* Extra bold */
}
This approach keeps your styling concerns separate from your component logic, promoting cleaner code and easier maintenance.
Dynamic Inline Styling with cellStyle
While cellClassRules is excellent for applying predefined CSS classes, sometimes you need to apply inline styles directly to a cell based on its value. This is where the cellStyle property comes in handy.
How it Works
cellStyle can be either a static CSS style object or a function that returns a CSS style object. If it's a function, AG-Grid will call it for each cell, passing the params object, allowing you to compute styles dynamically.
Example: Stock Price Fluctuations
Consider displaying stock price changes, where positive changes are green and negative changes are red.
Column Definition:
const columnDefs = [
{
field: 'priceChange',
headerName: 'Price Change',
width: 150,
cellStyle: params => {
if (params.value > 0) {
return { color: 'green', fontWeight: 'bold' };
}
if (params.value < 0) {
return { color: 'red', fontWeight: 'bold' };
}
return null; // No specific style for zero changes
}
},
// ... other column definitions
];
The function receives the params object, allowing you to inspect the cell's value (`params.value`) and return an inline style object. Returning null or an empty object means no custom inline style will be applied.
When to use cellStyle vs. cellClassRules:
- Use
cellClassRuleswhen you have complex or reusable style definitions that are better managed as CSS classes. It generally leads to better performance for many cells because the browser is more efficient at applying classes than numerous inline styles. - Use
cellStylefor simpler, one-off inline style changes, or when the style value itself is dynamic (e.g., setting a background color based on a color code from the data).
Formatting Display Values with valueFormatter
Conditional styling changes how a cell looks. What if you want to change how the cell's value is displayed without altering the underlying data? This is the purpose of valueFormatter.
How it Works
valueFormatter is a function defined in a Column Definition that takes the params object and returns a formatted string. AG-Grid will use this string for display in the cell, but the actual data value remains unchanged, which is crucial for sorting, filtering, and other grid operations.
Examples: Currency and Date Formatting
Let's format currency values with a '$' prefix and two decimal places, and display dates in a user-friendly format.
Column Definitions:
const columnDefs = [
{
field: 'amount',
headerName: 'Transaction Amount',
width: 180,
valueFormatter: params => {
// Ensure it's a number before formatting
if (typeof params.value === 'number') {
return '$' + params.value.toFixed(2);
}
return params.value; // Return original if not a number
}
},
{
field: 'transactionDate',
headerName: 'Date',
width: 150,
valueFormatter: params => {
if (params.value) {
const date = new Date(params.value);
return date.toLocaleDateString('en-US'); // Format as MM/DD/YYYY
}
return '';
}
},
{
field: 'percentage',
headerName: 'Completion %',
width: 150,
valueFormatter: params => {
if (typeof params.value === 'number') {
return `${params.value.toFixed(1)}%`;
}
return params.value;
}
},
// ... other column definitions
];
valueFormatter is incredibly versatile for:
- Adding currency symbols, percentage signs.
- Formatting numbers (decimal places, thousands separators).
- Displaying dates and times in locale-specific formats.
- Converting boolean values (e.g.,
trueto "Yes",falseto "No"). - Any custom string manipulation for display purposes.
Note: Do not confuse valueFormatter with valueGetter. valueGetter processes the raw data into a new value that AG-Grid then uses for all operations (sorting, filtering, displaying). valueFormatter only affects the *visual representation* of the cell's value.
A Comprehensive Example: Product Inventory Dashboard
Let's put all these concepts together to build a mini product inventory dashboard. We'll track product stock levels, sales performance, and order status, applying conditional styling and formatting to enhance clarity.
First, ensure you have AG-Grid installed:
npm install ag-grid-community ag-grid-react
# or
yarn add ag-grid-community ag-grid-react
Then, create a React component, for instance, ProductGrid.js:
// ProductGrid.js
import React, { useState, useMemo } from 'react';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css'; // Core grid CSS
import 'ag-grid-community/styles/ag-theme-alpine.css'; // Theme CSS
// Define custom CSS rules for our styling
import './ProductGrid.css';
const ProductGrid = () => {
// Sample product data
const [rowData] = useState([
{ id: 'P001', name: 'Laptop Pro 15"', stock: 15, sales: 8500, status: 'Active', lastUpdate: '2023-10-26T10:00:00Z' },
{ id: 'P002', name: 'Gaming Mouse RGB', stock: 3, sales: 1200, status: 'Active', lastUpdate: '2023-10-25T14:30:00Z' },
{ id: 'P003', name: 'Wireless Keyboard X', stock: 0, sales: 500, status: 'Discontinued', lastUpdate: '2023-10-20T09:00:00Z' },
{ id: 'P004', name: 'Monitor UltraWide', stock: 25, sales: 12000, status: 'Active', lastUpdate: '2023-10-26T11:45:00Z' },
{ id: 'P005', name: 'Webcam HD 1080p', stock: 8, sales: 300, status: 'On Hold', lastUpdate: '2023-10-24T16:00:00Z' },
{ id: 'P006', name: 'USB-C Hub', stock: 50, sales: 2500, status: 'Active', lastUpdate: '2023-10-26T08:00:00Z' },
{ id: 'P007', name: 'External SSD 1TB', stock: 1, sales: 7000, status: 'Low Stock', lastUpdate: '2023-10-25T10:00:00Z' },
]);
// Define column properties
const [columnDefs] = useState([
{ field: 'id', headerName: 'Product ID', width: 100 },
{ field: 'name', headerName: 'Product Name', flex: 2, minWidth: 200 },
{
field: 'stock',
headerName: 'Stock Level',
width: 140,
// Apply CSS classes based on stock level
cellClassRules: {
'stock-critical': params => params.value <= 3 && params.value > 0, // Critical but not zero
'stock-out': params => params.value === 0, // Out of stock
'stock-low': params => params.value > 3 && params.value <= 10, // Low stock, but not critical
},
// Format stock numbers into user-friendly text
valueFormatter: params => {
if (params.value === 0) return 'Out of Stock';
if (params.value <= 3) return `Critical: ${params.value}`;
if (params.value <= 10) return `Low: ${params.value}`;
return `${params.value} In Stock`;
}
},
{
field: 'sales',
headerName: 'Total Sales',
width: 160,
// Apply inline styles based on sales performance
cellStyle: params => {
if (params.value < 1000) return { backgroundColor: '#f8d7da', color: '#721c24', fontWeight: 'bold' }; // Reddish for low sales
if (params.value < 5000) return { backgroundColor: '#fff3cd', color: '#856404' }; // Amber for moderate sales
return { backgroundColor: '#d4edda', color: '#155724', fontWeight: 'bold' }; // Greenish for high sales
},
// Format sales figures as currency
valueFormatter: params => params.value ? `$${params.value.toLocaleString()}` : '$0'
},
{
field: 'status',
headerName: 'Status',
width: 120,
// Apply a simple bold style for specific statuses
cellStyle: params => {
if (params.value === 'Discontinued' || params.value === 'On Hold') {
return { fontWeight: 'bold', fontStyle: 'italic', color: '#6c757d' };
}
return null;
}
},
{
field: 'lastUpdate',
headerName: 'Last Update',
width: 180,
valueFormatter: params => {
if (params.value) {
const date = new Date(params.value);
return date.toLocaleString('en-US', {
year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit'
});
}
return '';
}
}
]);
// Default column properties applied to all columns
const defaultColDef = useMemo(() => ({
sortable: true,
filter: true,
resizable: true,
// Allow cell values to be copied to clipboard
suppressPaste: false,
}), []);
return (
<div className="ag-theme-alpine" style={{ height: 500, width: '100%', maxWidth: '1200px', margin: '20px auto' }}>
<h2>Product Inventory Overview</h2>
<AgGridReact
rowData={rowData}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
animateRows={true} // For smoother row updates
/>
</div>
);
};
export default ProductGrid;
And create a ProductGrid.css file in the same directory:
/* ProductGrid.css */
/* Styles for Stock Level */
.stock-critical {
background-color: #ffb3b3; /* Light red */
color: #cc0000;
font-weight: bold;
animation: pulse-critical 1s infinite alternate; /* Simple animation */
}
.stock-out {
background-color: #ff4d4d; /* More intense red */
color: white;
font-weight: bold;
text-transform: uppercase;
}
.stock-low {
background-color: #ffe6b3; /* Light orange */
color: #e68a00;
font-weight: bold;
}
/* Optional animation for critical stock */
@keyframes pulse-critical {
from { transform: scale(1); }
to { transform: scale(1.02); }
}
To display this grid, you would typically render `ProductGrid` in your `App.js` or main component:
// App.js
import React from 'react';
import ProductGrid from './ProductGrid';
function App() {
return (
<div className="App">
<ProductGrid />
</div>
);
}
export default App;
Explanation of Combined Usage:
- Stock Level Column:
cellClassRules: Appliesstock-critical,stock-out, orstock-lowCSS classes based on the numeric value. This immediately draws the eye to problematic stock situations.valueFormatter: Changes the display from a raw number to descriptive text like "Out of Stock" or "Critical: 3", making it more human-readable while keeping the underlying number for sorting/filtering.
- Total Sales Column:
cellStyle: Uses inline background and text colors to categorize sales performance (low, moderate, high) directly within the cell.valueFormatter: Formats the numeric sales figure into a currency string (e.g., "$8,500").
- Status Column:
cellStyle: Applies a bold, italicized, and greyed-out style for 'Discontinued' and 'On Hold' statuses to visually deprioritize them without hiding them.
- Last Update Column:
valueFormatter: Converts the ISO date string into a more readable local date and time format.
This comprehensive example demonstrates how these three properties work together seamlessly to create a highly informative and visually engaging grid experience.
Conclusion
Conditional cell styling with cellClassRules and cellStyle, coupled with display formatting using valueFormatter, are indispensable tools in your AG-Grid React arsenal. They empower you to transform raw, tabular data into meaningful insights, improving data comprehension and user experience significantly.
By judiciously applying these techniques, you can guide your users' attention, highlight critical information, and ensure that your AG-Grid implementations are not just functional, but also incredibly intuitive and user-friendly. Experiment with these properties in your own projects to unlock the full potential of dynamic data visualization in AG-Grid.