Version v3 Documentation
You are viewing documentation for an older version.
View latest (v4) documentation →
Changelog
Release history and version updates for Blazor Blueprint.
September 22, 2026
Fixed
- The mobile DateRangePicker preset dropdown reflects the selected dates instead of defaulting to Today. Unmatched or partial selections show Custom; an empty selection shows Select date range.
- Radar tooltips escape data-derived text, preventing HTML injection through labels or series names.
- Signature stroke restoration waits for the drawn pad to mount and initialize, including repeated switches from typed mode.
- Pivot parent headings and Gantt non-working-day bands redraw when their visible metadata changes. Initial Gantt build errors appear immediately.
- ListBox, Slider and Checkbox keyboard navigation preserves Tab behavior; ListBox uses its visible label as its accessible name.
- Chart click callbacks can be added, removed or replaced after initialization without remounting the chart.
- Swipe cancellation discards queued movement updates. Explicitly setting FileUpload's bound
Filesto null clears the selection and releases removed file resources.
September 21, 2026
Added
- Accessible names on picker, selection, OTP and upload controls; configurable
BbMapChartandBbMap: a world choropleth for visitors, sales, and otherAsChildDiagnostics.WarnIfUnconsumed<TTrigger>andAsChildTriggerDescriptionBarcodeFormatException, inBlazorBlueprint.Primitives.Barcode. AnArgumentException, so
Fixed
- Responsive tab adding/reordering, conditional step order, required-chip dismissal,
- Date/time restrictions now also apply to Now and empty-value time stepping.
- Primitive menus now support the documented keyboard and controlled-state behavior.
- Tinted bubble contrast, message alignment, editable-date invalid semantics,
- ScrollToTop observes changing options and late targets, preserves keyboard focus,
- Upload text/errors, AM/PM labels and MultiSelect removal/count labels are localizable.
BbDrawerreportsOpenChangedwhen it is left to itself. WithoutOpenbound, the drawer- A trigger in AsChild mode now says so when nothing inside it can use it. Eleven triggers
OnBuiltno longer rebuilds forever when the page that declares the chart handles it.BbGanttno longer wires up JavaScript against an element that is not there. The chart is- A Gantt's tree column shows the task name again when the chart declares its own
Columns. - A Gantt's column resize handles are wired on the first mount. They were drawn but never
BbBarcodeno longer printsArg_ParamName_Nameinstead of an error on WebAssembly. It
September 20, 2026
Added
- **
BbGantt, a plan drawn against a timeline: a task list down the side, a bar for every task - **
BbPivotDataGrid, a cross-tabulation: one field down the side, another across the top, and a BbBarcode, fourteen linear symbologies drawn as SVG, on encoders written from scratch in C#.BbQrCode, a scannable code drawn as SVG, on a QR encoder written from scratch in C#. NoBbPickList, two lists and the buttons that move options between them. Pick rows and pressBbListBox, an always-visible list of options. No trigger, no popover: the choices stay onBbSignature, a signing field, on the newBbSignaturePadprimitive. Sign by drawing or byBbSwipeArea, a headless swipe gesture primitive. Wrap any content and hear about swipes
Changed
BbTabsListcan now add, close, rename and reorder its tabs. Four flags —Addable,datagrid-columns.jsis nowtable-columns.js, shared byBbDataGridandBbGantt. It was
September 19, 2026
Added
- The last two chart gaps: a rose and a sankey.
- Five new components for links, search highlighting, image fallbacks, scroll-to-top navigation, and unsaved-change prompts.
- Right-to-left support.
BbDirectionProvidersets the writing direction for everything inside it, so the library mirrors for Arabic or Hebrew. Wrap the layout in it, including the overlay hosts. It renders no box of its own —display: contents— so adding it changes no layout; it writes adirattribute and cascades aDirectionContext.TextDirection.Auto, the default, followsCultureInfo.CurrentCulture, which is what every component did before the provider existed, so nothing changes until you ask for it. BbChipandBbChipSet. A chip is the interactive counterpart to a badge: it carries a selected state, a dismiss button, or both. Five variants and three sizes, each variant with its own selected treatment, plus anIconfragment that takes an avatar as readily as an icon. A chip renders as a plain span until something makes it interactive, so a static chip is not announced as a button; a selectable one carriesaria-pressed, and the dismiss button gets its own accessible name fromDismissLabelor the localizedChip.Dismiss.BbFab. A floating action button: raised, pinned to a corner, carrying the one main action of a screen. It is built onBbButton, so it works as anAsChildtrigger — that is how a speed dial is composed, with the button opening aBbDropdownMenuinstead of running an action. Five placements written in logical properties, so the corner follows the reading direction, pinned to the viewport or to the nearest positioned ancestor throughFixed. It sits above a bottom navigation bar and below the overlay layer, and clears the device safe area so a home indicator cannot cover it. SupplyingChildContentwidens the button with a label. Corners follow the theme's radius by default — the samerounded-mdevery other component uses — andShape="FabShape.Circle"makes it a full circle instead, or a pill once it carries a label. An icon-only button needsAriaLabel— it has no text of its own.BbStepperandBbStep. A progress indicator for a sequence of steps, horizontal or vertical, with optional per-step content rendered only while that step is active. It is deliberately not a form: it holds noEditContext, validates nothing and renders no navigation buttons, which is what separates it fromBbFormWizard. State is derived from position — behind the active step is complete, ahead is pending — andBbStep.Stateoverrides that, which is how an error or a skipped step is shown.Clickableturns each marker into a button, and aDisabledstep refuses activation. The indicator is an ordered list, the active step carriesaria-current="step", and each step adds hidden text with its position and its state, so a state is never colour alone.- Active hours.
ActiveHoursnames the part of each weekday the schedule is about, and everything outside it is muted. A day with no range is muted end to end, so weekends need no entry; two ranges for one day mute the gap between them, which is how you get a lunch break. Times are wall times in the display zone, so a day that gains or loses an hour keeps the same clock boundaries. Start is inclusive and End exclusive;TimeOnlycannot express 24:00, soTimeOnly.MinValueas the End runs to the end of the day. Muted slots say so to a screen reader rather than relying on the tint. An empty list — the default — mutes nothing. - Name your own time zones. The editor listed every IANA zone the host supports — roughly 600 of them, labelled by identifier, so
Australia/Sydneyread as "Australia/Sydney".TimeZonestakes a list ofSchedulerTimeZone(Id, Title)and shows only those, under your names. IDs are validated when the parameter is set, so you cannot rename your way into a zone that does not exist. An event already stored in a zone you did not list is still offered, so opening it cannot quietly move it to whichever zone happened to sort first. LeavingTimeZonesempty keeps the old full list. - Your own fields in the event editor.
SchedulerEventis no longer sealed: derive from it to carry a description, an agenda, a customer ID.Clone()is virtual and a protectedCopyTo()handles every field the base type owns, so an override only writes its own.NewEventFactorydecides the type of a newly created event — without it a new event is the base type and your fields have nowhere to live.EditorContentthen renders your fields in the editor; render@context.DefaultContentto keep the built-in ones and add around them, or leave it out to replace them. The context carriesDraft,IsNew,ScopeandDefaultContent. BbSchedulergains a Month view.SchedulerView.Monthrenders six week rows — always six, so the grid height does not change between months — with multi-day events drawn as bars across the days they cover and single-day events as chips. Previous and Next step a calendar month rather than six weeks.MaxEventsPerDaycaps each cell; a bar spends that budget in every day it covers, so a day carrying one shows fewer chips and counts the difference into its own "+x more". Resources act as a filter here rather than lanes, because a month cell cannot carry a column per resource.AllowDragandAllowResizeare ignored in Month: the pointer code derives times from the time grid's fixed 40px-per-slot lane geometry, which a month cell does not have.- All-day events. Set
SchedulerEvent.IsAllDayand the event draws as a bar in a band above the time grid instead of a block inside it. The band does not scroll with the day, and its columns are pinned to the lane columns below.StartandEndare then read as dates in the event'sTimeZoneIdand their time components are ignored, so flipping the flag on existing timed data is safe.Endis exclusive — a single all-day event on the 14th runs from the 14th to the 15th — which matches iCalendar'sDTENDand keeps the existing half-open range checks unchanged; the editor asks for the last day the event covers and converts. - Multi-day all-day events are one bar, not a chip per day. Runs that overlap stack into rows, longest at the top; runs that never touch share a row. A run leaving the visible range is squared off at the join, and both halves carry the whole event's dates in their
aria-labelrather than their own segment's. With two or more resources visible the band groups by resource — one row per resource, one column per day — rather than mirroring the day-major lane order below it: lanes run Mon/Room A, Mon/Room B, Tue/Room A, so one room's two-day event occupies non-adjacent columns and no single bar could span them. The band then stops lining up column-for-column with the grid, which is the price of drawing the bar correctly. NarrowingVisibleResourceIdsto one resource restores the alignment. - Choose which resources to show.
VisibleResourceIdsis a two-way bindable list of resource IDs: null shows every resource, an empty list shows none and says so, and the empty string selects the Unassigned lane.ShowResourceFilterdraws aBbMultiSelectin the toolbar bound to the same property, so a built-in picker and an application's own UI cannot disagree. Off by default, so existing schedules are unchanged. - Context menus on slots and events. Right-click an empty slot for New event; right-click an appointment for Edit event and Delete event. Delete confirms without opening the editor first. Both menus are replaceable through
SlotContextMenuContentandEventContextMenuContent: render the context'sDefaultItemsto keep the built-in entries and add your own around them, or leave it out to replace the menu entirely. The slot context carriesStart,EndandResource; the event context carries theSchedulerOccurrence. BbScheduler.ToolbarContent. The toolbar is rendered inside the component, so there was no way to restyle or rearrange it. Render@context.DefaultContentto keep the built-in toolbar and add around it, or leave it out to replace it. The context carries read state plus the three commands that cannot be expressed from outside:Navigateknows the step size for the current view (one day, seven days, one month), andGoToTodayresolves today in the schedule's time zone rather than the server's.Date,View,FirstDayOfWeekandVisibleResourceIdsstay ordinary bindable parameters.
Changed
- Quill 2.0.3 now ships inside the package, so
BbRichTextEditorneeds no setup. The host page no longer needs a<script>and a<link>for Quill — until this change, forgetting them left the editor dead with nothing but a console error to say why, which was the one component in the library that did not just work on install. The interop loadslib/quill/quill.jsandlib/quill/quill.core.cssfrom the package's own static assets on first use, with the same lazy single-flight shape the ECharts renderer already uses: nothing is fetched on a page with no editor, and fourteen editors on one page still fetch once. Quill's dist build is UMD rather than ESM, so it is loaded through a<script>tag rather than a dynamicimport(), which is the only way the two loaders differ. A host that wants a different Quill build keeps full control:window.Quillis checked first, so loading your own copy before the first editor renders leaves the bundled one untouched. The stylesheet is injected unlayered, exactly where a host's own<link>put it, so the themed overrides inblazorblueprint.css— which are!importantinside@layer components— still win as before. Quill's BSD 3-Clause notice is inTHIRD-PARTY-NOTICES.txtalongside the ECharts and Tailwind ones. This adds roughly 214 KB of static assets to the package. - Clicking a scheduler event highlights it instead of opening the editor. Double-click it, press Enter while it is focused, or use the context menu's Edit event. This matches what an empty slot already does, and it stops a stray click on the way to a drag throwing the editor in your face. Highlighting is exclusive: selecting an event clears a highlighted slot or day, and vice versa. Two occurrences of one series highlight independently.
- Month view creates and edits like the other views. Double-click anywhere in a day cell — not only the day number — to open the editor for a new event at
StartHour. Double-click an event to edit it. Right-click empty space for New event, or an event for Edit and Delete. - Clicking an empty scheduler slot highlights it instead of opening the editor. Double-click it, or press Enter while it is focused, to create an event; Space highlights without creating. Enter created an event before this change and still does, so keyboard users lose nothing. The highlight is exactly one slot tall, is keyed to the lane as well as the time so the same hour in two resource lanes stays distinct, and clears on navigation, a view change or a resource-filter change. A drag that finishes over an empty slot creates nothing.
Added
- A themed reconnection dialog for Blazor Server. Give the host page an element with
Fixed
dark:utilities followed the operating system instead of the dark-mode toggle. Dark mode is- The radial chart's centre label sat off-centre, and its chart title vanished. The label in the
- Radial bar labels no longer overwrite each other.
ShowLabelsplaced each label at the angular - DataGrid cell editors no longer cover Save and Cancel, or wreck a checkbox. The editor slot reset every direct-child
button, which was wrong in both directions. A combobox and a multi select wrap their trigger in a container, so the reset never reached it and the trigger kept its own default width — 200px and 300px — inside a narrower cell, overflowing the slot and painting straight over the addon holding Save and Cancel. Both buttons stayed in the DOM, visible and reachable by keyboard, so nothing short of a hit test showed the problem. Meanwhile a checkbox, switch or toggle editor is a plain button, so the same reset stretched it to the full cell and stripped its border, leaving an unchecked cell looking empty. - Month view no longer claims no resources are selected when one is. The empty state inferred emptiness from the lane count, and Month builds no lanes at all, so filtering to a single resource and switching to Month replaced the grid with "No resources are selected". It now asks the filter directly.
- The scheduler's date heading no longer jumps above the navigation on a narrow container. The heading carried an ungated
row-start-1while its neighbouringcol-start-2was gated at the@4xlcontainer breakpoint (896px). Below that width the toolbar grid collapses to one column, and grid places explicitly positioned items first, so the heading claimed row 1 and pushed the previous/today/next buttons and the view controls into rows 2 and 3. Reported from the documentation site. - The scheduler's time-zone label no longer reads as part of the view button group. It shared a
gap-2flex row with the week-start select and the Day/Week/Work week buttons, so the 8px beside it was the same 8px that separates the buttons from each other. The three view buttons now form their own group, which also stops them splitting across a wrap boundary mid-group. - A context menu opened near a viewport edge no longer falls off screen.
BbContextMenuContentpositioned itself at the raw pointer coordinates and never measured against the viewport. It now flips back across the pointer when it does not fit, the way a native context menu does, and clamps when it fits on neither side. Not new, and not specific to the scheduler, but a tall scrolling scheduler is where it showed up. TagInputTrigger.Blur.BbTagInputcould only commit a tag on a keystroke — Enter, comma, space, tab or semicolon — so text the user had typed but not confirmed was thrown away when they clicked elsewhere. AddBlurtoAddTriggerand leaving the input commits it. It is the only trigger that is not a key, and it is off by default, so nothing changes unless you ask for it.
Changed
- A multi-day event in
BbEventCalendaris one bar, not a chip in every day it covers. An event running Thursday to Saturday drew three identical chips, so it read as three separate events that happened to share a title — the other half of #544. It is now a single bar across the days it covers, in both the month and the week view. Runs that overlap stack into lanes, longest at the top; runs that never touch share a line. An event crossing a week boundary becomes one bar per row, squared off at the join so the two halves read as one event, and both halves carry the whole event's dates in theiraria-labelrather than their own segment's. Bars are positioned with acalc()over the seven-column grid, so they stay on the column boundaries at any width with no measuring and no JavaScript.
Documentation
- Running your own Tailwind build needs one rule in your own stylesheet: the default border colour. Tailwind's preflight sets
border: 0 solid, and that shorthand resetsborder-colortocurrentColor. Both stylesheets write their preflight into the sharedbaselayer at the same specificity, so whichever loads last wins — and that is the application's. Without the rule an application's ownborder,border-band so on render in the text colour rather than the theme's grey; library components are unaffected, because they carry their own colour on abb:-prefixed utility. Not new in v4 — the default has sat in@layer basesince at least 3.17.0 — but v4 is the release that makes running your own Tailwind build alongside the library safe, so more people do it, and upgrading is when you rebuild your CSS and notice. Documented in the v4 migration guide and THEMING.md, and the migration checklist no longer claims there is nothing to do.
Fixed
- The calendar's day grid is centred instead of hanging to the left. The seven day cells are a fixed
w-9each — 252px — while the table is as wide as the month and year selects above it, which is wider. The rows were left-aligned, so the difference showed as dead space to the right of the dates. It appeared when the year select was widened from 80px to 100px (b39be911) to stop the year truncating, which is why it looked like it came from nowhere. The header row and every week row now centre their cells, so the day names stay over their columns and the space falls evenly on both sides.BbDatePicker,BbDateRangePickerandBbDateTimePickerembed the same calendar and pick the fix up with it.
September 18, 2026
Added — Component expansion
- DateInput and TimeInput with culture-aware segments, keyboard navigation/increments, nullable drafts, inclusive bounds, read-only/disabled behavior, optional pickers and EditContext validation.
- Sortable keyboard interaction and menu composition. Sortable supports pickup/reorder/drop/cancel, connected-list transfer with Control+Left/Right, typed move/drop predicates, a reusable handle and custom inert drag previews. DropdownMenu, ContextMenu and Menubar gain shared submenu and radio-item families; ContextMenu also gains checkbox items.
- Theme presets and scoped appearance. Density, font family, surface and menu options persist with existing colors and radius. ThemeScope carries local settings into floating overlays; ThemeSwitcher can expose design settings. Applications supply font assets.
- DataView selection, grouping and list virtualization. Stable keys preserve selections after item reloads; group headings remain mounted. Virtualization covers loaded list rows, while provider fetching stays page/batch based. MobileToolbar places sorting and FilterContent in a bottom sheet. Local data is no longer truncated when pagination is hidden.
- Practical form controls. MultiSelect adds FooterContent and CloseAsync. FilterBuilder adds saved presets, searchable fields and per-field value editor templates. Select gains a bottom-sheet presentation with shared option behavior and near-bottom loading.
- Mobile components. AppBar, BottomNav/BottomNavItem, NotificationBadge, QuantityStepper and SectionHeader, plus Drawer viewport snap points with pointer/keyboard resizing and optional drag dismissal.
- Reusable motion. Motion presets/custom keyframes and viewport/hover/press/manual triggers; HeightAnimation for expansion and resizing; SelectionIndicator; PageTransition, ScreenTransition and RenderStateProvider. Animations honor reduced motion, cancel stale work, and retain usable prerendered content. Screen/page transitions animate incoming content; they do not retain outgoing routes.
- Carousel and navigation. Carousel adds autoplay with pause/resume, drag navigation, measured responsive slide bounds, visible slide counts, gaps, indicators and index/change callbacks. Sidebar gains a Pill collapsed mode, compact navigation, inset spacing and a shared selection indicator.
- Small controls. Semantic/soft Badge variants and BadgeIcon, ToggleGroup Required/Scrollable options, and solid/dashed/dotted Separator patterns. Required toggle groups retain the last selection; applications supply the initial value.
- Discoverable component inventory. Document all 42 new styled components and six supporting headless menu components in README. The component homepage, sidebar and command search share the same grouped demo-page catalog, with one destination per actual demo. Standalone mobile and motion components have focused pages; composition helpers stay in their owning component's examples and API references. Sidebar and homepage badges identify new components only; API-reference badges mark specific v4 additions against the v3.17 release.
- Permanent live examples, copyable snippets and API reference entries accompany each addition.
- Browser regression coverage. Add a Playwright suite for Server, WebAssembly and Interactive Auto in Chromium and WebKit, covering keyboard navigation, mobile overlays, scoped themes, scheduler editing and Auto's transition to WebAssembly.
Added
- DataGrid cell and batch editing.
DataGridEditMode.Cellsaves each accepted cell independently;DataGridEditMode.Batchstages changes until the batch is committed. Drafts are isolated throughEditItemFactory, validated before saving, and retained when persistence rejects a change. Applications provide deep-copy factories and atomic batch persistence. Editors support keyboard save/cancel and custom text, numeric, date, select and checkbox controls; accepted changes reapply the active sorting. BbSchedulerfor time-slot scheduling. Day, Week and WorkWeek views include resource lanes, overlapping appointments, a Bb component event editor, and delete confirmation. Week view offers a bindable Monday/Sunday start throughFirstDayOfWeek/FirstDayOfWeekChanged. WorkWeek always shows Monday–Friday, navigates in seven-day steps, and preserves the full-week start preference.- Scheduler drag, resize and slot sizes. Drag events to move them between times and resource lanes, or extend the top/bottom borders to change start/end times. Gestures snap to
SlotMinutes, including 15, 30 and 60 minutes, and use the same persistence/rejection callbacks as editor saves.AllowDrag,AllowResizeandReadOnlycontrol interaction. - Scheduler recurrence and time zones. Daily, weekly, monthly and yearly rules support editing one occurrence or an entire series, with independent event/display IANA time zones and validation of skipped or repeated daylight-saving times.
EnableTimeZones="false"hides zone controls and usesTimeZoneIdfor display and editing while preserving stored instants and recurrence zones. - Form field wrappers for the five new bindable controls.
BbFormFieldDateInput,BbFormFieldTimeInput,BbFormFieldTreeSelect,BbFormFieldCascaderandBbFormFieldQuantitySteppercomplete the set: every control in the library that exposes aValueExpressionnow has a wrapper that supplies the label, helper text and validation message. Each was already built forEditContext; only the surrounding field was missing.BbFormFieldTreeSelectreadsValuesExpressioninMultiplemode andValueExpressionotherwise, so the validation message follows whichever one you bind.BbCascadergains theRequiredparameterBbTreeSelectalready had, andBbCascader,BbTreeSelectandBbQuantitySteppergainAriaDescribedBy— without it a wrapper had no way to point the control at its own error text. BbTreeSelectandBbCascader. Searchable hierarchy pickers with stable keys, form bindings, clear/disabled states and leaf-only selection. TreeSelect supports single/multiple selection with cascading parent checkboxes and indeterminate states. Cascader provides column navigation, full-path search, optional branch selection and keyboard/RTL navigation; opening a selected path or expanding a branch reveals the newest column.- FileUpload transport lifecycle. Optional
UploadHandlercallbacks receive a size-limited browser stream and cancellation token, report progress, and support cancellation and retry. Browser file references survive subsequent selections; retry starts a fresh attempt from zero. Applications supply the upload destination. BbEventCalendar.ContainerClass. Styles the view container — the month grid, the week grid or the agenda list — rather than the root, which also wraps the toolbar and so could not be used to size the calendar itself. The same classes apply in every view, so a height survives a switch between Month, Week and Agenda. PairClass="flex h-full flex-col"withContainerClass="grow overflow-auto"to fill the space a page gives you. Requested in #544.BbDataView.SearchDebounceMs. The toolbar search now waits out a typing pause before it filters, with the same 300 ms default asBbDataGrid(#543).
Fixed
- Card and other borders no longer render near-black in an application that runs its own Tailwind build. The default border colour was a bare
* { border-color: var(--border) }in the sharedbasecascade layer. A consumer's own Tailwind writes its preflight into that same layer, and preflight'sborder: 0 solidresets the colour tocurrentColor— two*selectors, one layer, identical specificity, so whichever stylesheet the page links second won. Linkblazorblueprint.cssfirst, which is what the prefixed-utility fix for #496 encourages, and every library border turned the foreground colour. The rule now matches on the class attribute instead, which settles it on specificity rather than link order, and stays inbaseso a consumer's ownborder-*utilities still win. Scoped to the library's own prefixed classes, so it never touches consumer markup (#527). BbDataGridglobal search honoursSearchDebounceMsagain. The search field was left onBbInput'sUpdateTiming.OnChangedefault, which reports only on blur or Enter — so the grid's ownTask.Delaydebounce never started, anItemsProviderwas not called while typing, and clearing the box did not reload until the field lost focus. The input now debounces in the browser atSearchDebounceMs, so a run of keystrokes costs one provider call rather than one per key, which matters most on Blazor Server.BbDataView's toolbar search had the same defect and takes the same fix (#543).- A live
<BbPortalHost />no longer reports itself missing. Registration was a boolean, and two hosts overlap more often than that allows:BbPortalHostis itself two category hosts, and a layout swap initialises the incoming page's host before the outgoing layout's host disposes. Whichever disposed first cleared the flag for the host still rendering. It is a count now, clamped so an unbalanced dispose cannot poison a later registration (#545). - Extra HTML attributes no longer crash a render.
BbDialog,BbSheet,BbPopover,BbHoverCard,BbAlertDialog,BbAlertDialogPortalandBbDataGridColumnVisibilityforwarded captured attributes to a component that could not accept them, so a singledata-testidthrewInvalidOperationException.BbColorPicker,BbDateRangePicker,BbTimePicker,BbThemeSwitcherandBbResponsiveNavContentnow render those attributes on their trigger instead of losing them; the context-only roots accept them and log once that they have nowhere to go. - Attributes that were accepted and then dropped.
BbTextarea,BbDatePickerandBbInputGroupButtonnow render them.BbFormFieldCheckboxapplies them in every orientation,BbFormSectionwhether or not it collapses, andBbFormFieldTimePickeron the picker rather than the surrounding field. - Dangling
aria-describedby.BbFieldDescriptionandBbFieldErrortook anIdand never rendered it, so everyBbFormField*control pointed itsaria-describedbyat an element that did not exist. - ARIA state attributes bound to booleans.
aria-expanded,aria-hidden,aria-selected,aria-checked,aria-disabledandaria-currentrendered with an empty value when true and vanished when false across Collapsible, Tabs, Menubar, DataGrid, Sidebar, Rating, Calendar and RangeSlider. They now render"true"and"false". - Keyboard traps and double activations. RadioGroup suppressed the default action of every key, so Tab could not leave the group. The Switch toggled twice per Space press, and a DropdownMenu trigger using
AsChildopened and closed again on one Enter. - ToggleGroup semantics and focus. Single-select groups are a
radiogroupofradioitems reportingaria-checked, not buttons reportingaria-pressed. Arrow keys move from the item that actually holds focus, and items remove themselves from the group when they are removed from the page. - HoverCard. Moving the pointer from the trigger onto the card no longer closes it, and
data-sideis written in lower case so the slide-in animation matches. - NavigationMenu triggers each register their own slot; every one of them previously overwrote the last.
- Parameters that did nothing now work:
BbSelect.Open/OpenChanged,BbCommand.Disabled,BbCommandVirtualizedGroup.LazyLoadBatchSize,BbSlider.OrientationandBbRangeSlider.Orientation,BbResizablePanel.Collapsible,BbToastProvider.MaxToasts,BbDataGridHierarchyColumn.IndentSize,BbRadialBarChart.EndAngle,ColoronBbPieandBbFunnel,BbSidebarMenuAction.ShowOnHover, the--sidebar-width-mobiletoken, andBbFillinsideBbLineandBbRadar. Text and numeric inputs now push configuration changes to the browser after the first render. BbNativeSelectwith an enum. Choosing an option reported the enum's default, because the conversion went throughConvert.ChangeType. AValueset before the user picked anything is also shown as selected.- Charts sharing one colour.
ChartConfig.GetColorreturned--chart-1for any key the config did not name, so a partially configured chart drew every remaining series the same colour. It returnsnullnow and the chart palette cycles. - DataGrid grouping with
Virtualizeand anItemsProviderrenders rows when aGroupedItemsProvideris supplied, instead of nothing at all. - Inline styles are merged, not replaced. A consumer
styleattribute onBbScrollArea,BbAspectRatio,BbSkeletonand the Command group/separator overwrote the component's own positioning and sizing. BbQuantityStepperandBbNumericInputno longer wrap past the end of the numeric range: ArrowUp atint.MaxValueused to land onMin.- Native
<dialog>withoutshowModal()falls back to the JavaScript strategy rather than rendering a dialog with no modal behaviour. - Overlay options survive either registration order.
AddBlazorBlueprintPrimitives(configureOverlays)beforeAddBlazorBlueprintComponents()silently lost the configuration;AddBlazorBlueprintComponentsalso takesconfigureOverlaysdirectly now. - Hard-coded English in the Sortable announcements, the DataGrid column-visibility menu and filter buttons, and the FilterBuilder Apply/Clear buttons and group labels now goes through
IBbLocalizer. - Primitives on their own.
primitives.cssdefines the utility classes the primitives render, so a Primitives-only app no longer shows Sortable's screen-reader text as body copy or gets a Menubar overlay with no size. - Escape cancels a DashboardGrid pointer drag, which it had only ever done for a keyboard drag.
- Resizable panels can be resized from the keyboard. Each handle is a
role="separator"witharia-orientation,aria-controlsandaria-value*reporting the panel in front of it, a localizedaria-label(Resizable.ResizeHandle, overridable per handle withAriaLabel) and a tab stop. Arrows move 5%, Page Up/Down 20%, Home/End go to the minimum and maximum. It was a plaindivwith a pointer handler, while the documentation described all of the above. InputConverter<T>parses enums. It went throughConvert.ChangeType, which cannot produce one, so an enum-typed input fell through to "no converter registered".- The Dialog and Sheet primitive roots unsubscribe from their context. Both had a
Dispose()the component never declaredIDisposablefor, so Blazor never called it. - NavigationMenu triggers unregister. The trigger list is keyed by the trigger rather than by a position, so a trigger removed from the page leaves with its entry and arrow-key navigation stops stepping onto a button that is no longer there.
- More inline styles merged rather than replaced.
BbResizablePanelGroup,BbResizablePanel,BbChartBase,BbDashboardGrid,BbDropdownMenuContent, and the Menubar content and DashboardWidget primitives let a consumerstyleoverwrite their flex sizing, grid template ordisplay: none. - Documentation corrections.
BbTooltipTrigger.AsChildis documented as defaulting tofalse; the Tooltip, Dialog, Message, DataView, MultiSelect, CopyText and Carousel snippets compile and do what they claim; every localization example useslocalizer.Set(...)rather than an options object that does not exist; candlestick click values are documented as[index, open, close, low, high]; the Resizable demo describes the accessibility it has rather than the accessibility it does not;THEMING.mdexplains that:root { --font-sans }overridesThemeFont; and the WebAssembly demo host loadsthemes.csslike the other two. - Modal keyboard containment. Tab and Shift+Tab enter the trapped controls when initial focus is on a container or non-tabbable listbox, preventing WebKit from moving focus behind a Select bottom sheet. Empty modals retain focus on their container.
- Drawer focus restoration. Closing a drawer with Escape, its close button, the backdrop or a dismiss gesture returns focus to the trigger that opened it, including composed triggers. Navigating away does not restore focus into the departing page.
- Menu demo documentation. Remove empty secondary API headings from Dropdown Menu, Context Menu and Menubar; their references remain in the final API section.
- Scheduler vertical scrolling. Demo schedules now include all 24 hours and initially scroll to 8 AM, instead of omitting hours outside short example ranges. The scroll region fits the viewport, retains sticky day headings and can receive keyboard focus.
InitialScrollHoursets the initial display-zone hour without changing the rendered range or resetting scrolling on later renders. - Cached core scripts. Revise both the core JavaScript entry URL and its relative dependency URLs so stale sidebar exports cannot disable an upgraded demo circuit.
- Demo sidebar sizing. Restore the standard sidebar width and align v4 badges at the trailing edge of each component row. Every badge sits on one vertical line, in the sidebar's own right gutter: a sub-menu insets both edges and a nested one insets again, so the right inset is dropped at each level. Long labels stay on one line and end in an ellipsis, with the full name on the row's
title. - Mobile Shop recipe and focused component demos. Move the combined shopping experience into Recipes with separate product browsing, cart editing and account screens. App Bar, Bottom Navigation, Notification Badge, Quantity Stepper and Section Header each have their own demo. Motion, Height Animation, Selection Indicator, Page Transition, Screen Transition and Render State Provider also have separate pages. The previous mobile-controls URL remains a recipe alias.
- Scheduler repeat editor. Daily, monthly and yearly choices no longer ask for an interval. Weekly events offer labelled weekday checkboxes, restore saved selections and require at least one day. Monthly/yearly repeats use the start date. Replace the raw custom-rule input with plain-language preservation of existing advanced schedules, which are kept until explicitly replaced. Optional occurrence limits remain available.
- Demo documentation review. Restore the original main-menu order while keeping Components and related pages alphabetized. Standardize accessibility and keyboard guidance across component demos, fill missing API references, consolidate stray API cards beneath one heading, and correct stale Tooltip placement and FieldError collection types. New APIs carry
v4badges; established components such as Sortable do not gain a new-component sidebar badge. - TimeInput selectors. Use
BbSelectfor AM/PM both inline and in the clock popup, and for the popup’s numeric choices. Preselected values display correctly, and period changes preserve minutes and obey read-only/disabled state. - Scheduler navigation and editor. Center the date heading and group Previous, Today and Next together. Today navigates to the current date in the schedule's time zone while preserving the view and week-start choice. Clicking the editor backdrop keeps the draft open.
- Sidebar demo organization. Place floating pill navigation and animated selection examples alongside the collapse/navigation examples, each with its code snippet. Keep accessibility guidance after the examples and the new component APIs in the final API reference.
- TreeSelect keyboard actions. Space expands/collapses the focused branch without selecting it. Enter selects and closes in single mode, including the current selection; in checkbox mode, it checks/unchecks the item while keeping the popup open. LeafOnly selection rules and standalone TreeView keyboard behavior are preserved. Revised the primitive bundle and tree-keyboard asset URLs so cached scripts do not retain the previous key mapping.
- Generate Tailwind CSS before static asset discovery, fingerprinting and compression, including CSS missing on a clean checkout. This prevents stale asset metadata and empty compressed stylesheet responses in the demo hosts. Added a check for all hosts and optional live HTTP delivery.
- DataGrid editors preserve column widths. Inline
BbInputGroupsave/cancel actions fit inside the existing cell without widening the column or moving neighbouring columns. - Hierarchy pickers match the standard Bb controls. TreeSelect and Cascader use consistent chevrons, search rows and focus styling. Parent selection reaches collapsed/filtered descendants, and closing a TreeSelect preserves its search through the exit animation to avoid flicker.
- Scheduler event layout and scrolling. Appointment content is top aligned with spacing between overlapping events. Native component scrollbars follow the light/dark theme.
- Picker focus and overlay dismissal. Date pickers restore focus after selection, Enter opens a Select without a second native click closing it, and overlay exit waits ignore unrelated child animations.
- A vertical slider now lays out vertically. Both
BbSliderandBbRangeSliderput the track in a row container, and the track carriesgrow, which follows the flex main axis — so a slider asked for vertically stretched its 8px track across the full width of its container instead. Vertical sliders stack on the cross axis, and the fill is pinned to the bottom edge it grows from.BbSlider.Orientationwas read nowhere at all: the value reached the primitive, which already handled vertical, but the styled layer drew the result horizontally either way.BbRangeSlidershows its value tooltips to the right of a vertical track and its tick marks to the left, so the two cannot overlap. Give the container a height; the slider fills it. - Consumer
group-*andpeer-*variants reach the library again. Tailwind resolves those variants against a literal marker class, and a consumer's own build emits:where(.group)— never:where(.bb\:group), because it never sees thebbprefix. Every marker the library shipped carried the prefix alone, across 30 components, so anything a consumer hooked onto one silently matched nothing: a collapsedBbSidebarkept the labels and chevronsgroup-data-[collapsible=icon]:hiddenwas meant to hide, a label next to a disabledBbCheckboxorBbSwitchnever dimmed, and aBbCollapsibleTriggerchevron never rotated — all visible in the demo's own pages.ClassNames.cnnow carries the bare twin of any marker it keeps, named ones (peer/menu-button) included; agroup-*variant is an ordinary utility and still ships prefixed only, which is what #496 was about. A marker handed straight to a component'sClassparameter is the one path the merge cannot see, because a primitive concatenatesClassverbatim —BbDataGrid's header row did exactly that, and a convention test now fails on it.
Documentation and Packaging
- The
BbPortalHostsetup snippets carry their@using, and the missing-host warning checks it first. An unresolved Razor tag is not a build error, so a missing using turned<BbPortalHost />into a literal<bbportalhost>element and every overlay silently stopped rendering. Alongside the namespace move above, both READMEs now show the using in the layout snippet and name the symptom, and the warning walks through the@usingcheck — including the v3 namespace — before anything about render modes (#545). - Getting Started dialog snippets name components that exist. The page showed
<Dialog>,<DialogTrigger>and the rest under@using BlazorBlueprint.Components.BbDialog, which is not a namespace — every tag wasBb-prefixed and the using isBlazorBlueprint.Components. The primitive snippet mixed a fully qualified root with unqualified children; it now shows one@usingand notes that primitives and components share type names, so a file imports one namespace or fully qualifies the tags. Two Next Steps links pointed at routes that no longer exist. - Previous/next component navigation in the demo header. Two buttons beside the sidebar trigger step through
ComponentCatalog.DemoPages, the same flattened list the sidebar, the component homepage and the command search already walk, so "next" always means the next row the reader can see. They disable at the ends and render nothing off that list — on the homepage, a guide or a primitive page there is no meaningful neighbour to offer. - Slider gains a vertical example with a live demo, a copyable snippet and an
OrientationAPI reference entry. The Controlled Sidebar demo wraps its menu labels in aspan, which is what icon mode hides. - More snippets naming components that do not exist. The Lucide, Heroicons, Feather and Font Awesome pages showed
<Button>where the live example beside them used<BbButton>, and a CopyText note asked for<PortalHost />rather than<BbPortalHost />. - Dead demo links and a broken image. The Primitives index advertised a Combobox primitive; there is no such primitive, only the styled
BbCombobox. The Sidebar demo's avatarSourcewas relative to the page rather than the base path, so it 404'd and always fell back to the initial. The ResponsiveNav demo's/docsand/loginlinks now use the#placeholder the rest of that file already uses. - Add permanent DataGrid editing, Scheduler, TreeSelect, Cascader and upload lifecycle demos with code examples, accessibility notes and API references. New pages appear in the sidebar, command search and component catalog; editing demos cover several input types, validation recovery and ordering after saves. See PR #536.
- Complete README acknowledgments and include library licenses and upstream notices in all six NuGet packages. Bundle browser-library notices as static assets, restore Floating UI and tw-animate-css license headers, and retain ECharts subcomponent notices.
- Correct Lucide and Font Awesome package license expressions to account for their embedded artwork, while retaining MIT for their C# wrappers.
Performance
- Paged DataGrid queries fetch the requested page. For
IQueryablesources without client-side search, grouping, hierarchy or row virtualization, counting andSkip/Takestay on the query provider. Full-data CSV exports run the filtered, sorted query on demand; keep its provider alive until export.ItemsProviderremains the asynchronous option for remote data, and formatted search and grouping retain their full-data behavior. - Command search shares filtered results and an item-index lookup. Visible items no longer each rebuild and scan the whole filtered list. Search, filter and item changes invalidate the cache; callback-only changes do not.
- Slider, RangeSlider and ColorPicker drag feedback is immediate in the browser. Live .NET updates are coalesced to the latest distinct value, normally no more than every 50 ms, with one callback in flight. Release and cancellation flush the final value. RangeSlider captures pointers locally; Rating hover updates on entering an icon instead of every mouse move. Public component parameters are unchanged.
- TreeView search indexes parents and visible nodes. Matching siblings share ancestor lookups, rendering no longer repeats descendant searches, and cached lazy children remain searchable after parent renders.
September 16, 2026
Added
- Tables in
BbRichTextEditor— TheFulltoolbar gains a table button. Outside a table it opens a size picker (up to 6 × 6); with the caret inside a table the same button offers insert row above/below, insert column left/right, delete row, delete column and delete table. The same actions are on the component reference:InsertTableAsync(rows, columns),InsertRowAboveAsync,InsertRowBelowAsync,InsertColumnLeftAsync,InsertColumnRightAsync,DeleteRowAsync,DeleteColumnAsyncandDeleteTableAsync. This is Quill 2's own built-in table module, so there is no new script to load; the HTML output is a plain<table>, cell borders follow--border, and the module's Tab, Enter and Backspace behaviour inside cells applies. Cell merging and column resizing are not part of Quill's module and are not offered. NewRichTextEditor.Table*,InsertRow*,InsertColumn*andDelete*localizer keys carry the labels. - Undo/redo, checklist, inline code, alignment, text colour, highlight and images in
BbRichTextEditor.Standardgains undo/redo buttons (Quill's history module,userOnlyso a programmaticValueupdate is never undoable; the buttons grey out when there is nothing to do, andTextChangeEventArgsreportsCanUndo/CanRedo) and a checklist toggle.Fullalso gains inline code, an alignment menu, text colour and highlight palettes, and an image button. Alignment uses Quill's style attributor so the output carriestext-aligninline rather thanql-align-*classes that mean nothing outside Quill's stylesheet; colour and highlight are inline styles too. Checklist state round-trips: Quill writes<li data-list="checked">but only readsdata-checkedon the list back in, so the interop adds a clipboard matcher for the item and the sanitizer keepsdata-list. Images go through the newImageUploaderparameter — every picked, dropped or pasted file is streamed to it and it returns the URL to embed, ornullto reject;MaxImageSize(10 MB) caps what is sent. Without a handler, images embed as data URLs, as Quill does on its own, and the sanitizer now letsdata:image/*through on<img src>only.UndoAsync,RedoAsyncandInsertImageAsync(url)join the public methods;EditorImageUploadis the new public type.
Changed
table,code,align,color,backgroundandimageare now registered formats inBbRichTextEditor. Pasted or bound HTML that carries them keeps them, where it used to flatten to plain paragraphs and text.HtmlSanitizer's defaults already allow the table elements and the inline styles involved.- Quill is pinned to 2.0.3 in the demo and setup notes (
[email protected]instead of the floatingquill@2tag), and the interop no longer carries Quill 1 fallbacks forgetSemanticHTML. The editor has required Quill 2 since it was rewritten for it; the fallbacks only hid a wrong script version behind subtly different HTML. - BREAKING — every Tailwind utility in
blazorblueprint.cssis now prefixedbb:— #501, fixing #496. The prebuilt stylesheet wrote its utilities into Tailwind'sutilitiescascade layer, the same layer a consumer's own build writes into. Layer names are global to the document, so two builds emitting the same class name were ordered by which<link>came second, not by Tailwind's sort order. A consumer'ssm:grid-cols-2 md:grid-cols-4collapsed when the library loaded after their stylesheet; the library's ownhidden sm:flexcollapsed when it loaded before. No load order fixed both, and the demo had been shipping in the broken arrangement.
Fixed
- Navigating away from an open overlay no longer logs
overlay: close callback failed. Clicking a link while a select, popover or menu was open closed the overlay and unloaded the page in the same gesture. The close asked the browser for aJsOnClosedcallback once the exit animation finished; the navigation disposed the component before the animation ended, and because the portal already counted itself closed, disposal skipped its own close call and released the .NET reference the callback was about to use. Nothing broke — the callback only unregisters content that disposal had already unregistered — but every such navigation put aSystem.ArgumentException: There is no tracked objectin the console. The portal now remembers that a callback is outstanding and sends one more close on disposal, which cancels the pending one on the browser side before the reference goes away. Introduced with the one-round-trip close in 4.0.0-beta.1.
September 15, 2026
Added
JsModules.TryGetLoadedandPrimitiveModules.TryGetLoaded— get an already-imported module without awaiting. For callers that must issue interop from a synchronous pass, where awaiting the import would put the call a round trip behind the render it needs to travel with. Returningfalseis "not yet", not an error: the caller falls back to the awaited path, which pays one round trip and warms the cache for every call after it.BbFloatingPortal.SideElementId— names the element that carries the resolveddata-side, written by JS as soon as the overlay is positioned and kept current when a scroll flips it.BbFloatingPortal.Keyboard(FloatingKeyboardOptions,FloatingKeyboardKind) — declares listbox or menu key handling to be wired inside the open call and released inside the close.BbFloatingPortal.ScrollToCurrentInandScrollToCurrentSelector, surfaced on both layers ofBbPopoverContentasScrollToSelectedandScrollToSelectedSelector— scroll the chosen item into view each time the overlay opens, before it is revealed.
Changed
- Moving the pointer over a command or combobox list no longer talks to the server per pixel. Every
BbCommandItembound@onmouseenterand@onmousemove, so on Blazor Server every pixel of pointer travel was a circuit message and every item crossed re-rendered the whole list. Themousemoveexisted only to tell a real hover from the list scrolling under a stationary pointer during keyboard navigation. The browser now answers that itself: one delegated listener per list (elementUtils.observeHover) and one call to .NET per item the pointer genuinely moves onto. Measured across five combobox items at one pixel a step: 157 pointer steps → 11 messages, where it was one per step.BbCommandItemalso gains aShouldRender, so a focus move re-renders the two items it touches rather than all of them. - Scrolling an infinite-scroll list no longer costs a round trip per scroll event.
BbCommandList,BbSelectContent,BbMultiSelectandBbDataViewbound@onscrolland then awaitedisNearBottomin JS — around sixty circuit messages a second while the wheel turned, each waiting a further round trip for the answer.elementUtils.observeNearBottomwatches the element in the browser and calls .NET exactly once when the scroll position enters the near-bottom zone, re-arming when it leaves or when the content grows. - Focus happens inside the open and close calls, not a round trip after.
BbComboboxandBbMultiSelectfocused their search box from the popover's ready callback — a round trip after the content rendered, then a 50ms sleep, then another round trip forFocusAsync. The newBbPopoverContent.AutoFocusId(andBbFloatingPortal.AutoFocusId) focuses the element one frame after the reveal, inside the call that positions the overlay.BbCommandInputgains anIdso an owner can name it. On the way out, Select, Popover and DropdownMenu awaitedFocusManager.RestoreFocusafter their close render on every Escape and every selection;RestoreFocusToIdandRestoreFocusOnCloseride along withoverlay.closeand the browser puts focus back on the trigger itself.BbNavigationMenuTrigger's ArrowDown no longer sleeps 50ms before focusing the first item. BbPopoverContentonly asks for the portal's ready callback when a consumer setOnContentReady. Nothing in the library listens toContext.OnContentReady, so the callback — ack-gated, and a re-render of its receiver — was a round trip spent notifying no one.- An overlay opens in one circuit round trip, and closes in one. Measured on a real deployment at a ~90ms round trip, opening a
BbSelecton 3.14.1 cost 11 sequential round trips — 1.6 seconds, and 5.5 seconds on a 500ms circuit. 4.0.0-beta.1 cut that to 5. It is now 1, and the arrow keys and hover cost none at all. - JavaScript owns the overlay's position, its side, and a listbox's highlight. These were all C#-rendered, and each one was a render batch — a round trip — to record a measurement the browser had already made.
- Keyboard wiring happens inside the call that opens the overlay. The new
FloatingKeyboardOptionscovers both a listbox and a menu;BbFloatingPortalattaches the handlers on the way open and releases them on the way closed, in the same calls that position and hide the element.BbSelectContentandBbDropdownMenuContentdeclare what they need rather than wiring it from a ready callback, which used to leave the overlay on screen and deaf to the arrow keys for a further round trip. - The command search box shows focus as a band, not a box inside a box.
BbCommandInputdrew a focus ring around the<input>, which sat inside a bordered row, which sat inside a bordered popover — three nested outlines with gaps between them. It was not an occasional look either: a combobox focuses its search field every time it opens, so the ring was on screen the whole time the dropdown was, marking the only element that could possibly have focus. BbDropdownMenuContentno longer callsmatchTriggerWidth.MatchAnchorWidthalready asks Floating UI's size middleware for the same width on every position pass, andautoUpdatere-runs that on resize. The separate call was a second writer of one style, bought with an awaited interop call that also allocated a JS object reference to dispose later.BbTooltipContentandBbHoverCardContentno longer ask for the portal's ready callback. Both handlers were empty, and on Server the callback is ack-gated, so asking for it cost a round trip to do nothing.
Fixed
- No
async voidhandler can take the circuit down. Eight fire-and-forget handlers — close timers, debounce, state-change and location-change subscribers — caught one or two exceptions and let the rest escape. Nothing awaits anasync voidmethod, so an escaping exception has no caller to reach and Blazor Server treats it as fatal. The sidebar's scroll-to-top was one of these. Every one now catches everything; each does best-effort work and none of it is worth a dead circuit. - A stale cached JavaScript module fails loudly, and a new release is a new URL. A consumer behind a CDN with a long browser-cache TTL deployed a new build and got a four-hour-old
sidebar.jsunder the new bundle;sidebar.initializewas not a function and every circuit died with nothing to say why. The C# import of each bundle now carries the library version as a query (PrimitiveModules.ModuleUrl, the newComponentModules.CoreUrl, built byJsModules.Versioned), so a cached entry file is never a valid answer for a new release. And each bundle asserts one export it depends on from every module it imports, at load, throwing an error that names the stale file and says what to do — the import rejects, consumers catch it and degrade, and the console says which file is old. - Navigating between pages no longer kills the circuit.
BbSidebarInsetscrolls the main area to the top on every navigation, and it calledscrollToTop— the name the function had before the five common modules were bundled intobb-components-core.js. The bundle re-exports each module under its file name in camelCase, so the bare identifier resolved to nothing and every client-side navigation threwCould not find 'scrollToTop'. The handler isasync void, so the exception had nowhere to go and took the connection down with it; a reload fixed it until the next link click. - A closing overlay no longer blinks back into view before it disappears. The dropdown faded out, snapped back to full opacity for a fifth of a second, and only then vanished.
BbComboboxreopens scrolled to the chosen item. Pick a country a long way down the list, close the combobox, open it again, and it came back at the top of the list with the selection out of sight.BbSelecthas always scrolled to its selection; the combobox never had the behaviour at all.BbComboboxno longer discards a paged list when it closes. Closing it told the consumer the search had been cleared even when nothing had been typed, which a paged list correctly reads as "reload your first page". Scroll in seven pages, pick an item from the last of them, and closing threw all seven away: reopening showed page one, and the chosen item was no longer in the list at all. The notification now fires only when there is a search to clear.- A closing overlay's exit animation is no longer cut short by its own unmount. Content that is unmounted on close (
ForceMount="false") has to survive long enough to animate, so the unmount can no longer share the close's render. JS reports back once the animation has finished and the element is hidden, and the unmount happens then — one round trip later, behind an overlay the user already cannot see. Content that stays mounted needs no callback at all.
Known
- Popover and dropdown menu still open in two round trips, not one. The second is a portal-host flush: the content's registration and its root's unconditional re-render both reach the host in the same render batch, and the host — unable to tell a same-batch refresh from a genuine mid-cycle update (#418) — defers a flush that lands one round trip after the overlay is already on screen. Removing the content's own open-side
StateHasChangeddid not clear it. The fix needs the host to know where a render batch ends, which Blazor does not expose; parked with that analysis.
September 14, 2026
Added
BbCopyTextcan take an asynchronous value — #466, the deferred half of #453.ValueFuncAsyncis for text that has to be fetched or computed.Valuestill wins when non-empty, thenValueFunc, then this.BbCopyTextreports a failed copy —OnCopyFailedcarries aCopyTextFailureofRefused(the browser rejected the write) orNoValue(nothing to copy). Failure used to be entirely invisible, which is exactly what let the bug above go unnoticed for so long.
Changed
- Theme and sidebar start up in one call each.
ThemeService.InitializeAsyncmade two to four separate calls — read localStorage, maybe clear a stale entry, maybe ask the OS for its dark-mode preference, apply the result — andBbSidebarProvidermade two. Every one was a circuit round trip, on every page load, before the page could settle into the right state. None of the decisions between them need the server: they are reads of a cookie, localStorage and a media query. - The five modules that load on nearly every page ship as one file.
bb-components-core.jsre-exportstheme,sidebar,sidebar-inset,text-inputandcomposition-guard, addressed astextInput.initializein the same style as the primitives bundle. - A data grid attaches its row key and click handlers once, not once per row. Every
BbDataGridRowregistered its own listeners in itsOnAfterRenderAsync, and disposed them one by one on teardown — one interop call, and so one circuit round trip, per row in each direction. The cost scaled with row count, which is why a large grid felt slow and a demo page never did. - A component's JavaScript module is imported once per circuit, not once per component. Every component cached its module reference in an instance field, so thirteen
BbInputs on a page issued thirteenimportcalls for the same already-loaded file — and on Blazor Server each one is a network round trip. The cost scaled with how many controls a page had, which is why it showed up on dense admin forms and not in a demo. - A select prepares its listbox in one call. Scrolling the selected option into view and attaching the keyboard handler were two separately awaited calls, so two more circuit round trips on every open, while the user waited for the list to become usable.
select.openListboxdoes both. - Opening an overlay costs one interop call, not five. Every
InvokeAsyncfrom C# on Blazor Server is a message the server posts to the browser and then awaits, so it costs a network round trip — paid on every open, forever, not just the first. - Breaking —
JsOnClickOutsideandJsOnEscapeKeyare gone fromBbPopoverContent,BbSelectContentandBbDropdownMenuContent. Both were[JSInvokable]and[EditorBrowsable(Never)]— callable only from the library's own JavaScript. Dismissal now arrives throughBbFloatingPortal.OnDismiss. - Escape dismisses the topmost overlay, not every open one. A popover opened inside a dialog used to close the dialog on the first press — sometimes both at once — because each overlay registered its own document-level
keydownlistener and none of them knew about the others. - Removed —
onClickOutsideandonEscapeKeyinclick-outside.js. Both are superseded and both caused a bug fixed in this release: the first went stale when Blazor replaced the element and had no exemption for nested portals, the second was the second document listener that broke Escape ordering. Neither has a caller left. UseonClickOutsideByIdsandescapeKeydown.initialize. BbFloatingPortalno longer gives up on the portal host after 500ms.MountPortalAsyncraced the host's render signal against aTask.Delay(500). The signal is never lost — it arrives exactly one network round trip after the portal registers, because the host only reachesOnAfterRenderAsynconce the browser has acknowledged the render batch. Measured, the wait tracks round-trip time 1:1: 104ms at a 100ms round trip, 305ms at 300ms, 488ms at 480ms, and at 600ms every single open times out. That is what produced the strayPortalRenderTimeoutwarnings — not a lost signal, a fixed budget for a variable cost.- The
execCommandclipboard fallback no longer runs for every failure — it ran whenevernavigator.clipboard.writeTextthrew, which meant an expired user activation quietly fell through to a path that cannot rescue one either, and success was reported regardless. It now runs only for the insecure-context case it was written for, where the Clipboard API is absent altogether.
September 13, 2026
Changed
- BREAKING —
BbTooltipTrigger.AsChildnow defaults tofalse— #428, the deferred half of #425. It defaulted totrue, where the trigger renders no element and no handlers and only cascades aTriggerContextfor the child to consume.BbButtonconsumes it;LucideIconand plain markup do not. So the most natural thing a consumer writes — a bare icon in a trigger — silently did nothing, and the usage that worked required knowing about an opt-out. That is the wrong way round for a component library, and it was reported from the field.
Added
- Charts report clicks — #480, requested by @netclectic for drilling down into the underlying data. Every chart type gains
OnDataPointClickandOnChartClickthroughBbChartBase, so bar, line, area, pie, scatter, candlestick and the rest all get them at once. BbFileUploadaccepts pasted files — #485, requested by @HugoVG, whose point was that not everyone wants to drag onto a dropzone. Copy a file in your file manager, or take a screenshot to the clipboard, focus the upload and press Ctrl+V.- A way to apply the saved theme before the first paint — #477, reported by @andrewbabbittdev. The theme lives in
localStorage, so a prerendered or statically rendered page cannot know it: the server renders the default and the saved theme is applied once Blazor has started. A user who chose dark mode got a flash of light first, on every load. There was no built-in answer, and there is no C#-only one — the preference is not available to the server at render time.
Fixed
- The last components showing the browser's own focus outline — final tranche of #459 on
develop.BbCarouselNext,BbCarouselPrevious,BbRating,BbMarker,BbSidebarRailandBbFileUpload. BbInputGroupAddonno longer has a click handler that does nothing — found while finishing #459. The addon rendered@onclick="HandleClick", and the handler was a stub: a comment reading "In a real implementation, we'd use ElementReference and JSInterop" followed byawait Task.CompletedTask. Its documentation claimed it focused the sibling input like a native label. It never did.- Four more components showed the browser's own focus outline — third tranche of #459.
BbDateTimePicker,BbFormWizard,BbThemeSwitcherandBbColorPicker.
September 12, 2026
Changed
- BREAKING —
BbDrawerTriggerandBbDrawerClosenow render a real<button>— #507, found while working #459. Both were a bare<div @onclick>with notabindex, noroleand no keyboard handler. They worked when the child happened to be focusable — which is what the demos did, wrapping aBbButton, so the common path was fine and this went unreported. Pass anything that is not itself focusable, which the API placed no constraint on, and the trigger was unreachable by keyboard and not exposed as a control at all: a WCAG 2.1.1 failure in a shape the component invited, failing silently and passing any mouse test.
Fixed
BbCopyText's tooltip reappeared and stuck when you came back to the browser tab — #506, reported by @garrenf with a video. The lastBbCopyTextyou clicked would show its tooltip again on returning to the tab, and nothing dismissed it: hovering the text and leaving again was the only way out.- Five more components showed the browser's own focus outline, and the focus-indicator allowlist is now empty — second tranche of #459, after #508.
BbBreadcrumbLink,BbAttachmentTrigger,BbCommandInput,BbCopyTextandBbResponsiveNavItems.