Difficulty: Intermediate
By default, grid items are placed automatically into the next available cell, flowing left to right and top to bottom. However, CSS Grid's real power comes from the ability to explicitly place items at specific positions using grid line numbers. Grid lines are the dividers between tracks -- a 3-column grid has 4 vertical lines (numbered 1 through 4) and the lines can be referenced to position items precisely.
The grid-column property is a shorthand for grid-column-start and grid-column-end. It specifies which vertical grid lines an item spans between. For example, grid-column: 1 / 3 places an item from line 1 to line 3, spanning two columns. The line numbers start at 1 (not 0) and the end line is exclusive -- the item occupies the space between the start and end lines. You can also use negative numbers to count from the end: grid-column: 1 / -1 spans from the first line to the last, covering all columns.
The grid-row property works identically but for horizontal lines. grid-row: 2 / 4 places an item from row line 2 to row line 4, spanning two rows. Combining grid-column and grid-row gives you complete control over an item's position in the grid, similar to placing a piece on a chessboard.
The span keyword provides an alternative syntax for specifying how many tracks an item should cover. Instead of calculating the end line number, you specify the number of tracks to span: grid-column: span 2 spans two columns from the item's auto-placed start position. You can also combine a start line with a span: grid-column: 2 / span 3 starts at line 2 and spans 3 columns (ending at line 5). The span syntax is often more readable than explicit end line numbers.
Grid placement allows items to overlap by assigning them to the same cells. When items overlap, the one that appears later in the DOM renders on top (unless you change the stacking order with z-index). This overlap capability is unique to Grid -- you cannot achieve it with Flexbox. It enables creative designs like overlaid text on images, stacked card effects, and complex dashboard widgets that span multiple areas.
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 150px);
gap: 16px;
}
/* Place items at specific positions */
.header {
grid-column: 1 / 5; /* Span all 4 columns */
grid-row: 1 / 2; /* First row */
}
.sidebar {
grid-column: 1 / 2; /* First column */
grid-row: 2 / 4; /* Rows 2-3 */
}
.main {
grid-column: 2 / 5; /* Columns 2-4 */
grid-row: 2 / 3; /* Row 2 */
}
.footer {
grid-column: 2 / 5;
grid-row: 3 / 4;
}
Grid lines are numbered starting from 1. The header spans from line 1 to line 5 (all 4 columns). The sidebar spans from row line 2 to row line 4 (2 rows). Items are explicitly placed by specifying their start and end lines.
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(100px, auto);
gap: 16px;
}
/* Span 2 columns from auto position */
.wide-card {
grid-column: span 2;
}
/* Span 2 rows from auto position */
.tall-card {
grid-row: span 2;
}
/* Start at column 2, span 3 columns */
.feature {
grid-column: 2 / span 3;
}
/* Full-width item */
.banner {
grid-column: 1 / -1; /* -1 = last line */
}
The span keyword specifies how many tracks an item covers. grid-column: span 2 lets the item auto-place but take up two columns. Negative line numbers count from the end: -1 is the last line, so 1 / -1 spans the full width.
.image-card {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 300px;
}
.card-image {
grid-column: 1;
grid-row: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.card-overlay {
grid-column: 1;
grid-row: 1;
align-self: end;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
color: white;
padding: 24px;
z-index: 1;
}
Both the image and overlay are placed in the same cell (column 1, row 1). The overlay stacks on top of the image. align-self: end positions the overlay at the bottom. This technique creates image cards with text overlays without using position: absolute.
.dashboard {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(180px, auto);
gap: 20px;
}
.widget-stats {
grid-column: 1 / 3; /* Columns 1-2 */
grid-row: 1;
}
.widget-chart {
grid-column: 3 / 5; /* Columns 3-4 */
grid-row: 1 / 3; /* Rows 1-2 (tall) */
}
.widget-table {
grid-column: 1 / 3;
grid-row: 2;
}
.widget-feed {
grid-column: 1 / -1; /* Full width */
grid-row: 3;
}
A real-world dashboard layout. The chart widget spans two rows (grid-row: 1 / 3) while being two columns wide. The feed widget spans the full width (1 / -1). Each widget is precisely positioned by its grid lines.
grid-column, grid-row, Spanning, Line-Based Placement