BlazorBlueprint.Primitives 4.0.0-beta.3

This is a prerelease version of BlazorBlueprint.Primitives.
There is a newer version of this package available.
See the version list below for details.
dotnet add package BlazorBlueprint.Primitives --version 4.0.0-beta.3
                    
NuGet\Install-Package BlazorBlueprint.Primitives -Version 4.0.0-beta.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BlazorBlueprint.Primitives" Version="4.0.0-beta.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="BlazorBlueprint.Primitives" Version="4.0.0-beta.3" />
                    
Directory.Packages.props
<PackageReference Include="BlazorBlueprint.Primitives" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add BlazorBlueprint.Primitives --version 4.0.0-beta.3
                    
#r "nuget: BlazorBlueprint.Primitives, 4.0.0-beta.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package BlazorBlueprint.Primitives@4.0.0-beta.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=BlazorBlueprint.Primitives&version=4.0.0-beta.3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=BlazorBlueprint.Primitives&version=4.0.0-beta.3&prerelease
                    
Install as a Cake Tool

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 8: 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

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
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
Dropdown Menu Context menus with items, checkbox items, separators, and keyboard shortcuts
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
Progress Accessible progress bar with determinate and indeterminate states
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
Slider Range input with keyboard navigation and pointer drag support
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
<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 own import(...) from C# costs an extra circuit round trip on Blazor Server, every page load, cached or not.
  • Do not dispose what GetAsync returns. 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:

  1. Separation of Concerns: Primitives handle behavior and accessibility; you handle the design
  2. Composability: Build complex components by composing simple primitives
  3. No Style Opinions: Zero CSS included — bring your own design system
  4. 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.

Contributing

Contributions are welcome! Please see our Contributing Guide.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 3,136 9/22/2026
4.1.0-beta.2 62 9/22/2026
4.1.0-beta.1 61 9/21/2026
4.0.1 1,511 9/19/2026
4.0.0 118 9/19/2026
4.0.0-beta.10 64 9/19/2026
4.0.0-beta.9 64 9/18/2026
4.0.0-beta.8 74 9/17/2026
4.0.0-beta.7 65 9/17/2026
4.0.0-beta.6 65 9/17/2026
4.0.0-beta.5 105 9/16/2026
4.0.0-beta.4 75 9/16/2026
4.0.0-beta.3 83 9/15/2026
4.0.0-beta.2 88 9/15/2026
4.0.0-beta.1 65 9/15/2026
3.17.0 4,126 9/13/2026
3.16.1 179 9/12/2026
3.16.0 5,393 9/4/2026
3.15.0 25,677 8/5/2026
3.14.1 19,428 7/16/2026
Loading failed

## What's New in v4.0.0-beta.3

**This is a prerelease.** The API may still change before the stable v4.0.0 release.

### Breaking Changes

- **BbPopoverContent**, **BbSelectContent**, **BbDropdownMenuContent**: the `JsOnClickOutside` and `JsOnEscapeKey` JSInvokable methods are removed. Dismissal now arrives through the new `BbFloatingPortal.OnDismiss` callback.
- **BbTableRow**, **BbDataGridRow**, **BbPopoverContent**, **BbHoverCardTrigger**, **BbCategoryPortalHost** no longer implement `IAsyncDisposable`. Code that awaited `DisposeAsync()` on these components must be updated.
- **BbFloatingPortal** opens and closes from `OnParametersSet` without awaiting interop. It no longer passes an `ElementReference` to JavaScript; the content is located by a `data-bb-portal` attribute instead.
- **BbFloatingPortal**: JavaScript now owns the resolved `data-side` attribute and a listbox's `data-focused` and `aria-activedescendant` state. Owners that rendered these from C# must stop, or the two writers will conflict.
- **JavaScript modules**: `overlay.open` and `overlay.close` have new signatures. `open(portalId, reference, options, dotNetRef)` no longer takes a floating element, and `close(portalId, options, dotNetRef)` replaces `close(portalId, floating)`.
- **JavaScript modules**: the `onClickOutside` and `onEscapeKey` exports are removed from `click-outside.js`. Use `overlay.open` with `FloatingDismissOptions` instead.
- **JavaScript modules**: all primitive modules are now bundled into `bb-primitives.js` and exposed under a namespace (`focusTrap.createFocusTrap`, `positioning.computePosition`, etc.). Custom code that imported the individual module files must load the bundle via `PrimitiveModules.GetAsync` and use the namespaced identifier.
- **escape-keydown.js**: `initialize` now takes an optional `methodName` argument; the default callback name changed from `HandleEscape` to `JsOnEscapeKey`.
- **PrimitiveModules** imports the bundle through the new versioned `ModuleUrl`, not `ModulePath`. Code that passed `ModulePath` to `JsModules.TryGetLoaded` must pass `ModuleUrl` instead.
- **bb-primitives.js** now throws at load when any module it imports is older than the bundle. A stale cached file that previously failed later with a missing-function error now fails immediately with the file named.

### New Features

- **Native dialog rendering**: **BbDialog** gains a `RenderingStrategy` parameter. Set it to `OverlayRenderingStrategy.Native` to render a browser `<dialog>` element driven by `showModal()`, which works across Blazor render-mode boundaries and does not need a portal host. Falls back to JavaScript rendering with a diagnostic when the browser lacks support.
- **OverlayRenderingOptions**: `AddBlazorBlueprintPrimitives` now accepts a configure callback to set a global `DefaultStrategy` for all overlays.
- **INativeOverlayService**: new scoped service that resolves the effective rendering strategy and drives the native `<dialog>` element (show, close, focus, and lifecycle events).
- **BbDialogContent** gains `CloseOnOverlayClick` to control whether a backdrop click closes a native dialog.
- **BbFloatingPortal** gains `Dismiss` (`FloatingDismissOptions`) and `OnDismiss` (`EventCallback<FloatingDismissReason>`). Owners declare which dismissal gestures to listen for and the portal wires them in the same call that opens the overlay.
- **BbFloatingPortal** gains `Keyboard` (`FloatingKeyboardOptions`). Listbox or menu keyboard handling is wired inside the open call, with `FloatingKeyboardKind` selecting the behaviour.
- **BbFloatingPortal** gains `SideElementId`, so JavaScript writes the resolved `data-side` attribute on a named element without a C# re-render.
- **BbFloatingPortal** gains `ScrollToCurrentIn` and `ScrollToCurrentSelector`, which scroll a chosen item into view before the overlay is revealed.
- **BbPopoverContent** gains `ScrollToSelected` and `ScrollToSelectedSelector`, so a popover-based list opens already scrolled to its current item.
- **FloatingDismissReason** enum reports whether an overlay was dismissed by an outside interaction or the Escape key.
- **JsModules.GetAsync** and **PrimitiveModules.GetAsync**: shared, per-circuit module references that any component can use without owning or disposing them. **JsModules.TryGetLoaded** and **PrimitiveModules.TryGetLoaded** return an already-loaded module synchronously.
- **BbFloatingPortal** gains `AutoFocusId`, which focuses a named element one frame after the reveal, inside the call that opens the overlay.
- **BbFloatingPortal** gains `RestoreFocusToId` and `RestoreFocusOnClose`, so the browser returns focus to the trigger inside the close call for an intentional close only.
- **BbPopoverContent** gains `AutoFocusId`, so a search box inside a popover takes focus without a round trip.
- **JsModules.Versioned** appends an assembly's informational version to a module path as a `v` query, so a new release is a new URL. **PrimitiveModules.ModuleUrl** exposes the versioned bundle URL.
- **elementUtils.observeNearBottom** and **observeHover**: new JavaScript observers that call .NET once when a list scrolls near its bottom or when the pointer moves onto a different item.

### Bug Fixes

- **Overlays**: Escape now closes only the topmost open overlay. A popover inside a dialog no longer closes the dialog on the first press.
- **Overlays**: the exit-animation wait ignores infinite animations, so an overlay with a spinner inside closes on time instead of reappearing at full opacity.
- **BbSelectContent**: hover and keyboard highlight are written by JavaScript only, so two options can no longer appear focused at once.
- **BbDropdownMenuContent**: clicking a nested portal inside an open menu no longer closes the menu. Outside-click detection now resolves elements by id per event, so it does not go stale after a re-render.
- **BbFloatingPortal**: removed the fixed 500ms deadline on the portal host render signal, which timed out on slow connections. The wait is now unbounded and cancelled on close or disposal.
- **BbFloatingPortal**: a listbox is focused only after the reveal, so arrow keys no longer reach the trigger while the overlay is still hidden.
- **JavaScript modules**: a browser or CDN that serves a stale copy of a bundled module no longer kills the circuit at the first call. The bundle fails at load with an error that names the file and says what to do.
- **NavigationMenuContext**: the close timer catches every exception, so an unexpected error in the fire-and-forget handler can no longer close the Blazor Server circuit.
- **BbPopoverContent** and **BbDropdownMenuContent** no longer render a second time on open. The duplicate render raised a portal refresh mid-cycle that the host had to defer by a round trip.

### Performance

- **Overlays** open and close in one circuit round trip each. The portal registers content, positions, reveals, starts auto-update, and wires dismissal and keyboard listeners in a single interop call that is not awaited. On a 20ms round trip, a select open went from 11 messages to 1 and a close from 16 to 1 compared with v3.
- **Overlays**: arrow keys and option hover in a listbox no longer send a message to the server.
- **JavaScript modules** are imported once per circuit and shared, instead of once per component instance. Pages with many inputs issue far fewer round trips on Blazor Server.
- **Floating UI** is statically imported by `positioning.js`, removing a hidden dynamic import on the first position computation.
- **BbDataGridRow** and **BbTableRow** no longer attach keyboard and click handlers per row. **BbDataGrid** and **BbTable** delegate a single handler from the container, and rows opt in via `data-bb-row-keys` and `data-bb-row-click`.
- **BbSelectContent** scrolls the selected option into view and attaches the keyboard handler in the same call that opens the overlay, so the list appears already scrolled to the selection.
- **BbDropdownMenuContent** drops a redundant width-matching interop call; `MatchAnchorWidth` already covers it.
- **BbTooltipContent** and **BbHoverCardContent** no longer request the portal's ready callback, which was empty and cost a round trip on Blazor Server.
- **BbSelectContent**, **BbPopoverContent** and **BbDropdownMenuContent** restore focus to the trigger inside `overlay.close` instead of awaiting `FocusAsync` after the close render, saving a round trip on every Escape and every selection.
- **BbPopoverContent** requests the portal's ready callback only when a consumer has set `OnContentReady`. Without one, the callback was a round trip spent notifying no one.
- **Overlays**: an element named by `AutoFocusId` is focused inside the open call, replacing a ready callback, a 50ms sleep, and a second round trip.
- **Scroll containers**: `observeNearBottom` checks the scroll position in the browser, coalesced to one check per frame, and calls .NET once on entering the near-bottom zone instead of once per scroll event.
- **Lists**: `observeHover` uses one delegated hover listener per list and reports only a genuine change of item, so pointer travel no longer sends a message per pixel.

### Improvements

- **README** documents the JavaScript bundle, the `overlay.open` pattern, and the rules for adding a primitive that needs JavaScript.