Headless, unstyled Blazor primitive components with ARIA attributes and keyboard support. Build your own component library using these composable primitives.
$ dotnet add package BlazorBlueprint.PrimitivesHeadless, unstyled Blazor primitive components with ARIA attributes and keyboard support. Build your own component library using these composable primitives.
dotnet add package BlazorBlueprint.Primitives
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
| Primitive | Description |
|---|---|
| Accordion | Collapsible content sections with single or multiple item expansion |
| Checkbox | Binary selection control with indeterminate state and BbCheckboxIndicator sub-component |
| Collapsible | Expandable content area with trigger control |
| Dialog | Modal dialogs with backdrop, focus management, and portal rendering |
| Dropdown Menu | Context menus with items, checkbox items, separators, and keyboard shortcuts |
| Floating Portal | Unified floating content infrastructure with ForceMount and positioning |
| Hover Card | Rich preview cards on hover with delay control |
| Label | Accessible labels for form controls with automatic association |
| Popover | Floating panels for additional content with positioning |
| Radio Group | Mutually exclusive options with keyboard navigation |
| Select | Dropdown selection with cascading type inference and display text resolution |
| Sheet | Side panels that slide in from viewport edges |
| 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 |
| Tooltip | Brief informational popups with hover/focus triggers |
| 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 |
<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 |
<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 |
<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 |
<BbDialog @bind-Open="isOpen">
<BbDialogTrigger>Open</BbDialogTrigger>
<BbDialogPortal>
<BbDialogOverlay />
<BbDialogContent>
<BbDialogTitle>Title</BbDialogTitle>
<BbDialogDescription>Description</BbDialogDescription>
<BbDialogClose>Close</BbDialogClose>
</BbDialogContent>
</BbDialogPortal>
</BbDialog>
<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 |
<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 |
<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 |
<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 |
<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.
<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.
<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 |
<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) |
<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 |
Primitives use a two-layer portal system for rendering overlay content:
PortalCategory.Container): Dialog, Sheet — full-screen overlaysPortalCategory.Overlay): Popover, Select, Dropdown, Tooltip, HoverCard — positioned floating contentEach 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.
All stateful primitives support both controlled and uncontrolled modes:
<BbDialog>
<BbDialogTrigger>Open</BbDialogTrigger>
<BbDialogPortal>
<BbDialogOverlay />
<BbDialogContent>Content</BbDialogContent>
</BbDialogPortal>
</BbDialog>
<BbDialog @bind-Open="isDialogOpen">
<BbDialogTrigger>Open</BbDialogTrigger>
<BbDialogPortal>
<BbDialogOverlay />
<BbDialogContent>
<button @onclick="() => isDialogOpen = false">Close</button>
</BbDialogContent>
</BbDialogPortal>
</BbDialog>
@code {
private bool isDialogOpen = false;
}
BlazorBlueprint.Primitives follows the "headless component" pattern popularized by Radix UI and Headless UI:
Use BlazorBlueprint.Primitives when:
Consider BlazorBlueprint.Components when:
For full documentation, examples, and API reference, visit:
Apache License 2.0 - see LICENSE for details.
Contributions are welcome! Please see our Contributing Guide.