Building Better and Easier UIs with React Grid: A Practical Guide

bchiragbchirag
9 min read

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:

  1. 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.

  2. 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.
  3. 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

  1. Simpler Row and Column Management:

    • In the Grid example, the layout is controlled directly using gridTemplateAreas, gridTemplateColumns, and gridTemplateRows. 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).

  2. Multi-Row Sidebar:

    • With Grid, the sidebar naturally spans two rows (gridTemplateAreas assigns it to both sidebar main and sidebar footer).

    • With Flexbox, spanning rows is not straightforward; the sidebar aligns only in one row, requiring adjustments with nested layouts or flex-grow properties.

  3. 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.

  4. 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

FeatureCSS Grid (Grid Layout)Flexbox (Box with Flex)Stack
Primary Use CaseComplex, two-dimensional layouts (rows & columns).One-dimensional layouts (row or column).Simplified vertical or horizontal stacking.
DirectionRows and columns simultaneously.Single axis (row or column) at a time.Single axis (column by default).
Alignment ControlAlign 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.
ResponsivenessEasier to create grids that adapt across breakpoints.Flexible but may require additional CSS for multi-row layouts.Basic responsive stacking.
Ease of UseSlightly 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:

  1. 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.

  2. 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.

  3. Interactive Sidebar:

    • Categories are displayed as clickable chips.

    • Users can filter products or reset all filters for a broader view.

  4. 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! 🙌

0
Subscribe to my newsletter

Read articles from bchirag directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

bchirag
bchirag