BlueprintShell 0.1.4
See the version list below for details.
dotnet add package BlueprintShell --version 0.1.4
NuGet\Install-Package BlueprintShell -Version 0.1.4
<PackageReference Include="BlueprintShell" Version="0.1.4" />
<PackageVersion Include="BlueprintShell" Version="0.1.4" />
<PackageReference Include="BlueprintShell" />
paket add BlueprintShell --version 0.1.4
#r "nuget: BlueprintShell, 0.1.4"
#:package BlueprintShell@0.1.4
#addin nuget:?package=BlueprintShell&version=0.1.4
#tool nuget:?package=BlueprintShell&version=0.1.4
BlueprintShell

BlueprintShell is an embeddable editor shell built on BlazorBlueprint. Add it to any .NET application and get a fully themed, dockable editor UI - panels, toolbars, dark-mode, hot-reload - served on a configurable port from your own process.
Packages are published to GitHub Packages.
Installation
Add the GitHub Packages feed to your NuGet.config (or ~/.nuget/NuGet/NuGet.Config):
<packageSources>
<add key="EggyStudio" value="https://nuget.pkg.github.com/EggyStudio/index.json" />
</packageSources>
Then add the package:
dotnet add package BlueprintShell
Usage
Standalone mode - dedicated port
Spin up a separate Kestrel server inside your existing process. The shell runs independently and doesn't interfere with your app's own HTTP stack.
using BlueprintShell;
// Start the shell server (non-blocking).
var shell = await EditorServerHost.StartAsync(new BlueprintShellOptions
{
Url = "http://localhost:5100",
AppTitle = "My Engine Editor",
});
// ... run your loop ...
// Shut down cleanly when done.
await shell.StopAsync();
Open http://localhost:5100 in a browser to see the shell.
Embedded mode - same ASP.NET Core app
If your host is already an ASP.NET Core app, register the shell services and map its hub into your existing pipeline:
// Program.cs
builder.Services.AddBlueprintShell(o =>
{
o.AppTitle = "My Editor";
});
// After builder.Build():
app.MapBlueprintShell(); // maps /shell-hub SignalR endpoint
Then add the shell's App, Routes, and ShellLayout into your Razor routing as needed.
Configuration - BlueprintShellOptions
| Property | Type | Default | Description |
|---|---|---|---|
Url |
string |
"http://localhost:5000" |
Kestrel listen URL. Accepts any format supported by UseUrls, e.g. "http://*:5100" or "https://localhost:5001;http://localhost:5000". Only used in standalone mode. |
AppTitle |
string |
"Blueprint Shell" |
Title shown in the shell header and browser tab. |
LaunchBrowser |
bool |
false |
Open the system browser pointing at Url once the server is ready. |
ChromeMode |
ShellChromeMode |
Full |
Default chrome level: Full (dock + header), Minimal (header only), Hidden (nothing - just shell services around @Body). |
ChromeFor |
Func<HttpContext, ShellChromeMode>? |
null |
Per-request override; evaluated before ChromeMode. |
ChromeForServices |
Func<HttpContext, IServiceProvider, ShellChromeMode>? |
null |
DI-aware variant of ChromeFor; evaluated first when set. |
MobileBreakpointPx |
int |
768 |
Viewport width below which the dock collapses. |
MobileBehavior |
MobileBehavior |
Stacked |
Stacked, Drawer, or Hidden below the mobile breakpoint. |
StaticAssetsBasePath |
string? |
null |
Override the /_content/BlueprintShell prefix used by the shell's CSS imports. |
ScanAssemblies |
IList<Assembly> |
all loaded | Explicit assemblies to scan for [EditorShell], [EditorPanel], [ReaderPage]. Empty (default) scans the whole AppDomain. |
EnableDiagnostics |
bool |
false |
Expose GET /_shell/diagnostics (recommended only in Development). |
SignalRHubPath |
string |
"/shell-hub" |
Customise to avoid collisions with other SignalR endpoints. |
ActiveTheme |
string? |
null |
Name of the initial theme preset (see Theme presets). |
Per-request chrome (anonymous reader vs. logged-in editor)
builder.Services.AddBlueprintShell(o =>
{
o.AppTitle = "Encyclopedia";
o.ChromeFor = ctx => ctx.Request.Path.StartsWithSegments("/edit")
? ShellChromeMode.Full
: ShellChromeMode.Hidden;
});
Writing panels
Decorate any Blazor component with [EditorPanel] to register it as a dockable panel. The attribute is discovered at startup by StaticShellLoader via reflection.
@* MyPanel.razor *@
@attribute [EditorPanel("my-panel", "My Panel", DockZone.Right,
Icon = "settings",
Route = "/my-panel",
Visible = true)]
@inject ShellRegistry Registry
<div class="p-4">
<h3 class="text-sm font-semibold">Hello from My Panel</h3>
</div>
| Parameter | Description |
|---|---|
id |
Unique panel key (used for tab-grouping and persistence). |
title |
Display name in the panel header / tab. |
zone |
Default dock position: Top, Left, Right, Bottom, Center, Float. |
Icon |
Optional Lucide icon name shown in the header. |
Route |
When set, a nav-link to this panel appears in the shell header and the catch-all router serves the panel at the given URL. Route parameters (e.g. "/edit/article/{Identifier}") are forwarded to matching [Parameter] properties — same convention as @page. |
TabGroup |
Groups this panel as a tab inside another panel. |
TabOrder |
Sort order within a tab group. |
InitialSize |
Fraction (0–1) of the parent dock area. Default 0.25. |
Closeable |
Whether the user can close the panel. Default true. |
Visible |
Whether the panel starts visible. Default true. |
RequiresRole |
Filters the panel out for requests where IShellAuthContext.Roles doesn't contain the value ("*" means any authenticated user). |
Writing shell builders
For code-driven layout (no Razor), implement IEditorShellBuilder and decorate with [EditorShell]:
using BlueprintShell.Shell;
[EditorShell]
public class MyShell : IEditorShellBuilder
{
public int Order => 0; // lower runs first
public void Build(IShellBuilder shell)
{
shell.Panel("toolbar", "Toolbar", DockZone.Top, p => p
.Content(c => c
.Row(null, row => row
.Button(null, "Save", onClick: OnSave, icon: "save")
.Button(null, "Build", onClick: OnBuild, icon: "hammer", variant: "secondary")
.Spacer()
.DarkModeToggle())));
}
private static void OnSave() { /* ... */ }
private static void OnBuild() { /* ... */ }
}
Multiple builders coexist: Order controls execution order; higher-precedence sources (dynamic hot-reload) override lower ones on panel-id collisions.
Multiple shell sources
ShellRegistry supports multiple named sources that are merged at runtime. This lets an engine contribute a static source at startup and a hot-reload compiler contribute a dynamic source later:
var registry = new ShellRegistry();
// Static source - built-in panels:
registry.RegisterSource("static", new ShellSource
{
Builders = new[] { new MyShell() },
Precedence = 0,
});
// Dynamic source - runtime-compiled panels override static ones on id collision:
registry.RegisterSource("dynamic", new ShellSource
{
PanelComponents = discoveredPanels,
Precedence = 100,
});
var shell = await EditorServerHost.StartAsync(registry: registry);
Reader pages
Reader pages are routed components that bypass the dock layout - useful for full-bleed content (articles, marketing pages) hosted alongside an editor.
@* Article.razor *@
@attribute [ReaderPage("article", "/wiki/{Identifier}",
Chrome = ShellChromeMode.Hidden)]
@code {
[Parameter] public string? Identifier { get; set; }
}
The shell resolves the route via a catch-all router and forwards segments as
[Parameter] properties on the component (same convention as @page).
Incremental adoption (UseBlazorRouter = true)
If you already have a catalog of @page-routed components and don't want to
migrate them wholesale, set UseBlazorRouter = true. The shell records the
entry in ShellRegistry (so it shows up in diagnostics and participates in
role tracking) but the catch-all router skips it — your existing Blazor
Router continues to handle the URL. RequiresRole becomes advisory in this
mode: the shell can't gate a request it doesn't render, so enforce the role
check inside the component or via your own routing middleware.
@page "/wiki/{Identifier}"
@attribute [ReaderPage("article", "/wiki/{Identifier}", UseBlazorRouter = true)]
Auth-aware chrome
Implement IShellAuthContext and the shell will filter [EditorPanel(..., RequiresRole = "...")]
panels and [ReaderPage(..., RequiresRole = "...")] pages automatically. Use "*" to require
any authenticated user.
public sealed class MyAuthContext : IShellAuthContext
{
public bool IsAuthenticated => /* ... */;
public string? UserId => /* ... */;
public IReadOnlySet<string> Roles => /* ... */;
}
builder.Services.AddScoped<IShellAuthContext, MyAuthContext>();
Header slots
Drop your own content into the shell header (e.g. a global search box):
registry.RegisterHeaderSlot(builder =>
{
builder.OpenComponent<GlobalSearchBox>(0);
builder.CloseComponent();
});
Theme presets
registry.RegisterTheme("reader", new ThemePreset
{
Variables =
{
["--background"] = "oklch(1 0 0)",
["--foreground"] = "oklch(0.15 0 0)",
["--font-sans"] = "\"Source Serif Pro\", Georgia, serif",
},
});
registry.SetActiveTheme("reader");
The active preset is rendered as <style id="bp-active-theme"> and replaced when
SetActiveTheme is called.
PWA
app.MapBlueprintShellPwa(new BlueprintShellPwaOptions
{
ShortName = "Encyclopedia",
ThemeColor = "#0a0a0a",
BackgroundColor = "#ffffff",
CacheStaticAssets = true,
});
Serves a manifest at /manifest.webmanifest and a service worker at /sw.js,
precaching the shell's own CSS and any URLs you add via ExtraCacheUrls.
The generated service worker skips /_blazor, /_framework, and the
configured SignalR hub path by default (caching them breaks Blazor Server
reconnects after deploy). Add more prefixes via
BlueprintShellPwaOptions.ExcludePathPrefixes.
To register the service worker on the client, either drop the helper
component into your layout / App.razor:
<BlueprintShellPwaBootstrap />
…or set RenderRegistrationScript = false and emit the
navigator.serviceWorker.register('/sw.js') call yourself.
DI-aware chrome resolution
ChromeFor runs without access to scoped services. When you need to resolve
IShellAuthContext (or anything else from DI) to pick chrome, use
ChromeForServices instead — it receives the scoped IServiceProvider and is
evaluated before ChromeFor.
o.ChromeForServices = (ctx, sp) =>
{
var auth = sp.GetRequiredService<IShellAuthContext>();
return auth.IsAuthenticated ? ShellChromeMode.Full : ShellChromeMode.Hidden;
};
Dialogs / popovers (BbPortalHost)
BlazorBlueprint renders dialogs, popovers, tooltips, and toasts into a portal
host — your layout must include <BbPortalHost /> (from
BlazorBlueprint.Primitives.Services, not BlazorBlueprint.Primitives)
for those primitives to appear. All three of the shell's built-in layouts
(ShellLayout, MinimalLayout, ReaderLayout) already include it; consumers
writing their own layout need to add it themselves.
Diagnostics
Set o.EnableDiagnostics = true and GET /_shell/diagnostics returns a JSON dump
of panels, reader pages, themes, and loaded assemblies - useful when something
doesn't render and you want to know whether it was discovered.
CSS / theming
The shell uses BlazorBlueprint's OKLCH-based CSS variable system. Override variables in your own stylesheet or supply raw CSS snippets via the builder API:
// Custom CSS injected into every page:
registry.RegisterSource("my-theme", new ShellSource
{
CustomCss = new[]
{
":root { --primary: oklch(0.55 0.2 260); }",
},
});
License
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- BlazorBlueprint.Components (>= 3.10.0)
- BlazorBlueprint.Icons.Feather (>= 2.0.0)
- BlazorBlueprint.Icons.Heroicons (>= 2.0.0)
- BlazorBlueprint.Icons.Lucide (>= 2.0.0)
- BlazorBlueprint.Primitives (>= 3.10.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.