Overview
The Postnomic Client SDKs ship with clean, responsive default styling that works out of the box. You can customize the appearance to match your application's brand by choosing a markup style, overriding theme tokens, choosing a blog layout, and replacing default views or components.
Markup Styles
PostnomicClientOptions.MarkupStyle decides which CSS classes the components emit. This is the single most important setting for theming:
| Value | What the components emit |
|---|---|
PostnomicMarkupStyle.Bootstrap (default) |
Bootstrap 5 utility classes — card, col-lg-8, badge bg-secondary, and so on |
PostnomicMarkupStyle.Semantic |
A small, stable set of pn-* classes with no framework dependency |
builder.Services.AddPostnomicBlog(options =>
{
options.BaseUrl = "https://api.postnomic.com";
options.ApiKey = "pk_your_blog_key_here";
options.BlogSlug = "my-dev-blog";
options.MarkupStyle = PostnomicMarkupStyle.Semantic;
});
Use Bootstrap when your application already uses Bootstrap and you want the blog to inherit your theme's typography, colors, and spacing for free. Use Semantic when you want to theme the blog yourself without fighting Bootstrap's utility classes.
Theming the Semantic Style
In Semantic mode the SDK ships a stylesheet that covers every pn-* class. Link it from your layout:
<!-- ASP.NET Core -->
<link rel="stylesheet" href="_content/Postnomic.Client.AspNetCore/postnomic-blog.css" />
<!-- Blazor -->
<link rel="stylesheet" href="_content/Postnomic.Client.Blazor/postnomic-blog.css" />
That stylesheet is built entirely from --pn-* custom properties declared on .pn-blog. You theme by overriding the properties, not the classes — no class overrides are required, and none are needed to keep working across SDK versions:
.pn-blog {
--pn-font: "Inter", system-ui, sans-serif;
--pn-font-heading: "Georgia", serif;
--pn-max-width: 900px;
--pn-surface: #ffffff;
--pn-surface-variant: #f6f7f9;
--pn-text: #1a1a2e;
--pn-text-muted: #6b7280;
--pn-primary: #2a5bd7;
--pn-on-primary: #ffffff;
--pn-border: #e5e7eb;
--pn-link: var(--pn-primary);
--pn-radius: 12px;
--pn-radius-lg: 18px;
}
The --pn-space-xs … --pn-space-xl properties control the spacing scale and can be overridden the same way. Because the properties cascade, setting them on any ancestor of .pn-blog works too — which makes it easy to drive the blog from your application's existing design tokens.
The pn-* Classes
If you do want to target elements directly, these are the classes the Semantic style emits:
| Area | Classes |
|---|---|
| Root and chrome | pn-blog, pn-header, pn-title, pn-lead, pn-layout, pn-main, pn-sidebar |
| Post cards | pn-card, pn-card__media, pn-card__body, pn-post-title, pn-post-meta, pn-excerpt, pn-tag |
| Post detail | pn-post-content |
| Sidebar widgets | pn-widget, pn-widget__title, pn-searchbox, pn-filter-banner |
| Comments | pn-comment, pn-comment-form, pn-field |
| Pagination | pn-pagination, pn-page, pn-page--active, pn-page--disabled |
| States | pn-loading, pn-empty |
| Layout variant | pn-masonry |
A handful of postnomic-* classes are emitted in both markup styles, because they carry behaviour rather than theme: postnomic-masonry (Bootstrap mode's masonry container), postnomic-post-content, postnomic-cover-image, postnomic-post-thumbnail, postnomic-comment, postnomic-comment-replies, postnomic-branding, and postnomic-sidebar-ad. These are the only postnomic-* hooks that exist.
Blog Layout Options
Postnomic supports two built-in blog layouts, configurable per blog through the dashboard under Settings → Blog Settings:
- Default — A traditional single-column blog listing with posts displayed vertically. Ideal for text-heavy content.
- Masonry — A multi-column grid layout where posts flow into available space, similar to Pinterest. Uses CSS
column-countwithbreak-inside: avoidfor a compact, visual presentation.
The layout setting is stored on the blog entity and returned via the API as defaultLayout. The Client SDKs automatically apply the corresponding CSS layout when rendering the blog index.
Masonry Layout Customization
To adjust the masonry layout's column count for different screen sizes, override the container. In Bootstrap mode the container is .postnomic-masonry; in Semantic mode it is .pn-masonry:
.postnomic-masonry,
.pn-masonry {
column-count: 1;
}
@media (min-width: 768px) {
.postnomic-masonry,
.pn-masonry {
column-count: 2;
}
}
@media (min-width: 1200px) {
.postnomic-masonry,
.pn-masonry {
column-count: 3;
}
}
Overriding Views (ASP.NET Core)
For the ASP.NET Core integration, you can override individual Razor Pages by creating pages at the matching Area path in your own project. The ASP.NET Core routing system will prioritize your pages over the package defaults:
/Areas/Blog/Pages/Index.cshtml → Override blog listing
/Areas/Blog/Pages/Post.cshtml → Override post detail
/Areas/Blog/Pages/Author.cshtml → Override author profile
Inject IPostnomicBlogService into your custom pages to fetch data:
public class IndexModel : PageModel
{
private readonly IPostnomicBlogService _blogService;
public IndexModel(IPostnomicBlogService blogService)
{
_blogService = blogService;
}
public async Task OnGetAsync()
{
var posts = await _blogService.GetPostsAsync();
// Custom rendering logic
}
}
Customizing Components (Blazor)
The Blazor package ships no routable pages — you already author your own pages that wrap BlogPage, PostPage, and AuthorPage (see Blazor Integration), so "overriding" the defaults is simply a matter of changing what those pages render. To replace a section outright, drop the SDK component and call IPostnomicBlogService yourself:
@inject IPostnomicBlogService BlogService
@code {
private PostnomicPagedResult<PostnomicPostSummary>? _posts;
protected override async Task OnInitializedAsync()
=> _posts = await BlogService.GetPostsAsync();
}
Customizing Text
PostnomicClientOptions.UiStrings lets you override the built-in UI labels (button text, empty-state copy, and so on) without touching markup, which is often all that is needed to make the blog feel native to your application.