DEV Community

Manohari Jayachandran
Manohari Jayachandran

Posted on

Bootstrap for Blazor: Styling the Task Tracker

This is the final post in the series. The Task Tracker has been built, refactored, rendered, and made interactive, genuinely working since Part 4, but visually plain, using nothing but default, unstyled HTML elements. This post applies Bootstrap directly on top of that same working Blazor app, with zero changes to the underlying logic, purely visual, closing out the series with something that actually looks presentable.

What Bootstrap Actually Is

Bootstrap is a CSS framework, a large, pre-written collection of CSS classes covering layout, spacing, colors, and common UI components, buttons, cards, navigation bars, so a page can look genuinely designed without writing custom CSS from scratch.

Think of pre-fabricated furniture versus building everything from raw lumber. Bootstrap's classes are like a well-designed furniture catalog, a specific bookshelf, a specific desk, already proportioned and finished correctly. You still arrange the room yourself, the actual layout of your page, but you're not hand-cutting and sanding every piece of wood from scratch.

Getting Bootstrap Into the Project

<!-- In the main HTML host page (index.html for Blazor
     WebAssembly, or _Layout.cshtml/App.razor's host
     page for Blazor Server) -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />

<!-- That single line makes every Bootstrap class used
     throughout this post immediately available across
     the entire Blazor app -->
Enter fullscreen mode Exit fullscreen mode

The Grid System: Rows and Columns, No Custom CSS

Bootstrap's layout is based on a 12-column grid. A row contains one or more columns, and the column widths, col-md-8, col-md-4, should add up to 12 for a clean, full-width layout.

<div class="row">
    <div class="col-md-8">
        <!-- Main content - takes 8 of 12 columns -->
    </div>
    <div class="col-md-4">
        <!-- Sidebar - takes the remaining 4 columns -->
    </div>
</div>

<!-- No manual percentage math, no custom CSS -
     just column counts that add up to 12 -->
Enter fullscreen mode Exit fullscreen mode

Cards: Wrapping Each Task Properly

A card is a bordered, padded container with a consistent visual style, genuinely useful for turning a bare list into something that actually reads as a real UI element, not raw text.

<div class="card mb-2">
    <div class="card-body d-flex justify-content-between align-items-center">
        <span>Write documentation</span>
        <button class="btn btn-sm btn-outline-danger">Delete</button>
    </div>
</div>

<!-- mb-2 adds margin-bottom (spacing between cards)
     d-flex, justify-content-between, and
     align-items-center are Bootstrap's UTILITY classes -
     small, single-purpose classes for common layout
     needs, used here to put the task text and the
     Delete button on opposite ends of the same row -->
Enter fullscreen mode Exit fullscreen mode

Button Variants: Replacing Plain, Unstyled Buttons

<button class="btn btn-primary">Add Task</button>
<button class="btn btn-outline-danger">Delete</button>
<button class="btn btn-outline-secondary">All</button>

<!-- btn-primary - a solid, prominent color for the
     main action (adding a task)
     btn-outline-danger - a red outline, appropriate
     for a destructive action (deleting), without being
     as visually loud as a solid red button everywhere
     btn-outline-secondary - a neutral, low-emphasis
     style, appropriate for the filter buttons, which
     aren't the primary focus of the page -->
Enter fullscreen mode Exit fullscreen mode

A Navbar: Giving the App an Actual Header

<nav class="navbar navbar-dark bg-dark mb-4">
    <div class="container">
        <span class="navbar-brand mb-0 h1">Task Tracker</span>
    </div>
</nav>

<!-- navbar-dark + bg-dark gives a dark background with
     light text automatically color-adjusted for
     contrast - no manual color picking required -->
Enter fullscreen mode Exit fullscreen mode

The Complete Styled Task Tracker

@page "/tasks"

<nav class="navbar navbar-dark bg-dark mb-4">
    <div class="container">
        <span class="navbar-brand mb-0 h1">Task Tracker</span>
    </div>
</nav>

<div class="container">

    <div class="row mb-3">
        <div class="col-md-9">
            <input class="form-control" @bind="newTaskDescription" placeholder="New task..." />
        </div>
        <div class="col-md-3">
            <button class="btn btn-primary w-100" @onclick="AddTask">Add Task</button>
        </div>
    </div>

    <div class="mb-3">
        @foreach (var task in FilteredTasks)
        {
            <div class="card mb-2">
                <div class="card-body d-flex justify-content-between align-items-center">
                    <div class="form-check">
                        <input class="form-check-input" type="checkbox" @bind="task.IsCompleted" />
                        @if (task.IsCompleted)
                        {
                            <s class="ms-2">@task.Description</s>
                        }
                        else
                        {
                            <span class="ms-2">@task.Description</span>
                        }
                    </div>
                    <button class="btn btn-sm btn-outline-danger" @onclick="() => DeleteTask(task)">Delete</button>
                </div>
            </div>
        }
    </div>

    <div class="btn-group">
        <button class="btn btn-outline-secondary" @onclick='() => currentFilter = "All"'>All</button>
        <button class="btn btn-outline-secondary" @onclick='() => currentFilter = "Active"'>Active</button>
        <button class="btn btn-outline-secondary" @onclick='() => currentFilter = "Completed"'>Completed</button>
    </div>

</div>

@code {
    // IDENTICAL to Part 4 - zero logic changes.
    // Every single change in this post is a class
    // attribute added to existing markup
    private List<TaskItem> tasks = new();
    private string newTaskDescription = string.Empty;
    private string currentFilter = "All";

    private IEnumerable<TaskItem> FilteredTasks => currentFilter switch
    {
        "Active" => tasks.Where(t => !t.IsCompleted),
        "Completed" => tasks.Where(t => t.IsCompleted),
        _ => tasks
    };

    private void AddTask()
    {
        if (!string.IsNullOrWhiteSpace(newTaskDescription))
        {
            tasks.Add(new TaskItem { Description = newTaskDescription, IsCompleted = false });
            newTaskDescription = string.Empty;
        }
    }

    private void DeleteTask(TaskItem task) => tasks.Remove(task);
}
Enter fullscreen mode Exit fullscreen mode

Worth noticing directly: the @code block is completely unchanged from Part 4. Every single addition in this post, navbar, card, btn-primary, row and col-md classes, is a CSS class attached to existing markup. This is the practical meaning of separating structure and logic, Blazor's job, from presentation, Bootstrap's job: styling an app doesn't require touching the code that makes it actually work.

The Series, Complete

This closes the WPF, MVVM, Razor, Blazor, Bootstrap series. Part 1 built a working Task Tracker in plain WPF, honestly naming the pain points left in on purpose. Part 2 refactored that exact app into proper MVVM, fixing every one of those pain points directly, tangled logic separated, the missed-refresh bug eliminated structurally, the filtering logic made genuinely unit-testable. Part 3 pivoted to the web, rendering the same task data with Razor, deliberately read-only. Part 4 made that page interactive with Blazor, using C# instead of JavaScript, arriving at genuine feature parity with the WPF and MVVM version, add, check, delete, filter, through a completely different technology stack. Part 5, this post, finished the job visually, applying Bootstrap on top of already-working Blazor logic with zero changes to that logic at all.

One example, five posts, two genuinely different technology stacks, WPF desktop and Blazor web, built to the same functional specification, which is itself a useful thing to be able to say plainly in an interview: the same problem, solved twice, in two different paradigms, understanding what changed and what stayed conceptually the same across both.

Key Lessons

Bootstrap is a pre-written CSS framework, classes for layout, components, and utilities, letting a page look genuinely designed without writing custom CSS from scratch.

The 12-column grid system, row and col-md classes, handles layout proportions without manual percentage math.

Cards, button variants, and the navbar are pre-styled components, dropping a class onto existing markup, not building visual style from raw CSS.

Applying Bootstrap required zero changes to the Blazor @code block, proof that structure and logic and visual presentation are genuinely separable concerns.

Across this five-part series, the same Task Tracker was built twice, in WPF and MVVM, and in Razor, Blazor, and Bootstrap, the same functional requirements, two different technology stacks, each with its own idioms for solving the same underlying problems, keeping UI in sync with data, separating concerns, styling presentation.

Summary

Bootstrap turns a functionally complete but visually plain Blazor app into something genuinely presentable, using a pre-written grid system, styled components, and utility classes, all applied without touching a single line of the underlying Blazor logic. That separation is the point: the same Task Tracker that worked correctly in Part 4 works identically here, just dressed properly. Across all five posts in this series, one example carried through two genuinely different technology stacks, a plain WPF window refactored into proper MVVM, and a Razor page made interactive with Blazor and then styled with Bootstrap, arriving at the same functional result from two different directions, which is exactly the kind of comparative understanding worth having ready for an interview question about either one.


More from TechStack Blog: C# / .NET: https://www.techstackblog.com/category.html?cat=csharp
CS Fundamentals: https://www.techstackblog.com/category.html?cat=cs-fundamentals

Top comments (1)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.