BlazorBlueprint.Primitives
4.1.0
dotnet add package BlazorBlueprint.Primitives --version 4.1.0
NuGet\Install-Package BlazorBlueprint.Primitives -Version 4.1.0
<PackageReference Include="BlazorBlueprint.Primitives" Version="4.1.0" />
<PackageVersion Include="BlazorBlueprint.Primitives" Version="4.1.0" />
<PackageReference Include="BlazorBlueprint.Primitives" />
paket add BlazorBlueprint.Primitives --version 4.1.0
#r "nuget: BlazorBlueprint.Primitives, 4.1.0"
#:package BlazorBlueprint.Primitives@4.1.0
#addin nuget:?package=BlazorBlueprint.Primitives&version=4.1.0
#tool nuget:?package=BlazorBlueprint.Primitives&version=4.1.0
BlazorBlueprint.Primitives
Headless, unstyled Blazor primitive components with ARIA attributes and keyboard support. Build your own component library using these composable primitives.
Features
- Headless & Unstyled: Complete control over styling — primitives provide behavior, accessibility, and state management without imposing any visual design
- Built with Accessibility in Mind: Includes ARIA attributes and keyboard interaction support
- Composition-Based: Flexible component composition patterns for building complex UIs
- Type-Safe: Full C# type safety with IntelliSense support
- State Management: Built-in controlled and uncontrolled state patterns
- Keyboard Support: Keyboard interaction support for interactive components
- Two-Layer Portal Architecture: Category-scoped portals (Container and Overlay) for efficient rendering
- .NET 10: Built for the latest .NET platform
Installation
dotnet add package BlazorBlueprint.Primitives
Setup
Register services in Program.cs:
builder.Services.AddBlazorBlueprintPrimitives();
Add the portal host to your root layout (MainLayout.razor):
<BbPortalHost />
Add a single import to _Imports.razor:
@using BlazorBlueprint.Primitives
Reference the primitives stylesheet from your host page (App.razor or index.html):
<link rel="stylesheet" href="_content/BlazorBlueprint.Primitives/css/primitives.css" />
It is small and carries no design decisions — only the rules the primitives' own markup depends on, such as hiding screen-reader-only announcements and sizing the dismiss overlay. Skip it and the Sortable list reads its keyboard instructions out as visible body text. Apps that also use BlazorBlueprint.Components can load it or not; the two stylesheets agree.
Available Primitives
| Primitive | Description |
|---|---|
| Accordion | Collapsible content sections with single or multiple item expansion |
| Alert Dialog | Modal requiring explicit acknowledgement, no dismiss via overlay or Escape |
| Barcode | Fourteen linear symbology encoders, each with its own alphabet, length rule and check digit, producing bar geometry rather than pixels |
| Checkbox | Binary selection control with indeterminate state and BbCheckboxIndicator sub-component |
| Collapsible | Expandable content area with trigger control |
| Context Menu | Right-click menu with keyboard navigation and positioning |
| Dashboard Grid | Widget layout state, drag-and-drop coordination, resize handling, responsive breakpoints |
| DataGrid | Headless data grid with sorting, filtering, pagination, selection, expansion, row grouping, and state management |
| Dialog | Modal dialogs with backdrop, focus management, and portal rendering |
| Direction | Writing direction for everything inside it, cascaded as a context and written as a dir attribute |
| Dropdown Menu | Context menus with items, checkbox items, separators, and keyboard shortcuts |
| Gantt | Task tree, summary roll-up, the timeline's two tiers and dependencies resolved against the rows on screen, with no markup |
| Hover Card | Rich preview cards on hover with delay control |
| Label | Accessible labels for form controls with automatic association |
| Menubar | Application-style menu bar with roving focus, submenus and typeahead |
| Navigation Menu | Site navigation with hoverable panels, pointer intent and keyboard access |
| Pivot | Cross-tabulation: nested headings on both axes, and totals worked out from the items rather than from the cells |
| Popover | Floating panels for additional content with positioning |
| Progress | Accessible progress bar with determinate and indeterminate states |
| QR Code | The whole of ISO/IEC 18004: every version and error-correction level, Reed-Solomon over GF(256), block interleaving and the eight masks, producing a module matrix |
| Radio Group | Mutually exclusive options with keyboard navigation |
| Scroll Area | Custom scrollbar with accessible ARIA scrollbar role and drag support |
| Select | Dropdown selection with cascading type inference and display text resolution |
| Separator | Semantic or decorative divider with orientation support |
| Sheet | Side panels that slide in from viewport edges |
| Signature Pad | Stroke capture from pointer, touch or stylus, keeping the raw points behind a signature |
| Slider | Range input with keyboard navigation and pointer drag support |
| Sortable | Drag-and-drop sortable lists with SortableJS interop, ARIA live announcements, and connected multi-list support |
| Swipe Area | Swipe gestures with a distance threshold, an axis to judge them on, and pointer capture so a swipe off the edge still counts |
| Switch | Toggle control with BbSwitchThumb sub-component for automatic data-state sync |
| Table | Data table with header, body, rows, cells, and pagination |
| Tabs | Tabbed interface with keyboard navigation |
| Toggle | Pressed/active state with aria-pressed support |
| Tooltip | Brief informational popups with hover/focus triggers |
| Tree View | Hierarchical expand/collapse, selection, and checkbox state management |
Services
| Service | Description |
|---|---|
IPortalService |
Two-layer portal management with Container and Overlay categories |
IFocusManager |
Focus trapping and restoration for overlays |
IPositioningService |
Floating UI positioning with auto-update |
IKeyboardShortcutService |
Global keyboard shortcut registration and management |
DropdownManagerService |
Coordinates open/close state across multiple dropdowns |
API Reference
Accordion
<BbAccordion Type="AccordionType.Single" Collapsible="true" DefaultValue="item-1">
<BbAccordionItem Value="item-1">
<BbAccordionTrigger>Section 1</BbAccordionTrigger>
<BbAccordionContent>Content 1</BbAccordionContent>
</BbAccordionItem>
</BbAccordion>
| Parameter | Type | Default | Description |
|---|---|---|---|
Type |
AccordionType |
Single |
Single (one item open) or Multiple (many items open) |
Collapsible |
bool |
false |
When Single, allows closing all items |
Checkbox
<BbCheckbox @bind-Checked="isChecked" Indeterminate="@isIndeterminate">
<BbCheckboxIndicator />
</BbCheckbox>
| Parameter | Type | Default | Description |
|---|---|---|---|
Checked |
bool |
false |
Checked state |
Indeterminate |
bool |
false |
Shows partial/mixed state |
BbCheckboxIndicator renders the appropriate check or indeterminate SVG icon automatically based on parent state:
| Parameter | Type | Default | Description |
|---|---|---|---|
ChildContent |
RenderFragment? |
null |
Custom content instead of default icons |
Size |
int |
14 |
SVG icon size in pixels |
StrokeWidth |
int |
3 |
SVG stroke width |
Select
<BbSelect TValue="string" @bind-Value="selected" @bind-Open="isOpen">
<BbSelectTrigger>
<BbSelectValue Placeholder="Choose..." />
</BbSelectTrigger>
<BbSelectContent>
<BbSelectItem Value="@("a")" Text="Option A" />
<BbSelectItem Value="@("b")" Text="Option B" />
</BbSelectContent>
</BbSelect>
Select uses [CascadingTypeParameter] — child components infer TValue from the parent. Supports ItemClass for parent-level item styling.
| Parameter | Type | Default | Description |
|---|---|---|---|
Value |
TValue? |
— | Selected value (two-way bindable) |
Open |
bool |
false |
Open state (two-way bindable) |
ItemClass |
string? |
null |
CSS classes cascaded to all BbSelectItem children |
Dialog
<BbDialog @bind-Open="isOpen">
<BbDialogTrigger>Open</BbDialogTrigger>
<BbDialogPortal>
<BbDialogOverlay />
<BbDialogContent>
<BbDialogTitle>Title</BbDialogTitle>
<BbDialogDescription>Description</BbDialogDescription>
<BbDialogClose>Close</BbDialogClose>
</BbDialogContent>
</BbDialogPortal>
</BbDialog>
Sheet
<BbSheet>
<BbSheetTrigger>Open</BbSheetTrigger>
<BbSheetPortal>
<BbSheetOverlay />
<BbSheetContent Side="SheetSide.Right">
<BbSheetTitle>Title</BbSheetTitle>
<BbSheetDescription>Description</BbSheetDescription>
<BbSheetClose>Close</BbSheetClose>
</BbSheetContent>
</BbSheetPortal>
</BbSheet>
| Parameter | Type | Default | Description |
|---|---|---|---|
Side |
SheetSide |
Right |
Top, Right, Bottom, Left |
Popover
<BbPopover>
<BbPopoverTrigger>Open</BbPopoverTrigger>
<BbPopoverContent Side="PopoverSide.Bottom" Align="PopoverAlign.Center">
Content here
</BbPopoverContent>
</BbPopover>
| Parameter | Type | Default | Description |
|---|---|---|---|
Side |
PopoverSide |
Bottom |
Top, Right, Bottom, Left |
Align |
PopoverAlign |
Center |
Start, Center, End |
CloseOnEscape |
bool |
true |
Close when Escape key pressed |
CloseOnClickOutside |
bool |
true |
Close when clicking outside |
Tooltip
<BbTooltip DelayDuration="700" HideDelay="0">
<BbTooltipTrigger>Hover me</BbTooltipTrigger>
<BbTooltipContent>Tooltip text</BbTooltipContent>
</BbTooltip>
| Parameter | Type | Default | Description |
|---|---|---|---|
DelayDuration |
int |
700 |
Milliseconds before showing |
HideDelay |
int |
0 |
Milliseconds before hiding |
HoverCard
<BbHoverCard OpenDelay="700" CloseDelay="300">
<BbHoverCardTrigger>Hover for preview</BbHoverCardTrigger>
<BbHoverCardContent>Rich preview content</BbHoverCardContent>
</BbHoverCard>
| Parameter | Type | Default | Description |
|---|---|---|---|
OpenDelay |
int |
700 |
Milliseconds before showing |
CloseDelay |
int |
300 |
Milliseconds before hiding |
Dropdown Menu
<BbDropdownMenu ItemClass="px-2 py-1.5 cursor-pointer rounded hover:bg-accent">
<BbDropdownMenuTrigger>Menu</BbDropdownMenuTrigger>
<BbDropdownMenuContent>
<BbDropdownMenuItem>Cut</BbDropdownMenuItem>
<BbDropdownMenuItem>Copy</BbDropdownMenuItem>
<BbDropdownMenuItem Href="https://example.com" Target="_blank">Visit Site</BbDropdownMenuItem>
<BbDropdownMenuCheckboxItem @bind-Checked="isEnabled">Enable</BbDropdownMenuCheckboxItem>
</BbDropdownMenuContent>
</BbDropdownMenu>
| Parameter | Type | Default | Description |
|---|---|---|---|
ItemClass |
string? |
null |
CSS classes cascaded to all menu items |
BbDropdownMenuItem supports Href and Target for link items — renders as <a> when Href is set.
Switch
<BbSwitch @bind-Checked="isEnabled" class="relative h-6 w-11 rounded-full bg-input">
<BbSwitchThumb class="pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg" />
</BbSwitch>
BbSwitchThumb automatically syncs data-state ("checked" / "unchecked") from the parent via cascading parameter.
Radio Group
<BbRadioGroup TValue="string" @bind-Value="selected" ItemClass="flex items-center gap-2">
<BbRadioGroupItem Value="@("a")">Option A</BbRadioGroupItem>
<BbRadioGroupItem Value="@("b")">Option B</BbRadioGroupItem>
</BbRadioGroup>
| Parameter | Type | Default | Description |
|---|---|---|---|
ItemClass |
string? |
null |
CSS classes cascaded to all radio items |
Tabs
<BbTabs DefaultValue="tab1" Orientation="TabsOrientation.Horizontal"
ActivationMode="TabsActivationMode.Automatic">
<BbTabsList>
<BbTabsTrigger Value="tab1">Tab 1</BbTabsTrigger>
</BbTabsList>
<BbTabsContent Value="tab1">Content</BbTabsContent>
</BbTabs>
| Parameter | Type | Default | Description |
|---|---|---|---|
Orientation |
TabsOrientation |
Horizontal |
Horizontal, Vertical |
ActivationMode |
TabsActivationMode |
Automatic |
Automatic (on focus), Manual (on click) |
Table
<BbTable TData="Person">
<BbTableHeader>
<BbTableRow>
<BbTableHeaderCell>Name</BbTableHeaderCell>
<BbTableHeaderCell>Email</BbTableHeaderCell>
</BbTableRow>
</BbTableHeader>
<BbTableBody>
@foreach (var person in people)
{
<BbTableRow>
<BbTableCell>@person.Name</BbTableCell>
<BbTableCell>@person.Email</BbTableCell>
</BbTableRow>
}
</BbTableBody>
</BbTable>
| Parameter | Type | Default | Description |
|---|---|---|---|
SelectionMode |
SelectionMode |
None |
None, Single, Multiple |
SortDirection |
SortDirection |
None |
None, Ascending, Descending |
Portal Architecture
Primitives use a two-layer portal system for rendering overlay content:
- Container portals (
PortalCategory.Container): Dialog, Sheet — full-screen overlays - Overlay portals (
PortalCategory.Overlay): Popover, Select, Dropdown, Tooltip, HoverCard — positioned floating content
Each category has its own host (BbContainerPortalHost, BbOverlayPortalHost), so opening a tooltip doesn't cause Dialog portals to re-render. BbPortalHost is a convenience wrapper that renders both.
BbFloatingPortal keeps content mounted in the DOM when closed (ForceMount defaults to true), hidden via CSS. A data-state attribute ("open" / "closed") on the portal content enables CSS animations.
JavaScript Modules
Every primitive that needs JavaScript gets it from one bundle,
js/primitives/bb-primitives.js, loaded through PrimitiveModules:
var module = await PrimitiveModules.GetAsync(JSRuntime);
await module.InvokeVoidAsync("elementUtils.scrollIntoView", elementId, "nearest");
The identifier is namespace.function, where the namespace is the module's file name in
camelCase — clickOutside, elementUtils, positioning, focusTrap, and so on. The individual
files are still importable on their own if you need just one.
overlay.open is the one to reach for when adding a floating component: it positions, reveals,
keeps the element positioned and wires the dismissal listeners in a single call. Splitting that back
into separate awaits puts a network round trip between each step, on every open.
From a component, declare what you want rather than wiring it — BbFloatingPortal forwards it:
<BbFloatingPortal Dismiss="@(new FloatingDismissOptions {
ContentId = Context.ContentId,
TriggerId = Context.TriggerId,
OnOutsideInteraction = true,
OnEscapeKey = true })"
OnDismiss="@HandleDismiss">
The portal reports the gesture and does nothing else with it; what a dismissal means stays with the owner.
Two rules matter if you add a primitive that needs JavaScript:
- Re-export the new module from
bb-primitives.js. A module reached by its ownimport(...)from C# costs an extra circuit round trip on Blazor Server, every page load, cached or not. - Do not dispose what
GetAsyncreturns. It is shared by every component on the circuit. The returned reference ignores disposal so that a mistake here cannot break anything, but the call is still dead code.
Controlled vs Uncontrolled
All stateful primitives support both controlled and uncontrolled modes:
Uncontrolled (Component manages its own state)
<BbDialog>
<BbDialogTrigger>Open</BbDialogTrigger>
<BbDialogPortal>
<BbDialogOverlay />
<BbDialogContent>Content</BbDialogContent>
</BbDialogPortal>
</BbDialog>
Controlled (Parent component manages state)
<BbDialog @bind-Open="isDialogOpen">
<BbDialogTrigger>Open</BbDialogTrigger>
<BbDialogPortal>
<BbDialogOverlay />
<BbDialogContent>
<button @onclick="() => isDialogOpen = false">Close</button>
</BbDialogContent>
</BbDialogPortal>
</BbDialog>
@code {
private bool isDialogOpen = false;
}
Design Philosophy
BlazorBlueprint.Primitives follows the "headless component" pattern popularized by Radix UI and Headless UI:
- Separation of Concerns: Primitives handle behavior and accessibility; you handle the design
- Composability: Build complex components by composing simple primitives
- No Style Opinions: Zero CSS included — bring your own design system
- Accessibility by Default: ARIA attributes and keyboard navigation built-in
When to Use
Use BlazorBlueprint.Primitives when:
- Building a custom design system from scratch
- Need complete control over component styling
- Want to match a specific brand or design language
- Integrating with existing CSS frameworks or design tokens
Consider BlazorBlueprint.Components when:
- Want beautiful defaults with shadcn/ui design
- Prefer zero-configuration setup with pre-built CSS
- Need to ship quickly without custom styling
Documentation
For full documentation, examples, and API reference, visit:
License
Apache License 2.0 - see LICENSE for details.
The package includes LICENSE, NOTICE, and staticwebassets/THIRD-PARTY-NOTICES.txt. The bundled Floating UI and SortableJS assets retain their MIT licenses. These notices are also available at _content/BlazorBlueprint.Primitives/THIRD-PARTY-NOTICES.txt.
Contributing
Contributions are welcome! Please see our Contributing Guide.
| 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
- Microsoft.AspNetCore.Components.Web (>= 10.0.12)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on BlazorBlueprint.Primitives:
| Package | Downloads |
|---|---|
|
BlazorBlueprint.Components
Pre-styled Blazor components built with shadcn/ui design and Tailwind CSS. Beautiful defaults that you can customize to match your brand. |
|
|
BlueprintShell
Embeddable Blazor shell built on BlazorBlueprint. Spin up a themed, dockable UI on a configurable port from any .NET application. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.1.0 | 638 | 9/22/2026 |
| 4.1.0-beta.2 | 48 | 9/22/2026 |
| 4.1.0-beta.1 | 54 | 9/21/2026 |
| 4.0.1 | 886 | 9/19/2026 |
| 4.0.0 | 100 | 9/19/2026 |
| 4.0.0-beta.10 | 59 | 9/19/2026 |
| 4.0.0-beta.9 | 57 | 9/18/2026 |
| 4.0.0-beta.8 | 68 | 9/17/2026 |
| 4.0.0-beta.7 | 59 | 9/17/2026 |
| 4.0.0-beta.6 | 57 | 9/17/2026 |
| 4.0.0-beta.5 | 98 | 9/16/2026 |
| 4.0.0-beta.4 | 68 | 9/16/2026 |
| 4.0.0-beta.3 | 76 | 9/15/2026 |
| 4.0.0-beta.2 | 72 | 9/15/2026 |
| 4.0.0-beta.1 | 58 | 9/15/2026 |
| 3.17.0 | 1,538 | 9/13/2026 |
| 3.16.1 | 162 | 9/12/2026 |
| 3.16.0 | 4,321 | 9/4/2026 |
| 3.15.0 | 23,675 | 8/5/2026 |
| 3.14.1 | 18,514 | 7/16/2026 |
## What's New in v4.1.0 (unreleased)
> **Prerelease preparation:** these cumulative notes describe the current v4.1.0 branch. The release script selects the package version.
### Breaking Changes
- **BbTabsTrigger**: Ctrl or Cmd with an arrow key, Home or End no longer moves focus to another tab. With an arrow key it now asks to move the tab through `OnMoveRequested`, and does nothing when that callback is not set.
- **BbTooltipTrigger** and **BbHoverCardTrigger**: the Development-only AsChild warning changes its event name from `TooltipTriggerContextUnconsumed` and `HoverCardTriggerContextUnconsumed` to `TriggerContextUnconsumed`, and its message text changes. Update any log filter that matched the old names.
### New Components
- **BbSwipeArea**: a headless swipe gesture primitive. Wrap any content to get `OnSwipe` with direction, distance and velocity, plus `OnSwipeMove`, `OnSwipeCancel` and `CancelAsync()`. `Threshold`, `MinVelocity`, `Axis` and `TouchAction` tune the gesture, and the gesture maths runs in the browser, so a swipe costs one call into .NET.
- **BbSignaturePad**: a drawing surface for signatures. The line tapers with pen speed (`MinWidth`, `MaxWidth`, `VelocityWeight`), and the ink follows the theme unless `StrokeColor` is set. It exports SVG, PNG and raw strokes (`GetSvgAsync`, `GetPngAsync`, `GetPngBytesAsync`, `GetStrokesAsync`), and supports `ClearAsync`, `UndoAsync` and `SetStrokesAsync`. Exports are streamed, so a large signature does not exceed the Blazor Server message size limit.
### New Features
- **QrEncoder**: a QR code encoder written from scratch, with no JavaScript and no dependency. It covers versions 1 to 40, all four error correction levels (`QrErrorCorrection`), and numeric, alphanumeric and UTF-8 byte modes (`QrEncodingMode`). `GetCapacity` reports how much a version holds.
- **BarcodeEncoder**: encoders for 14 barcode types (`BarcodeType`): Code 128, Code 39, EAN-13, EAN-8, UPC-A, Interleaved 2 of 5, Codabar, ISBN, ISSN, MSI, Telepen, Pharmacode, POSTNET and Royal Mail 4-state. Each type checks its own characters, length and check digit, and a bad value throws `BarcodeFormatException` with a message that is safe to show to the user.
- **GanttBuilder**: the timeline engine behind BbGantt, with no markup. It builds the task tree, rolls up summary dates and progress, lays out the time axis at six zoom levels (`GanttZoom`), and resolves the four dependency types (`GanttDependencyType`).
- **PivotBuilder**: the cross-tabulation engine behind BbPivotDataGrid, with no markup. Totals and subtotals (`PivotTotals`) are computed from every item under them, so an average total is a true average.
- **BbTabsTrigger** gains `OnCloseRequested` (Delete or Backspace), `OnRenameRequested` (F2) and `OnMoveRequested` (Ctrl or Cmd with an arrow key). Each key does nothing until its callback is set, and the move follows the writing direction.
- **AsChildDiagnostics** and **AsChildTriggerDescription**: public helpers, so a custom AsChild trigger can log the same Development-only warning as the library's own triggers.
### Bug Fixes
- **BbCheckbox**, **BbSlider**: keyboard activation/navigation preserves the next Tab; checkbox activation uses the native button click once per key.
- **BbSignaturePad**: stroke restoration waits for JavaScript initialization when called immediately after mounting.
- **BbSwipeArea**: cancellation discards unsent movement updates, including work queued behind an in-flight callback.
- **BbDialogClose**, **BbSheetClose**: native keyboard activation invokes the close action once, including when closing is prevented.
- **Dialog and Popover**: `Modal` now controls outside/Escape dismissal according to its existing contract; it does not change focus trapping.
- **BbMenubar**: closed triggers support keyboard navigation, Escape restores focus, and outside-pointer handling no longer uses a blocking overlay.
- **Navigation menus**: optional arrow/Home/End/Escape navigation works, links remain tabbable, and items without explicit values receive stable IDs instead of opening on a null match.
- **BbContextMenu**: controlled `Open` values are observed, and uncontrolled state changes notify subscribed callbacks.
- **Dropdown menus**: `Dir` applies to trigger content and portaled panels; a null value inherits surrounding direction.
- **BbSignaturePad**: restoring strokes reports whether the filtered drawing is actually empty. Clearing an already-empty pad retains the documented successful-operation callback.
### Improvements
- **Sortable**: per-message overrides make headless keyboard instructions and announcements localizable.
- **AsChild triggers**: **BbCollapsibleTrigger**, **BbPopoverTrigger**, **BbDialogTrigger**, **BbDialogClose**, **BbSheetTrigger**, **BbSheetClose** and **BbDropdownMenuTrigger** now log a Development-only warning when nothing inside them reads the `TriggerContext`. Before, text or an icon inside such a trigger did nothing, and nothing said why.
- **AsChild warning**: all triggers share one message, which says to set `AsChild="false"` or to put a BbButton inside, and names any class or attributes that had no element to go on.
- **bb-primitives.js** now bundles the `signaturePad` and `swipeArea` modules, and `PrimitiveModules.ModuleUrl` changes so browsers fetch the new bundle.