# Mobile-First Layouts with Bootstrap

Build pages that work on a phone first, then widen. Bootstrap's row-cols classes, cards, a collapsing navbar and pagination turn a Razor loop into a responsive product grid.

Journeyman · ASP.NET Core · CSS · Updated November 3, 2026

## After this lesson you can

- Lay out a list of items as a responsive grid with Bootstrap's row-cols classes, starting from the phone
- Build a pager for a long list with Bootstrap's pagination and the page in the query string

## Key takeaways

- Mobile-first means designing for the phone first: Bootstrap classes without a size apply to every screen, and `sm`, `md` and `lg` versions take over from that width up.
- `row-cols-1 row-cols-sm-2 row-cols-lg-3` shows one card per row on phones, two from small screens up and three from large ones, with one `col` per item.
- A pager's links carry the page in the query string, such as `/products?page=2` from `asp-route-page`, and the current page's item gets `active`.
- `OFFSET` and `FETCH NEXT` fetch one page of rows, and need an `ORDER BY` so every page sees the rows in the same order.
- Check every page in the browser's phone view (F12, then the device button): the cards stack, the menu folds, and nothing scrolls sideways.

More visitors use a phone than a computer. **Mobile-first** means designing for the narrow screen first, then letting the page spread out as the screen grows. Bootstrap, from *Bootstrap 5*, is built this way: its classes without a size apply to every screen, and `sm`, `md`, `lg` versions take over from that width up.

## A responsive grid of cards

The product list puts its cards in a grid that changes with the screen:

```cshtml
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 g-3">
    @foreach (Product product in Model)
    {
        <div class="col">
            <partial name="_ProductCard" model="product" />
        </div>
    }
</div>
```

| Class | Means |
|---|---|
| `row-cols-1` | One card per row: phones, the starting point |
| `row-cols-sm-2` | Two per row from small screens (576px) up |
| `row-cols-lg-3` | Three per row from large screens (992px) up |
| `g-3` | A gap between the cards |
| `col` | Each card's column, sized by the row's `row-cols` |

The Razor loop doesn't change at all: one `col` per product, and Bootstrap decides how many fit across. In each card, `h-100` makes every card in a row as tall as the tallest.

## The navbar folds away

The template's layout already uses a responsive navbar: on a wide screen the links sit in a row; on a phone they fold into a menu button.

```cshtml
<nav class="navbar navbar-expand-sm navbar-light bg-white border-bottom box-shadow mb-3">
    <div class="container-fluid">
        <a class="navbar-brand" asp-controller="Home" asp-action="Index">NorthwindWeb</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse"
                aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
            <ul class="navbar-nav flex-grow-1">
                <li class="nav-item">
                    <a class="nav-link text-dark" asp-controller="Products" asp-action="Index">Products</a>
                </li>
            </ul>
            <div class="navbar-nav">
                <partial name="_LoginStatus" />
            </div>
        </div>
    </div>
</nav>
```

`navbar-expand-sm` says when to stop folding: from small screens up, the links show in a row.

## A pager for a long list

Seventy-seven products are too many for one phone screen. Show twelve at a time, with Bootstrap's **pagination** below, and the page number in the query string (from *Route Values and the Query String*):

```cshtml
@{
    int page = (int)ViewData["Page"]!;
    int pageCount = (int)ViewData["PageCount"]!;
}

<nav aria-label="Product pages" class="mt-4">
    <ul class="pagination">
        @for (int i = 1; i <= pageCount; i++)
        {
            <li class="page-item @(i == page ? "active" : "")">
                <a class="page-link" asp-action="Index" asp-route-page="@i">@i</a>
            </li>
        }
    </ul>
</nav>
```

The links come out as `/products?page=1` to `/products?page=7`, and the current page's item gets the `active` class. On the server, the DAO fetches just one page with `OFFSET` and `FETCH`:

```sql
SELECT ProductId, ProductName, CategoryId, UnitPrice, UnitsInStock
FROM Products
ORDER BY ProductName
OFFSET @Skip ROWS FETCH NEXT @PageSize ROWS ONLY;
```

`OFFSET` skips the earlier pages' rows, and `FETCH NEXT` takes one page. It needs an `ORDER BY`, so every page sees the rows in the same order.

## Check it on a phone

Every browser can pretend to be a phone: press **F12** for the developer tools, then the **device** button (or **Ctrl+Shift+M**), and pick a phone. Check that the cards stack, the menu folds, and nothing makes the page scroll sideways.

## Check yourself

2 questions to try on the lesson page.

## So what

Start narrow, then add columns as the screen grows: a `row-cols` grid of partial-view cards, a navbar that folds, and a pager, and the same Razor works on every screen.

## Key terms

- [Mobile-first design](https://www.ask-codey.com/glossary/mobile-first-design)
- [Pagination](https://www.ask-codey.com/glossary/pagination)
- [OFFSET Clause](https://www.ask-codey.com/glossary/offset-clause)

## Cheat sheets

- [Mobile-First Layouts with Bootstrap](https://www.ask-codey.com/cheat-sheets/partial-views-in-mvc-mobile-first-layouts-with-bootstrap-cheat-sheet)

Source: https://www.ask-codey.com/lessons/partial-views-in-mvc-mobile-first-layouts-with-bootstrap
