This guide shows you how to implement and use the new enhanced column renderers in ResponsiveDataGrid for optimal display in both table and card views.
Enhanced column renderers allow you to specify different formatting logic for table view vs card view, optimizing the display for each context:
- Table View: More space for detailed information
- Card View: Limited space requiring concise, mobile-friendly display
Before using ResponsiveDataGrid, import the necessary stylesheets in the correct order:
// 1. Core ResponsiveDataGrid styles (always required)
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/css';
// 2. Healthcare templates (if using NHS healthcare data)
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/HealthcareCardTemplates/css';Critical: Import stylesheets in this specific order:
ResponsiveDataGrid.css- Core responsive grid functionalityHealthcareCardTemplates.css- NHS healthcare card styling- Your custom CSS - Application-specific overrides
// ✅ Correct order
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/css';
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/HealthcareCardTemplates/css';
import './my-custom-styles.css';
// ❌ Wrong order - healthcare styles may not apply correctly
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/HealthcareCardTemplates/css';
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/css';import { ResponsiveDataGrid } from '@nhsdigital/fdp-design-system';
import { ColumnDefinition } from '@nhsdigital/fdp-design-system';const columns: ColumnDefinition[] = [
{
key: 'name',
label: 'Name',
sortable: true,
// Standard renderer (fallback)
render: (data) => data.name,
// Table-specific renderer (detailed)
tableRenderer: (data) => `${data.name} (ID: ${data.id})`,
// Card-specific renderer (concise)
cardRenderer: (data) => {
return data.name.length > 20
? `${data.name.substring(0, 17)}...`
: data.name;
}
}
];<ResponsiveDataGrid
ariaLabel="My data grid"
tabPanels={[
{
id: 'data',
label: 'Data',
ariaLabel: 'Data table',
data: myData,
columns: columns // Enhanced columns with dual renderers
}
]}
/>Perfect for NHS healthcare data that needs different levels of detail:
const healthcareColumns: ColumnDefinition[] = [
{
key: 'patient_name',
label: 'Patient',
sortable: true,
tableRenderer: (data) => {
// Table: Full name with NHS number
return `${formatName(data.patient_name)} (NHS: ${data.nhs_number})`;
},
cardRenderer: (data) => {
// Card: Just abbreviated name
return abbreviateName(data.patient_name);
}
},
{
key: 'ews_score',
label: 'EWS',
sortable: true,
tableRenderer: (data) => {
// Table: Detailed risk assessment
const score = data.ews_score;
const risk = score >= 7 ? 'HIGH' : score >= 3 ? 'MEDIUM' : 'LOW';
return `${score} (${risk} RISK)`;
},
cardRenderer: (data) => {
// Card: Visual indicator
const score = data.ews_score;
const icon = score >= 7 ? '🔴' : score >= 3 ? '🟡' : '🟢';
return `${icon} ${score}`;
}
}
];ResponsiveDataGrid supports two sorting modes to suit different use cases:
By default, ResponsiveDataGrid includes a basic dropdown sorting interface that requires only the core CSS:
<ResponsiveDataGrid
ariaLabel="My data grid"
tabPanels={[
{
id: 'data',
label: 'Data',
ariaLabel: 'Data table',
data: myData,
columns: columns // Columns with sortable: true
}
]}
/>CSS Required:
// ResponsiveDataGrid includes all styling for simple sorting controls
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/css';For enhanced sorting capabilities with multi-column sorting and visual status indicators, enable advanced sorting:
<ResponsiveDataGrid
enableAdvancedSorting={true}
ariaLabel="My data grid"
tabPanels={[
{
id: 'data',
label: 'Data',
ariaLabel: 'Data table',
data: myData,
columns: columns
}
]}
/>Additional CSS Required for Advanced Sorting:
// Core ResponsiveDataGrid styles (includes Select and Button for simple sorting)
import '@nhsdigital/fdp-design-system/components/ResponsiveDataGrid/css';
// Additional dependencies for advanced sorting features
import '@nhsdigital/fdp-design-system/components/SortableDataTable/SortStatusControl/css';
import '@nhsdigital/fdp-design-system/components/Tag/css';| Mode | Use Case | CSS Dependencies | Features |
|---|---|---|---|
| Simple | Basic data display with dropdown sorting | ResponsiveDataGrid.css only* | Single column sorting via dropdown |
| Advanced | Complex data grids requiring multi-column sorting | ResponsiveDataGrid.css and SortStatusControl.css and Tag.css | Multi-column sorting, visual indicators, enhanced UX |
*ResponsiveDataGrid.css automatically includes Select and Button styling needed for simple sorting controls.
Great for financial data that needs precision vs readability:
const financialColumns: ColumnDefinition[] = [
{
key: 'amount',
label: 'Amount',
sortable: true,
align: 'right',
tableRenderer: (data) => {
// Table: Full currency formatting
return new Intl.NumberFormat('en-GB', {
style: 'currency',
currency: 'GBP'
}).format(data.amount);
},
cardRenderer: (data) => {
// Card: Abbreviated for large numbers
const amount = data.amount;
if (amount >= 1000000) return `£${(amount/1000000).toFixed(1)}M`;
if (amount >= 1000) return `£${(amount/1000).toFixed(1)}K`;
return `£${amount}`;
}
},
{
key: 'transaction_date',
label: 'Date',
sortable: true,
tableRenderer: (data) => {
// Table: Full date with day of week
return new Date(data.transaction_date).toLocaleDateString('en-GB', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
},
cardRenderer: (data) => {
// Card: Relative time or short date
const date = new Date(data.transaction_date);
const now = new Date();
const diffDays = Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
if (diffDays === 0) return 'Today';
if (diffDays === 1) return 'Yesterday';
if (diffDays < 7) return `${diffDays} days ago`;
return date.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });
}
}
];{
key: 'status',
label: 'Status',
tableRenderer: (data) => `🟢 Active since ${data.start_date}`,
cardRenderer: (data) => '🟢'
}{
key: 'description',
label: 'Description',
tableRenderer: (data) => data.description,
cardRenderer: (data) => data.description.length > 30
? `${data.description.substring(0, 27)}...`
: data.description
}{
key: 'contact',
label: 'Contact',
tableRenderer: (data) => `${data.name} (${data.email})`,
cardRenderer: (data) => data.phone || data.email
}Look for columns where you currently compromise between table and card display.
// Before
{
key: 'amount',
label: 'Amount',
render: (data) => `£${data.amount}`
}
// After (keeping original as fallback)
{
key: 'amount',
label: 'Amount',
render: (data) => `£${data.amount}`, // Keep for backward compatibility
tableRenderer: (data) => formatCurrency(data.amount), // Add table enhancement
cardRenderer: (data) => abbreviateAmount(data.amount) // Add card enhancement
}Always test your enhanced renderers in both table and card views:
// Force table view for testing
<ResponsiveDataGrid forceLayout="table" {...props} />
// Force card view for testing
<ResponsiveDataGrid forceLayout="cards" {...props} />- Show complete, detailed information
- Use consistent formatting (dates, currency, etc.)
- Include helpful context (IDs, codes, etc.)
- Ensure rendered values work with sorting
- Prioritize most important information
- Use visual indicators (icons, colors)
- Keep text concise for mobile screens
- Consider touch target sizes
- Always provide fallback
renderfunction - Test on different screen sizes
- Verify accessibility with screen readers
- Performance test with large datasets
// ❌ Bad: No fallback
{
key: 'amount',
tableRenderer: (data) => formatCurrency(data.amount),
cardRenderer: (data) => abbreviateAmount(data.amount)
}
// ✅ Good: With fallback
{
key: 'amount',
render: (data) => `£${data.amount}`, // Fallback
tableRenderer: (data) => formatCurrency(data.amount),
cardRenderer: (data) => abbreviateAmount(data.amount)
}// ❌ Bad: Too much text for card
cardRenderer: (data) => `Patient: ${data.name}, Ward: ${data.ward}, Consultant: ${data.consultant}`
// ✅ Good: Concise for card
cardRenderer: (data) => data.nameMake sure your table renderer produces sortable values:
// ❌ Bad: HTML in table renderer breaks sorting
tableRenderer: (data) => `<span class="highlight">${data.value}</span>`
// ✅ Good: Clean values that sort properly
tableRenderer: (data) => `⭐ ${data.value}`| Property | Purpose | Priority |
|---|---|---|
tableRenderer |
Table view formatting | 1st for table |
cardRenderer |
Card view formatting | 2nd for card |
render |
Fallback formatting | Fallback for both |
- Check the complete documentation
- See working examples
- Review test cases
- View Storybook stories
The enhanced renderers give you fine-grained control over how your data appears in different contexts while maintaining full backward compatibility.