---
title: Style the site with MonorailCSS
description: "Layer MonorailCSS onto the Blazor-pages site through a routed `MainLayout.razor` and watch the stylesheet regenerate as new utility classes appear in the source."
canonical_url: https://usepennington.net/tutorials/getting-started/styling/
sidecar_url: https://usepennington.net/tutorials/getting-started/styling.md
content_hash: sha256:bac7dd720c54e15300683b96a7c47b17c2048e66468167a9d68a21c5e06c8e78
tokens: 2666
uid: tutorials.getting-started.styling
reading_time_minutes: 5
---

Getting Started
# Style the site with MonorailCSS

Layer MonorailCSS onto the Blazor-pages site through a routed `MainLayout.razor` and watch the stylesheet regenerate as new utility classes appear in the source.

 
By the end of this tutorial the Blazor-pages site from [Serve markdown through a Blazor catch-all](https://usepennington.net/tutorials/getting-started/first-page.md) is styled with [MonorailCSS](https://monorailcss.github.io/MonorailCss.Framework/) — a Tailwind-compatible JIT compiler in pure .NET. Every routed `@page` renders through a `MainLayout.razor` that carries the utility classes. The [MonorailCSS Discovery pipeline](https://usepennington.net/explanation/rendering/monorail-css.md) turns those classes into real CSS rules, served at `/styles.css`. The stylesheet regenerates whenever a new class appears in the source.

 
## Prerequisites

 
 - .NET 10 SDK installed
 - Completed [Serve markdown through a Blazor catch-all](https://usepennington.net/tutorials/getting-started/first-page.md) (or a Pennington project with `MapRazorComponents<App>()` wired and a catch-all `MarkdownPage.razor`)
 
 
The finished code for this tutorial lives in [examples/GettingStartedStylingExample](https://github.com/usepennington/pennington/tree/main/examples/GettingStartedStylingExample). For a documentation site, the DocSite template ships this MonorailCSS-plus-`MainLayout` stack with a sidebar, search, and theme toggle already assembled — [Scaffold a documentation site with DocSite](https://usepennington.net/tutorials/docsite/scaffold.md) covers it.

 
---

 
## 1. Wrap pages in a styled `MainLayout.razor`

 
Before MonorailCSS can do anything, the layout needs to carry the utility classes that will turn into CSS rules. The document shell moves out of `App.razor` and into a `MainLayout.razor` that holds those classes; `App.razor` shrinks to a bare router that wraps every routed page in the new layout.

 
**Create `Components/Layout/MainLayout.razor`**

 
Drop this file at `Components/Layout/MainLayout.razor`. Inheriting `LayoutComponentBase` makes it a Blazor layout — every routed page renders into the `@Body` placeholder. This component now owns the whole document shell — `<!DOCTYPE>`, `<html>`, `<head>` (with `<HeadOutlet>`), and `<body>` — moved here from `App.razor`. The `<link rel="stylesheet" href="/styles.css">` tag points at an endpoint section 2 will mount.

 
```razor:symbol
@* Styled shell. Lives once and wraps every routed @page via App.razor's
   DefaultLayout. The class strings (text-primary-700, bg-base-50, …) become
   literal IL strings after Razor compiles, and Discovery's startup IL scan
   picks them up to populate the class registry behind /styles.css. *@
  
@inherits LayoutComponentBase
  
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="stylesheet" href="/styles.css" />
    <HeadOutlet />
</head>
<body class="bg-base-50 text-base-900 min-h-screen">
    <div class="max-w-3xl mx-auto px-6 py-10">
        <header class="mb-8 border-b border-base-200 pb-4">
            <a class="text-lg font-bold text-primary-700" href="/">My Styled Pennington Site</a>
        </header>
        <article class="prose">
            @Body
        </article>
        <footer class="mt-12 pt-4 border-t border-base-200 text-xs text-base-500">
            Styled with MonorailCSS.
        </footer>
    </div>
</body>
</html>
```

 
The classes — `bg-base-50`, `text-primary-700`, `border-base-200`, and so on — come from the named color palette configured in the next section.

 
**Reference the layout namespace from `_Imports.razor`**

 
`App.razor` refers to `MainLayout` by its bare name, so the project's `_Imports.razor` needs an `@using` for the layout's namespace — without it `typeof(MainLayout)` fails to compile. Add the `Components.Layout` line to the `_Imports.razor` at the project root:

 
```razor:symbol
@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Pennington.Content
@using Pennington.Pipeline
@using Pennington.Routing
@using GettingStartedStylingExample.Components
@using GettingStartedStylingExample.Components.Layout
```

 
> [!NOTE]
> The embeds use the finished example's root namespace, `GettingStartedStylingExample` (in `_Imports.razor` and the section 2 `Program.cs` snippet). Swap in your own.

 
**Replace `Components/App.razor`**

 
With the shell now in `MainLayout.razor`, `App.razor` is replaced wholesale: it drops the `<!DOCTYPE>`, `<html>`, `<head>`, and `<HeadOutlet>` it used to own and becomes just the `<Router>`. `RouteView` names `MainLayout` as the `DefaultLayout` for every matched page, and the `LayoutView` for the not-found case lets the same shell wrap the 404 message.

 
```razor:symbol
@* Root component. The Router scans this assembly for [@page] components and
   wraps each match in MainLayout (the styled shell). <PageTitle> from each
   routed page flows into <head> via <HeadOutlet>. *@
  
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
    </Found>
    <NotFound>
        <LayoutView Layout="@typeof(MainLayout)">
            <p>Not found.</p>
        </LayoutView>
    </NotFound>
</Router>
```

 
## 2. Register MonorailCSS and mount `/styles.css`

 
MonorailCSS ships in its own package, separate from the core `Pennington` package the previous tutorial added. Pull it in:

 
```bash
dotnet add package Pennington.MonorailCss
```

 
With the package referenced, wire MonorailCSS into the service container, pick a color scheme, and mount the JIT stylesheet endpoint. `AddMonorailCss` registers the services; each of `PrimaryColorName`, `AccentColorName`, and `BaseColorName` takes a `ColorName` constant (indigo/pink/slate here — any combination works). `app.UseMonorailCss()` mounts `/styles.css` as a real endpoint that regenerates on every request, matching the `<link>` tag in `MainLayout.razor`. Both highlighted blocks are new in this section; the `using Pennington.MonorailCss;` at the top is what makes `AddMonorailCss`, `MonorailCssOptions`, `NamedColorScheme`, and `ColorName` resolve.

 
```csharp:symbol,bodyonly,imports
using GettingStartedStylingExample.Components;
using Pennington.FrontMatter;
using Pennington.Infrastructure;
using Pennington.MonorailCss;
  
var builder = WebApplication.CreateBuilder(args);
  
builder.Services.AddPennington(penn =>
{
    penn.SiteTitle = "My Styled Pennington Site";
    penn.ContentRootPath = "Content";
  
    penn.AddMarkdownContent<DocFrontMatter>(md =>
    {
        md.ContentPath = "Content";
        md.BasePageUrl = "/";
    });
});
  
// New in this stage: register MonorailCSS. Pick which named palettes
// back the `primary`, `accent`, and `base` utility prefixes. Any
// ColorName constant works — swap freely.
builder.Services.AddMonorailCss(_ => new MonorailCssOptions 
{ 
    ColorScheme = new NamedColorScheme 
    { 
        PrimaryColorName = ColorName.Indigo, 
        AccentColorName = ColorName.Pink, 
        BaseColorName = ColorName.Slate, 
    }, 
}); 
  
builder.Services.AddRazorComponents();
  
var app = builder.Build();
  
app.UsePennington();
  
// New in this stage: mount /styles.css. The default path matches the
// <link> tag in MainLayout.razor.
app.UseMonorailCss(); 
  
app.UseAntiforgery();
app.MapRazorComponents<App>();
  
await app.RunOrBuildAsync(args);
```

 
> [!CHECKPOINT]
>  - Run `dotnet run --urls http://localhost:5000` and visit `http://localhost:5000/` — the header, article, and footer now render with indigo accents, slate neutrals, and the layout spacing the utility classes describe
>  - Visit `http://localhost:5000/styles.css` directly and a populated stylesheet appears, containing rules for every utility class the layout emits

---

 
## 3. Watch the stylesheet regenerate

 
Under `dotnet run`, MonorailCSS rescans your project for new utility classes on the next `/styles.css` request, so classes you add in source (Razor components and other compiled C#) appear without a restart. Markdown bodies are out of scope: a utility token added to a `.md` file will not produce a CSS rule. The [MonorailCSS integration explanation](https://usepennington.net/explanation/rendering/monorail-css.md) covers why.

 
**Add a new utility class to `MainLayout.razor`**

 
Open `Components/Layout/MainLayout.razor` and wrap the footer's "MonorailCSS" word in an accented span:

 
```razor
<footer class="mt-12 pt-4 border-t border-base-200 text-xs text-base-500">
    Styled with <span class="text-accent-600 italic">MonorailCSS</span>.
</footer>
```

 
The class `text-accent-600` wasn't in the layout, so it doesn't yet exist in the stylesheet.

 
**Reload and confirm the new rule**

 
Reload any page in the browser. The footer's "MonorailCSS" word renders in pink italic because the `.razor` edit refreshed the class set, and the next `/styles.css` request picked up the new token. Reload `/styles.css` directly and the `text-accent-600` rule is present.

 
> [!CHECKPOINT]
>  - The footer's "MonorailCSS" word renders in pink italic on every page
>  - `http://localhost:5000/styles.css` now contains a rule for `text-accent-600` that wasn't there before the edit
>  - No server restart was required — the MonorailCSS file watcher refreshed the stylesheet under the running `dotnet run`

---

 
## Summary

 
 - `MainLayout.razor` (a Blazor `LayoutComponentBase`) holds the utility-class scaffold every routed `@page` renders into via `App.razor`'s `DefaultLayout`.
 - `AddMonorailCss(...)` registers the service container; `UseMonorailCss()` mounts the `/styles.css` endpoint that regenerates on every request.
 - A `NamedColorScheme` of three `ColorName` constants drives every `primary-*`, `accent-*`, and `base-*` utility prefix.
 - Under `dotnet run`, adding a new utility class to a `.razor` or `.cs` file regenerates the stylesheet on the next request without a restart — markdown edits do not participate.
 
 
The site is styled, but every page is an island with no way to reach the next. The [final getting-started tutorial](https://usepennington.net/tutorials/getting-started/navigation.md) adds a navigation menu that links them.

 
[Previous
                
                Serve markdown through Blazor Pages](https://usepennington.net/tutorials/getting-started/first-page.md)[Next
                    
                Add navigation across your pages](https://usepennington.net/tutorials/getting-started/navigation.md)