Building Better and Easier UIs with React Grid: A Practical Guide
Hey people! Hope everything's good. 😄😄😄 Today, let's dive into building a dynamic Product Dashboard and see how the Grid layout can make our UI smarter and more responsive!
Why Grids Are a Game-Changer in React Layouts
Let’s talk about grids and why they’re worth considering when building layouts in React, especially with MUI.
Picture this: you’re designing a page with a sidebar, a main content section, and a footer. If you’re using the Box
component for everything, you’ll probably end up nesting components inside each other and styling them individually. At first, it might not seem like a big deal. But as the layout becomes more complex, all that nesting can make your code messy and harder to follow.
That’s where grids come in. A grid layout lets you define the structure and styling of your page in one go. No need for excessive nesting or repetitive styling. You can treat each section—like the sidebar, content, and footer—as separate, independent components. The grid handles all the positioning and alignment, so your code stays clean and easy to manage.
Now, don’t get me wrong—if you’re working with smaller components, using a Stack
can still be a great choice. But for more complex layouts, grids are a lifesaver. They keep your code organized, readable, and easier to scale as your app grows.
Intro: What Are Grid Parameters?
CSS grid is a powerful tool for create layout. The parameters specified define what and how the grid is to be structured. Here is a quick overview of the key grid parameters:
gridTemplateColumns
: Defines the number and size of columns in the grid.Example:
gridTemplateColumns: "1fr 2fr"
creates 2 columns one with the second taking up twice the width of first.Example:
gridTemplateColumns: "auto 1fr"
creates 2 columns one takes up width according to its content. It could end up take more space or less and the second column would end up taking the remaining width.
gridTemplateRows
: Specifies the number and size of rows in the grid.- Example:
gridTemplateRows: "100px auto"
creates one row of 100px and another that adjusts to content.
- Example:
gridTemplateAreas
: Lets you specify particular names to grid areas making it more intuitive and readable.gridTemplateAreas: "header header" "sidebar main" "footer footer";
To better showcase the benefits of CSS Grid, let’s work with multi-level nested layout and compare the Box (flexbox) version with the grid. The nested Box will have several levels each with header, sidebar, main content and some with additional sub-levels components inside.
Here are two revised examples—one using CSS Grid and the other using Flexbox (Box)—that clearly highlight the advantages of Grid over Flexbox for managing complex layouts. These examples include a header, sidebar spanning multiple rows, main content, and a footer
Flexbox Layout
import React from "react";
import { Box, Typography, Paper } from "@mui/material";
const FlexboxLayout = () => {
return (
<Box sx={{ padding: 2 }}>
{/* Header */}
<Paper sx={{ padding: 2, textAlign: "center" }}>
<Typography variant="h6">Header</Typography>
</Paper>
{/* Main Layout */}
<Box
sx={{
display: "flex",
flexDirection: { xs: "column", md: "row" },
marginTop: 2,
gap: 2,
}}
>
{/* Sidebar */}
<Paper sx={{ flex: { xs: "none", md: "1" }, padding: 2 }}>
<Typography variant="h6">Sidebar</Typography>
<Typography>Links and more...</Typography>
</Paper>
{/* Main Content */}
<Box sx={{ flex: 3, display: "flex", flexDirection: "column", gap: 2 }}>
<Paper sx={{ padding: 2 }}>
<Typography variant="h6">Main Content</Typography>
<Typography>This is the main content area.</Typography>
</Paper>
{/* Footer Inside */}
<Paper
sx={{
padding: 2,
textAlign: "center",
display: { xs: "block", md: "none" },
}}
>
<Typography variant="h6">Footer</Typography>
<Typography>© 2024 Dashboard</Typography>
</Paper>
</Box>
</Box>
{/* Footer for larger screens */}
<Paper
sx={{
padding: 2,
textAlign: "center",
marginTop: 2,
display: { xs: "none", md: "block" },
}}
>
<Typography variant="h6">Footer</Typography>
<Typography>© 2024 Dashboard</Typography>
</Paper>
</Box>
);
};
export default FlexboxLayout;
You can see how we had to write Footer components 2 times to adjust based on the layout. That’s not in the case of grid layout.
Grid Layout Equivalent
import React from "react";
import { Box, Typography, Paper } from "@mui/material";
const GridLayout = () => {
return (
<Box
sx={{
display: "grid",
gridTemplateAreas: {
xs: `"header" "main" "sidebar" "footer"`, // Stacks on small screens
md: `"header header"
"sidebar main"
"sidebar footer"`, // Sidebar spans rows on medium+ screens
},
gridTemplateColumns: { xs: "1fr", md: "1fr 3fr" }, // Sidebar 1fr, Main 3fr
gridTemplateRows: "auto",
gap: 2,
padding: 2,
}}
>
{/* Header */}
<Paper sx={{ gridArea: "header", padding: 2, textAlign: "center" }}>
<Typography variant="h6">Header</Typography>
</Paper>
{/* Sidebar */}
<Paper sx={{ gridArea: "sidebar", padding: 2 }}>
<Typography variant="h6">Sidebar</Typography>
<Typography>Links and more...</Typography>
</Paper>
{/* Main Content */}
<Paper sx={{ gridArea: "main", padding: 2 }}>
<Typography variant="h6">Main Content</Typography>
<Typography>This is the main content area.</Typography>
</Paper>
{/* Footer */}
<Paper sx={{ gridArea: "footer", padding: 2, textAlign: "center" }}>
<Typography variant="h6">Footer</Typography>
<Typography>© 2024 Dashboard</Typography>
</Paper>
</Box>
);
};
export default GridLayout;
Key Advantages of CSS Grid Over Flexbox
Simpler Row and Column Management:
In the Grid example, the layout is controlled directly using
gridTemplateAreas
,gridTemplateColumns
, andgridTemplateRows
. This eliminates the need for extra nesting or manual row/column management.In the Flexbox example, achieving the same layout requires nested
Box
components and additional logic (e.g., conditionally showing/hiding the footer for small vs. large screens).
Multi-Row Sidebar:
With Grid, the sidebar naturally spans two rows (
gridTemplateAreas
assigns it to bothsidebar main
andsidebar footer
).With Flexbox, spanning rows is not straightforward; the sidebar aligns only in one row, requiring adjustments with nested layouts or flex-grow properties.
Readability and Declarative Layout:
The Grid example uses
gridTemplateAreas
to clearly define the structure (header
,sidebar
,main
,footer
), making the code easier to understand and maintain.The Flexbox example relies on manual alignment and flex properties, which can make it harder to visualize the layout at a glance.
Reduced Nesting:
The Grid example avoids additional nesting by working directly with grid areas.
The Flexbox example needs more nesting (e.g., for handling column and row combinations), which increases complexity in larger layouts.
Table Summary Grid vs Flex
Feature | CSS Grid (Grid Layout) | Flexbox (Box with Flex) | Stack |
Primary Use Case | Complex, two-dimensional layouts (rows & columns). | One-dimensional layouts (row or column). | Simplified vertical or horizontal stacking. |
Direction | Rows and columns simultaneously. | Single axis (row or column) at a time. | Single axis (column by default). |
Alignment Control | Align items across both axes (align-items , justify-items ). | Align items along the primary axis (justify-content ) and cross-axis (align-items ). | Same as Flexbox but pre-configured. |
Responsiveness | Easier to create grids that adapt across breakpoints. | Flexible but may require additional CSS for multi-row layouts. | Basic responsive stacking. |
Ease of Use | Slightly more complex; requires understanding grid lines, tracks, and areas. | Easier for simple layouts. | Simplified for quick use. |
Real-Life Use Case: Product Dashboard with Filters and Responsive Grid
In this example, we build a Product Dashboard to showcase how a grid layout can manage dynamic data effectively. The dashboard displays multiple categories of products, each with details like name, price, and stock status. Users can filter products by category using an interactive sidebar and toggle the stock status of individual items.
Key Features:
Responsive Design with Grid:
The layout adapts seamlessly to different screen sizes.
On mobile, the grid limits the display to 4 products per category, optimizing the user experience for small screens.
Dynamic Data:
Fake data is generated for categories and products, mimicking a real-world e-commerce scenario.
Each product card includes essential information and an interactive button to toggle stock status.
Interactive Sidebar:
Categories are displayed as clickable chips.
Users can filter products or reset all filters for a broader view.
Grid Layout Efficiency:
The sidebar and main content adjust based on screen size, leveraging the
grid-template-areas
property for a clean layout.Product cards are displayed in a dynamic grid, scaling from 1 to 4 columns depending on the screen width.
Why Use a Grid?
The grid simplifies responsive design by enabling easy manipulation of areas and columns.
The
gridColumn: "1 / -1"
property allows the category banners to span across all columns, creating a clear visual separation for each section.
This dashboard demonstrates how grids enhance usability and responsiveness, making it perfect for dashboards, e-commerce sites, or any application managing categorized data.
import React, { useState } from "react";
import { Box, Typography, Chip, Paper, Button } from "@mui/material";
// Fake Data Generator
const generateFakeProducts = () => {
const categories = [
"Electronics",
"Fashion",
"Home Appliances",
"Books",
"Toys",
];
return categories.map((category) => ({
name: category,
products: Array.from(
{ length: Math.floor(Math.random() * 5) + 5 },
(_, i) => ({
id: `${category}-${i}`,
name: `${category} Product ${i + 1}`,
price: `$${(Math.random() * 100).toFixed(2)}`,
inStock: Math.random() > 0.5,
})
),
}));
};
const ProductDashboard = () => {
const [categories, setCategories] = useState(generateFakeProducts());
const [activeFilters, setActiveFilters] = useState([]);
// Handle adding/removing category filters
const toggleFilter = (category) => {
setActiveFilters((prevFilters) =>
prevFilters.includes(category)
? prevFilters.filter((filter) => filter !== category)
: [...prevFilters, category]
);
};
// Reset all filters
const resetFilters = () => setActiveFilters([]);
// Get filtered products
const filteredCategories =
activeFilters.length > 0
? categories.filter((category) => activeFilters.includes(category.name))
: categories;
// Handle product stock toggle
const toggleStock = (categoryIndex, productIndex) => {
const updatedCategories = [...categories];
updatedCategories[categoryIndex].products[productIndex].inStock =
!updatedCategories[categoryIndex].products[productIndex].inStock;
setCategories(updatedCategories);
};
return (
<Box
sx={{
display: "grid",
gridTemplateAreas: {
xs: `"filters" "main"`, // Stack filters above main content on small screens
md: `"filters main"`, // Sidebar and main content side by side on medium+ screens
},
gridTemplateColumns: { xs: "1fr", md: "1fr 3fr" },
gap: 3,
padding: 3,
}}
>
{/* Sidebar */}
<Box
sx={{
gridArea: "filters",
backgroundColor: "primary.light",
color: "white",
padding: 2,
borderRadius: 2,
}}
>
<Typography variant="h6" sx={{ marginBottom: 2 }}>
Filters
</Typography>
<Typography variant="body2" sx={{ marginBottom: 1 }}>
Categories
</Typography>
<Box sx={{ display: "flex", flexWrap: "wrap", gap: 1 }}>
{categories.map((category) => (
<Chip
key={category.name}
label={category.name}
onClick={() => toggleFilter(category.name)}
color={
activeFilters.includes(category.name) ? "secondary" : "default"
}
clickable
/>
))}
</Box>
<Button
variant="contained"
color="secondary"
size="small"
sx={{ marginTop: 2 }}
onClick={resetFilters}
>
Reset Filters
</Button>
</Box>
{/* Main Content */}
<Box sx={{ gridArea: "main" }}>
{filteredCategories.map((category, categoryIndex) => (
<Box key={categoryIndex} sx={{ marginBottom: 4 }}>
{/* Category Banner */}
<Box
sx={{
gridColumn: "1 / -1",
padding: 2,
backgroundColor: "primary.dark",
color: "white",
borderRadius: 2,
marginBottom: 2,
}}
>
<Typography variant="h4">Top Deals in {category.name}</Typography>
</Box>
{/* Product Grid */}
<Box
sx={{
display: "grid",
gridTemplateColumns: {
xs: "1fr",
sm: "repeat(2, 1fr)",
md: "repeat(3, 1fr)",
lg: "repeat(4, 1fr)",
},
gap: 2,
}}
>
{category.products
.slice(
0,
window.innerWidth < 600 ? 4 : category.products.length
)
.map((product, productIndex) => (
<Paper
key={product.id}
sx={{
padding: 2,
borderRadius: 2,
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
height: "100%",
}}
>
<Typography variant="h6" sx={{ marginBottom: 1 }}>
{product.name}
</Typography>
<Typography variant="body2" color="text.secondary">
Price: {product.price}
</Typography>
<Chip
label={product.inStock ? "In Stock" : "Out of Stock"}
color={product.inStock ? "success" : "error"}
sx={{ marginTop: 1 }}
/>
<Button
variant="outlined"
color="primary"
size="small"
sx={{ marginTop: 2 }}
onClick={() => toggleStock(categoryIndex, productIndex)}
>
Toggle Stock
</Button>
</Paper>
))}
</Box>
</Box>
))}
</Box>
</Box>
);
};
export default ProductDashboard;
Now to wrap up this example showcases how to effectively use the Grid layout in a real-world scenario like a Product Dashboard. The Grid's responsiveness and flexibility make it an ideal choice for managing complex layouts across devices. Experiment with the code, tweak the styles, and adapt it to your projects to create engaging and custom Uis! 🚀
Thanks for staying till the end! 🙌
Subscribe to my newsletter
Read articles from bchirag directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by