Skillversitas

CSS Grid - Two-Dimensional Layout

CSS Grid Layout is a two-dimensional layout system that lets you control the layout simultaneously along the row and column axes. Unlike Flexbox, which works in one dimension, Grid is designed for building complex page structures such as dashboards, galleries, and page layouts.

Grid introduces the concepts of grid containers, grid items, grid lines, grid tracks (rows/columns), and grid cells. You first define the grid structure, then place the items into that grid.

Advantages of CSS Grid:
- Full control over two-dimensional layouts
- Ideal for page layouts and dashboards
- Named areas make code easier to read
- The fr unit divides space proportionally
- minmax() and auto-fit make grids automatically responsive without media queries


Code Example

CSS Grid Basics

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>CSS Grid Dasar</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: 'Segoe UI', Tahoma, sans-serif;
      padding: 20px;
      background: #f0f2f5;
    }

    h2 {
      margin-bottom: 15px;
      color: #1a1a2e;
    }

    .section {
      margin-bottom: 40px;
    }

    .grid-item {
      padding: 20px;
      background: linear-gradient(135deg, #667eea, #764ba2);
      color: white;
      border-radius: 8px;
      text-align: center;
      font-weight: 600;
    }

    .grid-item:nth-child(2) {
      background: linear-gradient(135deg, #f093fb, #f5576c);
    }

    .grid-item:nth-child(3) {
      background: linear-gradient(135deg, #4facfe, #00f2fe);
    }

    .grid-item:nth-child(4) {
      background: linear-gradient(135deg, #43e97b, #38f9d7);
    }

    .grid-item:nth-child(5) {
      background: linear-gradient(135deg, #fa709a, #fee140);
    }

    .grid-item:nth-child(6) {
      background: linear-gradient(135deg, #a18cd1, #fbc2eb);
    }

    .label {
      font-size: 12px;
      color: #64748b;
      margin-bottom: 8px;
      font-family: monospace;
      background: #e2e8f0;
      display: inline-block;
      padding: 3px 8px;
      border-radius: 4px;
      margin-top: 15px;
    }

    /* ===== BASIC GRID ===== */
    .basic-grid {
      display: grid;
      grid-template-columns: 200px 200px 200px;
      gap: 15px;
    }

    /* ===== FR UNIT ===== */
    .fr-grid {
      display: grid;
      grid-template-columns: 1fr 2fr 1fr;
      gap: 15px;
    }

    /* ===== REPEAT ===== */
    .repeat-grid {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 15px;
    }

    /* ===== GRID ROWS ===== */
    .rows-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: 100px 200px 100px;
      gap: 15px;
    }

    /* ===== GRID COLUMN SPAN ===== */
    .span-grid {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 15px;
    }

    .span-grid .span-full {
      grid-column: 1 / -1;
    }

    .span-grid .span-two {
      grid-column: span 2;
    }

    /* ===== GRID ROW SPAN ===== */
    .row-span-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(3, 100px);
      gap: 15px;
    }

    .row-span-grid .span-row {
      grid-row: span 2;
    }

    /* ===== MINMAX + AUTO-FIT ===== */
    .auto-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 15px;
    }

    .auto-grid .grid-item {
      min-height: 120px;
    }

    /* ===== AUTO-FILL ===== */
    .autofill-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
      gap: 15px;
    }

    /* ===== GRID TEMPLATE AREAS ===== */
    .areas-layout {
      display: grid;
      grid-template-areas:
        "header  header  header"
        "sidebar main    aside"
        "footer  footer  footer";
      grid-template-columns: 200px 1fr 200px;
      grid-template-rows: auto 1fr auto;
      gap: 15px;
      min-height: 400px;
    }

    .areas-layout .header {
      grid-area: header;
      background: #1a1a2e;
      color: white;
      padding: 20px;
      border-radius: 8px;
      text-align: center;
    }

    .areas-layout .sidebar {
      grid-area: sidebar;
      background: #e2e8f0;
      padding: 20px;
      border-radius: 8px;
    }

    .areas-layout .main {
      grid-area: main;
      background: white;
      padding: 20px;
      border-radius: 8px;
    }

    .areas-layout .aside {
      grid-area: aside;
      background: #e2e8f0;
      padding: 20px;
      border-radius: 8px;
    }

    .areas-layout .footer {
      grid-area: footer;
      background: #1a1a2e;
      color: white;
      padding: 20px;
      border-radius: 8px;
      text-align: center;
    }

    /* ===== NAMED GRID AREAS ===== */
    .dashboard-layout {
      display: grid;
      grid-template-areas:
        "nav     nav     nav"
        "sidebar stats   stats"
        "sidebar chart   table"
        "sidebar chart   table";
      grid-template-columns: 250px 1fr 1fr;
      grid-template-rows: 60px 120px 1fr 1fr;
      gap: 15px;
      min-height: 500px;
    }

    .dashboard-layout .nav {
      grid-area: nav;
      background: #1e293b;
      color: white;
      display: flex;
      align-items: center;
      padding: 0 20px;
      border-radius: 8px;
    }

    .dashboard-layout .sidebar {
      grid-area: sidebar;
      background: #334155;
      color: white;
      padding: 20px;
      border-radius: 8px;
    }

    .dashboard-layout .stats {
      grid-area: stats;
      background: white;
      padding: 20px;
      border-radius: 8px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 20px;
      font-weight: bold;
    }

    .dashboard-layout .chart {
      grid-area: chart;
      background: white;
      padding: 20px;
      border-radius: 8px;
    }

    .dashboard-layout .table-area {
      grid-area: table;
      background: white;
      padding: 20px;
      border-radius: 8px;
      overflow: auto;
    }
  </style>
</head>
<body>
  <!-- Basic Grid -->
  <div class="section">
    <h2>Basic Grid (grid-template-columns)</h2>
    <span class="label">grid-template-columns: 200px 200px 200px</span>
    <div class="basic-grid">
      <div class="grid-item">1</div>
      <div class="grid-item">2</div>
      <div class="grid-item">3</div>
    </div>
  </div>

  <!-- FR Unit -->
  <div class="section">
    <h2>FR Unit</h2>
    <span class="label">grid-template-columns: 1fr 2fr 1fr</span>
    <div class="fr-grid">
      <div class="grid-item">1fr</div>
      <div class="grid-item">2fr</div>
      <div class="grid-item">1fr</div>
    </div>
  </div>

  <!-- Repeat -->
  <div class="section">
    <h2>Repeat Function</h2>
    <span class="label">grid-template-columns: repeat(4, 1fr)</span>
    <div class="repeat-grid">
      <div class="grid-item">1</div>
      <div class="grid-item">2</div>
      <div class="grid-item">3</div>
      <div class="grid-item">4</div>
    </div>
  </div>

  <!-- Grid Template Rows -->
  <div class="section">
    <h2>Grid Template Rows</h2>
    <span class="label">grid-template-rows: 100px 200px 100px</span>
    <div class="rows-grid">
      <div class="grid-item">R1</div>
      <div class="grid-item">R2</div>
      <div class="grid-item">R3</div>
      <div class="grid-item">R4</div>
      <div class="grid-item">R5</div>
      <div class="grid-item">R6</div>
    </div>
  </div>

  <!-- Grid Column Span -->
  <div class="section">
    <h2>Grid Column Span</h2>
    <span class="label">grid-column: span 2 | grid-column: 1 / -1</span>
    <div class="span-grid">
      <div class="grid-item span-full">Span Full (1 / -1)</div>
      <div class="grid-item span-two">Span 2</div>
      <div class="grid-item">1</div>
      <div class="grid-item">1</div>
      <div class="grid-item span-two">Span 2</div>
    </div>
  </div>

  <!-- Grid Row Span -->
  <div class="section">
    <h2>Grid Row Span</h2>
    <span class="label">grid-row: span 2</span>
    <div class="row-span-grid">
      <div class="grid-item span-row">Row Span 2</div>
      <div class="grid-item">1</div>
      <div class="grid-item">2</div>
      <div class="grid-item">3</div>
      <div class="grid-item">4</div>
    </div>
  </div>

  <!-- Auto-Fit + MinMax -->
  <div class="section">
    <h2>Auto-Fit + MinMax (Responsive without media queries)</h2>
    <span class="label">grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</span>
    <div class="auto-grid">
      <div class="grid-item">Auto 1</div>
      <div class="grid-item">Auto 2</div>
      <div class="grid-item">Auto 3</div>
      <div class="grid-item">Auto 4</div>
      <div class="grid-item">Auto 5</div>
      <div class="grid-item">Auto 6</div>
    </div>
  </div>

  <!-- Auto-Fill -->
  <div class="section">
    <h2>Auto-Fill</h2>
    <span class="label">grid-template-columns: repeat(auto-fill, minmax(180px, 1fr))</span>
    <div class="autofill-grid">
      <div class="grid-item">Fill 1</div>
      <div class="grid-item">Fill 2</div>
      <div class="grid-item">Fill 3</div>
      <div class="grid-item">Fill 4</div>
      <div class="grid-item">Fill 5</div>
    </div>
  </div>

  <!-- Grid Template Areas -->
  <div class="section">
    <h2>Grid Template Areas (Page Layout)</h2>
    <span class="label">grid-template-areas: "header header header" "sidebar main aside" "footer footer footer"</span>
    <div class="areas-layout">
      <div class="header">Header (grid-area: header)</div>
      <div class="sidebar">Sidebar (grid-area: sidebar)</div>
      <div class="main">Main Content (grid-area: main)</div>
      <div class="aside">Aside (grid-area: aside)</div>
      <div class="footer">Footer (grid-area: footer)</div>
    </div>
  </div>

  <!-- Dashboard Layout -->
  <div class="section">
    <h2>Dashboard Layout with Named Areas</h2>
    <div class="dashboard-layout">
      <nav class="nav">Navigation Bar</nav>
      <aside class="sidebar">
        <p>Sidebar Menu</p>
        <br>
        <p>• Dashboard</p>
        <p>• Analytics</p>
        <p>• Settings</p>
      </aside>
      <div class="stats">
        <div>📊 Stats Area</div>
      </div>
      <div class="chart">
        <h3>Chart Area</h3>
        <p style="color: #94a3b8; margin-top: 10px;">Data charts will appear here</p>
      </div>
      <div class="table-area">
        <h3>Data Table</h3>
        <p style="color: #94a3b8; margin-top: 10px;">Data tables will appear here</p>
      </div>
    </div>
  </div>
</body>
</html>

Code Explanation

display: grid

Enables CSS Grid on the container. All children instantly become grid items.

.container {
  display: grid;
}

grid-template-columns & grid-template-rows

Defines the number and size of columns/rows:

/* Fixed pixel */
grid-template-columns: 200px 200px 200px;

/* FR unit - proportional */
grid-template-columns: 1fr 2fr 1fr;

/* Repeat - shorthand */
grid-template-columns: repeat(3, 1fr);

/* Mixed */
grid-template-columns: 200px 1fr 200px;

FR Unit

fr (fraction) divides the remaining space proportionally. 1fr 2fr 1fr means the middle column is twice as wide as the left and right columns.

repeat()

repeat(count, size) shortens repeated declarations:

/* Same as: 1fr 1fr 1fr 1fr */
grid-template-columns: repeat(4, 1fr);

/* Same as: 100px 1fr 100px 1fr 100px 1fr */
grid-template-columns: repeat(3, 100px 1fr);

gap

Spacing between grid items (previously grid-gap):

gap: 15px;            /* Horizontal and vertical the same */
gap: 15px 20px;       /* Row gap | Column gap */

grid-column & grid-row

Determines an item's position in the grid:

/* From line 1 to line 3 */
.item { grid-column: 1 / 3; }

/* Span 2 columns */
.item { grid-column: span 2; }

/* From start to end */
.item { grid-column: 1 / -1; }

/* Row span */
.item { grid-row: span 2; }

minmax()

Defines the minimum and maximum size bounds of a track:

/* Column minimum 200px, maximum fills remaining space */
grid-template-columns: repeat(3, minmax(200px, 1fr));

auto-fit vs auto-fill

Both work with minmax(), but have different behavior:

  • auto-fit — Empty columns collapse, and items stretch to fill the space
  • auto-fill — Empty columns are kept even when empty
/* Responsive grid - minimum 200px, items stretch if there is leftover space */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

grid-template-areas

Defines the layout using named areas that read like the visual layout:

.layout {
  display: grid;
  grid-template-areas:
    "header  header  header"
    "sidebar main    aside"
    "footer  footer  footer";
  grid-template-columns: 200px 1fr 200px;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }

Areas with the same name are merged together. This is very intuitive for page layouts.


Everyday Use Cases

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }

    .gallery {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      grid-auto-rows: 200px;
      gap: 10px;
      padding: 20px;
    }

    .gallery-item {
      border-radius: 12px;
      overflow: hidden;
      position: relative;
    }

    .gallery-item.featured {
      grid-column: span 2;
      grid-row: span 2;
    }

    .gallery-item.tall {
      grid-row: span 2;
    }

    .gallery-item.wide {
      grid-column: span 2;
    }

    .gallery-item img,
    .gallery-item .img-placeholder {
      width: 100%;
      height: 100%;
      object-fit: cover;
      display: block;
    }

    .img-placeholder {
      display: flex;
      align-items: center;
      justify-content: center;
      color: white;
      font-weight: bold;
      font-size: 18px;
    }

    .img-placeholder:nth-child(1) { background: #667eea; }

    .gallery-item:nth-child(1) .img-placeholder { background: #667eea; }
    .gallery-item:nth-child(2) .img-placeholder { background: #f093fb; }
    .gallery-item:nth-child(3) .img-placeholder { background: #4facfe; }
    .gallery-item:nth-child(4) .img-placeholder { background: #43e97b; }
    .gallery-item:nth-child(5) .img-placeholder { background: #fa709a; }
    .gallery-item:nth-child(6) .img-placeholder { background: #a18cd1; }
    .gallery-item:nth-child(7) .img-placeholder { background: #ffecd2; color: #333; }
    .gallery-item:nth-child(8) .img-placeholder { background: #f5576c; }
  </style>
</head>
<body>
  <div class="gallery">
    <div class="gallery-item featured">
      <div class="img-placeholder">Featured</div>
    </div>
    <div class="gallery-item tall">
      <div class="img-placeholder">Tall</div>
    </div>
    <div class="gallery-item">
      <div class="img-placeholder">1</div>
    </div>
    <div class="gallery-item">
      <div class="img-placeholder">2</div>
    </div>
    <div class="gallery-item wide">
      <div class="img-placeholder">Wide</div>
    </div>
    <div class="gallery-item">
      <div class="img-placeholder">3</div>
    </div>
    <div class="gallery-item">
      <div class="img-placeholder">4</div>
    </div>
  </div>
</body>
</html>

2. Pricing Table Responsive

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }

    .pricing-section {
      padding: 40px 20px;
    }

    .pricing-section h2 {
      text-align: center;
      font-size: 32px;
      margin-bottom: 10px;
    }

    .pricing-section .subtitle {
      text-align: center;
      color: #64748b;
      margin-bottom: 40px;
    }

    .pricing-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
      gap: 25px;
      max-width: 1000px;
      margin: 0 auto;
    }

    .pricing-card {
      background: white;
      border-radius: 16px;
      padding: 40px 30px;
      text-align: center;
      box-shadow: 0 4px 20px rgba(0,0,0,0.08);
      transition: transform 0.3s;
      position: relative;
    }

    .pricing-card:hover {
      transform: translateY(-5px);
    }

    .pricing-card.popular {
      border: 3px solid #667eea;
    }

    .pricing-card.popular::before {
      content: "Populer";
      position: absolute;
      top: -12px;
      left: 50%;
      transform: translateX(-50%);
      background: #667eea;
      color: white;
      padding: 4px 16px;
      border-radius: 20px;
      font-size: 12px;
      font-weight: bold;
    }

    .pricing-card h3 {
      font-size: 22px;
      margin-bottom: 10px;
    }

    .pricing-card .price {
      font-size: 48px;
      font-weight: bold;
      color: #667eea;
      margin: 20px 0;
    }

    .pricing-card .price span {
      font-size: 16px;
      color: #94a3b8;
      font-weight: normal;
    }

    .pricing-features {
      list-style: none;
      margin: 25px 0;
      text-align: left;
    }

    .pricing-features li {
      padding: 10px 0;
      border-bottom: 1px solid #f1f5f9;
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .pricing-features li::before {
      content: "✓";
      color: #43e97b;
      font-weight: bold;
    }

    .btn-pricing {
      width: 100%;
      padding: 14px;
      border: 2px solid #667eea;
      background: transparent;
      color: #667eea;
      border-radius: 8px;
      font-size: 16px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.3s;
    }

    .pricing-card.popular .btn-pricing {
      background: #667eea;
      color: white;
    }

    .btn-pricing:hover {
      background: #667eea;
      color: white;
    }
  </style>
</head>
<body>
  <section class="pricing-section">
    <h2>Choose Plan</h2>
    <p class="subtitle">Find the plan that fits your needs</p>
    <div class="pricing-grid">
      <div class="pricing-card">
        <h3>Basic</h3>
        <div class="price">$99<span>/month</span></div>
        <ul class="pricing-features">
          <li>5 Proyek</li>
          <li>10GB Storage</li>
          <li>Email Support</li>
          <li>Analytics Dasar</li>
        </ul>
        <button class="btn-pricing">Choose Basic</button>
      </div>
      <div class="pricing-card popular">
        <h3>Pro</h3>
        <div class="price">$199<span>/month</span></div>
        <ul class="pricing-features">
          <li>Unlimited Proyek</li>
          <li>100GB Storage</li>
          <li>Priority Support</li>
          <li>Advanced Analytics</li>
        </ul>
        <button class="btn-pricing">Choose Pro</button>
      </div>
      <div class="pricing-card">
        <h3>Enterprise</h3>
        <div class="price">$499<span>/month</span></div>
        <ul class="pricing-features">
          <li>Unlimited Everything</li>
          <li>1TB Storage</li>
          <li>24/7 Support</li>
          <li>Custom Analytics</li>
        </ul>
        <button class="btn-pricing">Choose Enterprise</button>
      </div>
    </div>
  </section>
</body>
</html>

Explanation: grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)) creates a responsive grid without media queries. The number of cards adjusts automatically based on the viewport width. As the screen narrows, fewer cards fit per row.

3. Responsive Dashboard

<!DOCTYPE html>
<html lang="en">
<head>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }

    .dashboard {
      display: grid;
      grid-template-columns: 260px 1fr;
      grid-template-rows: 60px 1fr;
      min-height: 100vh;
    }

    .dash-nav {
      grid-column: 1 / -1;
      background: #1e293b;
      color: white;
      display: flex;
      align-items: center;
      padding: 0 25px;
      justify-content: space-between;
    }

    .dash-sidebar {
      background: #334155;
      color: white;
      padding: 20px;
    }

    .dash-sidebar ul {
      list-style: none;
    }

    .dash-sidebar li {
      padding: 12px 15px;
      border-radius: 8px;
      cursor: pointer;
      margin-bottom: 5px;
      transition: background 0.2s;
    }

    .dash-sidebar li:hover,
    .dash-sidebar li.active {
      background: #475569;
    }

    .dash-content {
      padding: 25px;
      background: #f1f5f9;
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
      grid-auto-rows: minmax(150px, auto);
      gap: 20px;
      align-content: start;
    }

    .stat-card {
      background: white;
      border-radius: 12px;
      padding: 25px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.06);
    }

    .stat-card h4 {
      color: #64748b;
      font-size: 14px;
      margin-bottom: 10px;
    }

    .stat-card .stat-value {
      font-size: 32px;
      font-weight: bold;
      color: #1e293b;
    }

    .stat-card:nth-child(1) .stat-value { color: #667eea; }
    .stat-card:nth-child(2) .stat-value { color: #f5576c; }
    .stat-card:nth-child(3) .stat-value { color: #43e97b; }
    .stat-card:nth-child(4) .stat-value { color: #fa709a; }

    .chart-card {
      background: white;
      border-radius: 12px;
      padding: 25px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.06);
      grid-column: span 2;
    }

    .chart-placeholder {
      height: 200px;
      background: linear-gradient(180deg, #e2e8f0 0%, #f1f5f9 100%);
      border-radius: 8px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #94a3b8;
    }

    @media (max-width: 768px) {
      .dashboard {
        grid-template-columns: 1fr;
      }

      .dash-sidebar {
        display: none;
      }

      .chart-card {
        grid-column: span 1;
      }
    }
  </style>
</head>
<body>
  <div class="dashboard">
    <nav class="dash-nav">
      <strong>Dashboard</strong>
      <span>Admin User</span>
    </nav>
    <aside class="dash-sidebar">
      <ul>
        <li class="active">Dashboard</li>
        <li>Analytics</li>
        <li>Users</li>
        <li>Settings</li>
      </ul>
    </aside>
    <main class="dash-content">
      <div class="stat-card">
        <h4>Total Users</h4>
        <div class="stat-value">12,345</div>
      </div>
      <div class="stat-card">
        <h4>Revenue</h4>
        <div class="stat-value">$45M</div>
      </div>
      <div class="stat-card">
        <h4>Orders</h4>
        <div class="stat-value">1,234</div>
      </div>
      <div class="stat-card">
        <h4>Growth</h4>
        <div class="stat-value">+23%</div>
      </div>
      <div class="chart-card">
        <h4>Grafik Penjualan</h4>
        <div class="chart-placeholder">📊 Chart Area</div>
      </div>
    </main>
  </div>
</body>
</html>

Explanation: The dashboard uses grid-template-columns: 260px 1fr for a fixed sidebar and flexible content. The stat cards use repeat(auto-fit, minmax(220px, 1fr)) for responsiveness. The media query is only used to hide the sidebar on mobile.


Conclusion

CSS Grid is an extremely powerful two-dimensional layout system in CSS. Key points:

  1. fr unit — Divides space proportionally
  2. repeat() — Shortens repeated declarations
  3. minmax() + auto-fit/auto-fill — Responsive grids without media queries
  4. grid-template-areas — Easy-to-read visual layouts
  5. grid-column/grid-row — Controls item spans

Grid and Flexbox complement each other. Use Grid for page layouts (two dimensions) and Flexbox for components (one dimension).