Razor Class Library providing BlazorBootstrapSelectSingle and BlazorBootstrapSelectMulti: Bootstrap 5–compatible, input-style single- and multi-select dropdowns. Behavior and styling follow the requirements specification below (§1–§12).
In short, this library provides dropdown controls that look and behave the same to the user, regardless if they are being asked to select zero, one, or multiple items.
Live Demo: See it in action here.
- .NET 10 (or compatible)
- Bootstrap 5 CSS — the host app must reference Bootstrap 5 CSS (e.g.
bootstrap.min.css). The components use Bootstrap form classes (form-control,form-label) and CSS variables (--bs-*). Note: Bootstrap CSS is required regardless of whether you use Blazor.Bootstrap or not; Blazor.Bootstrap does not bundle Bootstrap CSS. - No Blazor Bootstrap dependency — while orginally intended to be used with the Blazor.Bootstrap library, this library has no dependency on Blazor.Bootstrap. It works with any Blazor Web App that uses Bootstrap 5 CSS, whether or not Blazor.Bootstrap is also used elsewhere in the app.
Install from NuGet:
dotnet add package BlazorBootstrapCustomControls-
Add a project reference to
BlazorBootstrapCustomControls. -
Ensure the app loads Bootstrap 5 CSS before (or with) the app's own styles.
-
The RCL's
bootstrap-select.cssandbootstrap-select.jsare served from_content/BlazorBootstrapCustomControls/; reference them in the app (e.g. inApp.razororindex.html):<link href="_content/BlazorBootstrapCustomControls/css/bootstrap-select.css" rel="stylesheet" /> ... <script src="_content/BlazorBootstrapCustomControls/js/bootstrap-select.js"></script>
-
Add
@using BlazorBootstrapCustomControls.Components.Shared(or in_Imports.razor). -
Use the components:
<BlazorBootstrapSelectSingle TItem="MyItem" TValue="string" Data="@items" TextField="x => x.Name" ValueField="x => x.Id" PlaceholderText="-- Select --" @bind-Value="@selected" /> <BlazorBootstrapSelectMulti TItem="MyItem" TValue="string" Data="@items" TextField="x => x.Name" ValueField="x => x.Id" PlaceholderText="-- Select --" @bind-Value="@selectedMany" /> <!-- Convenience wrappers for string-only data --> <BlazorBootstrapSelectSingleString Data="@names" @bind-Value="@selectedName" /> <BlazorBootstrapSelectMultiString Data="@names" @bind-Value="@selectedNames" />
Note: The components do not add default outer spacing. Use standard Bootstrap utility classes (e.g.
class="mb-3") or app-level CSS to control layout spacing.
The core components are generic over both the data item and the selected value:
TItem: the type in yourDatacollection (e.g.,Person,SportOption).TValue: the type you want to bind to (e.g.,string,int,Guid).
This allows you to keep your domain objects intact and just provide selectors:
TextField: Func<TItem, string>— how to display each item in the UIValueField: Func<TItem, TValue>— the value to bind and compare
Example:
<BlazorBootstrapSelectSingle TItem="Person" TValue="int"
Data="@people"
TextField="p => p.FullName"
ValueField="p => p.Id"
@bind-Value="@selectedPersonId" />If your data is already IEnumerable<string>, you can skip selectors entirely:
<BlazorBootstrapSelectSingleString Data="@colors" @bind-Value="@selectedColor" />
<BlazorBootstrapSelectMultiString Data="@colors" @bind-Value="@selectedColors" />These wrappers simply wire TextField and ValueField to s => s for you.
When running BlazorBootstrapCustomControlsApp via dotnet watch run or F5, the app uses http://localhost:5175 and https://localhost:7240. If you rebuild while the app is still running, you may see "port is already in use" because the previous process is still holding those ports.
Avoiding the conflict:
- Stop before rebuild: Stop the running app (Ctrl+C in the
dotnet watchterminal, or Shift+F5 if debugging) before doing a full Rebuild. - Prefer Build over Rebuild: Use Build when possible;
dotnet watchalready rebuilds on changes. Use Rebuild only when you need a clean build.
If the port is already in use:
- Run
.\scripts\kill-dev-ports.ps1from the repo root to terminate processes listening on 5175 and 7240, then start the app again.
- Components:
BlazorBootstrapSelectSingle,BlazorBootstrapSelectMulti, shared base and markup. - Styles:
wwwroot/css/bootstrap-select.css— form-control–like input, dropdown, check-marks, focus, dark-mode–friendly variables. - Scripts:
wwwroot/js/bootstrap-select.js— keyboard navigation, click-outside, focus handling.
This section documents the complete functional and visual requirements that this library implements. The spec is Bootstrap compatible (Bootstrap 5 CSS variables, form patterns, and styling) and library-agnostic—it can be implemented with any Blazor dropdown or JS-based component.
| Component | Type | Bound value (generic API) | Description |
|---|---|---|---|
| SelectSingle | Single-select | TValue? |
One item selectable; re-selecting clears unless de-selection is prevented. |
| SelectMulti | Multi-select | TValue[]? |
Multiple items; re-selecting toggles; comma-separated in the input. |
Implementations are BlazorBootstrapSelectSingle / BlazorBootstrapSelectMulti with type parameters TItem (items in Data) and TValue (bound value). BlazorBootstrapSelectSingleString and BlazorBootstrapSelectMultiString fix both to string and omit TextField / ValueField (they use identity mapping).
Both families share: Label, PlaceholderText, Width, Disabled, ShowClearButton, and optional ValidationMessage (types follow TValue; see §9).
In this spec, control means the whole SelectSingle or SelectMulti component; input means the focusable value-display part of the control.
Implementations must support data binding (e.g. Value/ValueChanged or @bind-Value / @bind-Value:event or equivalent). Prefer C# in Razor over JavaScript when implementing; use JS only when necessary (e.g. keydown before Blazor can handle it, or focus in a portal).
Single and multi selects must behave the same except where noted:
- Same dropdown styling (border, radius, shadow, alignment).
- Same dropdown-arrow styling and placement on the right.
- Same list item layout: text plus right-aligned check-mark for selection.
- Same highlight behavior for focus and hover (blue).
- Same keyboard behavior for opening, moving, and (where applicable) selecting.
- Selection only on mouse click or Enter (while the item is highlighted); not on hover, arrow-key movement, or any other interaction.
Implementations must work within a Bootstrap 5 (or compatible) stylesheet:
- Colors: Use Bootstrap CSS variables:
--bs-primaryfor the highlight background;--bs-border-colorfor dropdown borders;--bs-secondaryor--bs-body-colorfor the dropdown arrow. - Forms: Labels and control sizing should match Bootstrap form conventions (e.g.
.form-labelfor labels; control height and border radius consistent with.form-control). - No Bootstrap JS: The spec does not require Bootstrap's JavaScript; the dropdown may be implemented with any UI library or custom code.
- Both controls: with focus in the input and the list closed, Down, Right, or Enter open the list. These keys do not close the list.
- When Down or Right opens the list, the first item is highlighted.
- When Enter opens the list, no item is highlighted initially.
- Down, Right, and Enter must not change the selected value(s) (e.g. must not cycle through options in the input).
- Both controls: with focus in the input, printable keys (single character keys, excluding Ctrl/Alt/Meta modifiers) move the keyboard highlight to the first list item whose display text starts with the current type-ahead string (case-insensitive prefix match).
- List closed: the first such key opens the list and applies the same matching rules (highlight only; no selection until Enter or mouse click, §4.4 / §5).
- List open: each printable key appends to a short buffer; the highlight updates to the first prefix match. If the longer string matches nothing, the implementation retries using only the latest character (so a mistyped letter can still jump to a valid prefix).
- Buffer reset: the type-ahead string is cleared when it goes idle for about 700 ms between keys, when the list closes, or when the list is opened by clicking the input (so mouse-open starts with a fresh buffer).
- Selection: typing alone does not change the bound value; the user still Enters or clicks to confirm (§4.4, §5).
- Both controls: when the list is open and the highlight is on the first item, ArrowUp and ArrowLeft close the list.
- When the list is open (no wrap):
- ArrowDown and ArrowRight: move highlight to the next item; if on the last item, do nothing.
- ArrowUp and ArrowLeft: move highlight to the previous item; when on the first item, they close the list (§4.2) instead of moving.
- Right behaves identically to Down; Left behaves identically to Up.
- Arrow keys only move the highlight; they do not select. Selection requires Enter (§4.4) or mouse click (§5).
- Highlight must use the same blue style as hover (see §7).
- Both controls: moving with these arrows must not change the selection (must not add, remove, or change the selected value) until the user presses Enter or clicks.
- Enter on the focused/highlighted list item:
- If the item is not selected: selects it. This is one of the two ways to select (the other is mouse click, §5).
- SelectSingle: select and close the list.
- SelectMulti: select and leave the list open.
- If the item is already selected: deselects it, with the same behavior as clicking the selected item (§5):
- SelectSingle: when de-selection is allowed (
AllowDeselecttrue), deselect (clear value) and close; when de-selection is prevented (AllowDeselectfalse), no-op. - SelectMulti: deselect (toggle); list stays open.
- SelectSingle: when de-selection is allowed (
- If the item is not selected: selects it. This is one of the two ways to select (the other is mouse click, §5).
- Escape: must close the list.
- Tab / Shift+Tab: move focus away and close the list.
- Both controls: click on the input toggles the list (open if closed, close if open).
- Selection occurs only when: (1) the list item is clicked, or (2) Enter is pressed while the item is highlighted (§4.4). Hover and arrow-key movement do not select.
- Click list item (not selected): select it.
- SelectSingle: select and close the list.
- SelectMulti: select and leave the list open.
- Click list item (already selected), or Enter on the selected item (§4.4): deselect it.
- SelectSingle: when de-selection is allowed (
AllowDeselecttrue), deselect (clear value) and close; when de-selection is prevented (AllowDeselectfalse), no-op. - SelectMulti: deselect (toggle); list stays open.
- SelectSingle: when de-selection is allowed (
- Hover over a list item: show blue highlight only; no selection.
- In the input, show selected items as a comma-separated list (e.g.
"Cross-Country, Swimming, Track & Field"). - Delimiter: comma plus space:
", ".
- Selected items remain in the list (not removed or hidden).
- List stays open after each selection so the user can pick several in a row.
- Selection is shown only by a right-aligned check-mark (✓-style), not by a background color.
- Check-mark: small,
rotate(45deg)-style "check" (e.g.0.5em × 1em, border-based). Check-mark color: the same as the list item's text color (e.g.currentColoror inherit). - Selected rows must not have a blue (or other) highlight; they stay neutral; selection = check-mark only.
- Focus (keyboard) and hover (mouse):
- Background:
var(--bs-primary, #0d6efd)(or theme primary). - Text and check-mark: both white while the item is highlighted.
- Background:
- When focus or hover leaves the item, text and check-mark are restored to their normal colors (text = default; check-mark = same as text per §7.1).
- This applies to:
- Items that are focused via keyboard (e.g.
:focus/:focus-withinor a custom class used for arrow-key highlight). - Items under
:hover.
- Items that are focused via keyboard (e.g.
- Selected + hover: still show blue highlight with white text and white check-mark (same as non-selected hover); restore on leave.
- The same blue style must be used for:
- Keyboard-driven focus (including arrow-key movement in the list).
- Mouse hover.
- No different colors or styles for "keyboard focus" vs "hover."
- A focus ring (e.g. Bootstrap focus styles: border highlight, box-shadow) must remain on the input for the whole time the user is interacting with the control—whether they are interacting with the input itself or with the open dropdown list.
- When the list is open, the user may hover over list items or move the keyboard highlight (e.g. Down/Up arrow) within the list. In all cases, the input retains focus and thus the focus ring; list items receive highlight only (blue background per §7.2–§7.3), not DOM focus.
- The input must not lose focus when the list opens or when the user hovers or arrows within the list.
- Single-select (custom item template):
- Structure: a container (e.g. with
data-value) for the display text and an optional.check-markwhen selected. Right padding (~40px) for the check-mark so the text does not overlap.
- Structure: a container (e.g. with
- When no custom item template is used (or when a template is not desired):
- Add right padding for the check-mark.
- Use CSS
::after(or equivalent) to draw the check-mark on the selected state (e.g.[aria-selected="true"]or the control's selected state).
- Same for single and multi: border
var(--bs-border-color, #dee2e6),border-radiuscompatible with Bootstrap (e.g.0.375rem),box-shadowfor elevation (e.g.0 16px 48px rgba(0,0,0,0.175)). - Vertical offset (e.g.
margin-top: 4px) so the list aligns with the control and has room for the focus ring. - Dynamic width: The dropdown sizes to fit its content (largest item + padding + check-mark space), with a minimum width of
var(--bs-dropdown-min-width, 10rem)to avoid tiny dropdowns.
- Same icon and color for single and multi:
var(--bs-secondary-color, #6c757d)orvar(--bs-body-color). - Arrow on the right, not hidden or overlapped.
- Uses Bootstrap's
--bs-form-select-bg-imgvariable for dark mode support.
- When the displayed text (single value or comma‑separated multi values) exceeds the input width, it is truncated with an ellipsis (
...). The control does not grow vertically by default. - An optional
AutoExpandVerticallyparameter (see §9) allows the input to wrap and grow vertically when overflow would occur (e.g. whenWidthis set or the control is in a narrow container).
Implementations must support data binding with Value, ValueChanged, and @bind-Value. ValueChanged is always an EventCallback<…> carrying the new value (or default / empty array when cleared)—not ChangeEventArgs (see §10).
Parameters below match BlazorBootstrapSelectSingle, BlazorBootstrapSelectMulti, and the *String wrappers. Generic components use [Parameter(CaptureUnmatchedValues = true)] on AdditionalAttributes; the *String wrappers only forward declared parameters (no unmatched capture)—use the generic components if you need arbitrary extra attributes on the outer element.
| Parameter | Type | Description |
|---|---|---|
Label |
string? |
Label above the control. |
PlaceholderText |
string? |
Placeholder when empty. |
Data |
IEnumerable<TItem>? |
Data source. |
TextField |
Func<TItem, string>? |
Display text per item. |
ValueField |
Func<TItem, TValue>? |
Bound value per item (required when TItem is not TValue; see base GetValue). |
Value |
TValue? |
Bound value. |
ValueChanged |
EventCallback<TValue?> |
Fired with the new value after user action (never ChangeEventArgs). |
Class |
string? |
Additional CSS classes on the interactive surface (e.g. \"is-invalid\"). |
Width |
string? |
Control width (e.g. 300px, 100%). |
AutoExpandVertically |
bool (default false) |
When true, displayed text wraps vertically when it exceeds width (§8.5). |
DropdownMatchInputWidth |
bool (default false) |
When true, dropdown width matches the input width. |
IsInvalid |
bool (default false) |
Invalid visual state (Bootstrap-compatible). |
Disabled |
bool |
Disable control. |
InputAttributes |
IDictionary<string, object>? |
Splatted onto the combobox surface. onchange / oninput keys are omitted at render (§10). |
AdditionalAttributes |
IDictionary<string, object>? |
Unmatched parameters / splat on the outer wrapper. Same stripping of onchange / oninput (§10). |
ValidationMessage |
Expression<Func<TValue?>>? |
For ValidationMessage / EditForm. |
AllowDeselect |
bool (default true) |
When false, re-click and clear cannot clear the selection. |
ShowClearButton |
bool (default true) |
Clear button (×) when a value is selected and AllowDeselect is true. |
ItemTemplate |
RenderFragment<SelectItemContext<TItem, TValue>>? |
Optional custom dropdown row (overrides default + convenience icon row). |
SelectedValueTemplate |
RenderFragment<SelectItemContext<TItem, TValue>>? |
Optional custom selected-value surface (overrides plain text + convenience icon). |
SemanticField |
Func<TItem, SelectSemantic>? |
Optional semantic for context + default chip. |
SemanticTextField |
Func<TItem, string?>? |
Optional chip text; defaults to enum label when unset. |
IconContent |
Func<TItem, string?>? |
Optional convenience icon content (ignored when ItemTemplate is set). |
IconAriaLabelField |
Func<TItem, string?>? |
Optional accessible name; otherwise icon is decorative. |
IconRenderMode |
SelectIconRenderMode |
CssClass (e.g. Bootstrap Icons) or Text (emoji/glyph). |
IconPlacement |
SelectIconPlacement |
Start or End relative to label text. |
ShowIconInSelectedValue |
bool (default true) |
Include convenience icon in combobox when not using SelectedValueTemplate. |
ShowSemanticPillInSelectedValue |
bool (default true) |
Include semantic chip in combobox when not using SelectedValueTemplate. |
| Parameter | Type | Description |
|---|---|---|
Label |
string? |
Label above the control. |
PlaceholderText |
string? |
Placeholder when empty. |
Data |
IEnumerable<TItem>? |
Data source. |
TextField |
Func<TItem, string>? |
Display text per item. |
ValueField |
Func<TItem, TValue>? |
Bound value per item. |
Value |
TValue[]? |
Bound selection. |
ValueChanged |
EventCallback<TValue[]?> |
Fired with the new array (empty when cleared). Never ChangeEventArgs. |
Class |
string? |
Additional CSS classes on the interactive surface. |
Width |
string? |
Control width. |
AutoExpandVertically |
bool (default false) |
Same as single (§8.5). |
DropdownMatchInputWidth |
bool (default false) |
Same as single. |
IsInvalid |
bool (default false) |
Same as single. |
Disabled |
bool |
Disable control. |
InputAttributes |
IDictionary<string, object>? |
Same as single (stripping §10). |
AdditionalAttributes |
IDictionary<string, object>? |
Same as single. |
ValidationMessage |
Expression<Func<TValue[]?>>? |
For validation. |
ShowClearButton |
bool (default true) |
Clear button when at least one value is selected. |
ItemTemplate |
RenderFragment<SelectItemContext<TItem, TValue>>? |
Same as single. |
SelectedValueTemplate |
RenderFragment<SelectItemContext<TItem, TValue>>? |
Same as single. |
SemanticField |
Func<TItem, SelectSemantic>? |
Same as single. |
SemanticTextField |
Func<TItem, string?>? |
Same as single. |
IconContent |
Func<TItem, string?>? |
Same as single. |
IconAriaLabelField |
Func<TItem, string?>? |
Same as single. |
IconRenderMode |
SelectIconRenderMode |
Same as single. |
IconPlacement |
SelectIconPlacement |
Same as single. |
ShowIconInSelectedValue |
bool (default true) |
Same as single. |
ShowSemanticPillInSelectedValue |
bool (default true) |
Same as single. |
Thin wrapper: TItem and TValue are both string; TextField and ValueField are fixed to identity (s => s). Same parameters as BlazorBootstrapSelectSingle except:
| Parameter | Type | Notes |
|---|---|---|
Data |
IEnumerable<string>? |
Item list. |
Value |
string? |
|
ValueChanged |
EventCallback<string?> |
Equivalent to EventCallback<TValue?> with TValue = string. |
ValidationMessage |
Expression<Func<string?>>? |
|
(no) TextField, ValueField |
— | Omitted on the wrapper. |
(no) AdditionalAttributes capture |
— | Use BlazorBootstrapSelectSingle for unmatched splat. |
InputAttributes |
IDictionary<string, object>? |
Forwarded; stripping still applied inside the generic implementation. |
(also) ItemTemplate, SelectedValueTemplate, SemanticField, SemanticTextField, IconContent, IconAriaLabelField, IconRenderMode, IconPlacement, ShowIconInSelectedValue, ShowSemanticPillInSelectedValue |
— | Forwarded to BlazorBootstrapSelectSingle (TItem/TValue = string). |
Same idea as BlazorBootstrapSelectSingleString for multi-select:
| Parameter | Type | Notes |
|---|---|---|
Data |
IEnumerable<string>? |
|
Value |
string[]? |
|
ValueChanged |
EventCallback<string[]?> |
|
ValidationMessage |
Expression<Func<string[]?>>? |
|
(no) TextField, ValueField |
— | |
(no) AdditionalAttributes capture |
— | Use BlazorBootstrapSelectMulti for unmatched splat. |
InputAttributes |
IDictionary<string, object>? |
Same as BlazorBootstrapSelectSingleString. |
(also) ItemTemplate, SelectedValueTemplate, SemanticField, SemanticTextField, IconContent, IconAriaLabelField, IconRenderMode, IconPlacement, ShowIconInSelectedValue, ShowSemanticPillInSelectedValue |
— | Forwarded to BlazorBootstrapSelectMulti. |
Use either full templates or the optional convenience icon parameters. If ItemTemplate / SelectedValueTemplate are set, they take precedence; convenience IconContent is ignored for that surface.
| Parameter | Type | Description |
|---|---|---|
ItemTemplate |
RenderFragment<SelectItemContext<TItem, TValue>>? |
Custom row markup for each dropdown item (full layout control). |
SelectedValueTemplate |
RenderFragment<SelectItemContext<TItem, TValue>>? |
Custom markup for selected value(s) in the combobox. |
SemanticField / SemanticTextField |
see §9 tables | Default semantic chips + SelectItemContext. |
IconContent |
Func<TItem, string?>? |
Low-code icon: CSS class string (IconRenderMode = CssClass, e.g. Bootstrap Icons) or text/emoji (Text). |
IconAriaLabelField |
Func<TItem, string?>? |
Meaningful icons: supply a short label; otherwise icon is decorative (aria-hidden). |
IconRenderMode |
SelectIconRenderMode |
CssClass → <i class="...">; Text → <span>…</span>. |
IconPlacement |
SelectIconPlacement |
Start or End next to the item label. |
ShowIconInSelectedValue / ShowSemanticPillInSelectedValue |
bool |
Toggle parts of the default selected-value row when not using SelectedValueTemplate. |
When to use convenience parameters vs templates
- Convenience path: one icon string per row, same layout everywhere (Bootstrap Icons, emoji). Fast to wire; keep icons decorative or set
IconAriaLabelField. - Template path: multiple badges, conditional markup, SVG/component icons, or row layouts that vary per item—use
ItemTemplate/SelectedValueTemplateand readSelectItemContext.
Reusing SelectSemantic styling outside the select (grids, labels, cards)
The library maps semantics to status-chip / status-chip-* classes in bootstrap-select.css. Use SelectSemanticCss so grids and forms match the select’s default chips:
@using BlazorBootstrapCustomControls.Components.Shared
<span class="@SelectSemanticCss.GetChipClasses(model.Status)">@labelText</span>GetChipModifierClass(SelectSemantic)returns only the modifier (e.g.status-chip-warning).GetChipClasses(SelectSemantic)returnsstatus-chip status-chip-*for a full badge.
Consistency: treat SelectSemantic as domain/UI-neutral data; centralize display mapping in SelectSemanticCss (or your own wrapper that delegates to it) so lists, selects, and detail views stay consistent.
Reference: define templates in @code (or a shared partial class)—avoid complex inline RenderFragment literals in markup (compiler limitations).
Verification checklist (implementation review)
- Template override: With both
ItemTemplateandIconContentset, list rows matchItemTemplateonly; convenience icon does not appear. - Convenience dropdown + selected: With
IconContent+SemanticFieldand no templates, dropdown rows and combobox surface show icon + text + chip (unless toggled off viaShow*flags). - CssClass mode:
IconRenderMode.CssClasswith Bootstrap Icons requires the app to include Bootstrap Icons CSS; verify glyphs render. - Text mode:
IconRenderMode.Textwith emoji shows in list and selected area; screen reader gets text fromTextField/ optionsaria-label. - Multi selected surface: Multiple selections render comma-separated convenience segments when
SelectedValueTemplateis null andIconContentis set. - External chip: A grid cell using
SelectSemanticCss.GetChipClassesmatches select chip colors for the sameSelectSemanticvalue.
- Prefer C# in Razor over JS: Favor C# in Razor over JavaScript when implementing behavior; use JS only when necessary (e.g. keydown before Blazor can handle it, or focus in a portal). See §1.
- Prerender / static SSR (issue #7): In Blazor Web Apps with default interactive prerender, the control registers
BSSelect.initonly whenRendererInfo.IsInteractiveis true (after the interactive render).DisposeAsynccallsBSSelect.teardownonly if that registration ran, so disposal during the static prerender segment does not invoke JS interop or throwInvalidOperationException. - Item templates and accessibility:
ItemTemplaterenders inside the existingrole="option"container. Keep non-text visuals (icons/pills) decorative and ensure text remains available viaTextField(type-ahead + accessibility). The control computes optionaria-labelfrom text + semantic text. - Down / Right / Enter to open list (§4.1), both controls: When the input has focus and the list is closed, Down, Right, or Enter open the list (they do not close it) and must not change the selected value(s). When Down or Right opens the list, the first item is highlighted. When Enter opens the list, no item is highlighted initially. Use a stable marker (e.g.
data-select-single,data-select-multi, or a wrapper class) so keydown (or equivalent) handling applies to the intended control(s). - Type-ahead (§4.1a), both controls: When the list is closed, printable keys are forwarded to
OpenAndTypeAhead; when open, toHandleTypeAhead(seewwwroot/js/bootstrap-select.js). Prefix matching uses each item’s display text (TextField/ToString()). Closing the list clears the buffer; scrollbar interaction remains as in issue #3 (dropdownmousedowndefault prevented in markup). IME: While composing (e.g. CJK input),keydownis ignored for type-ahead (isComposing/ composition events). - Open-list keyboard handling: Arrow keys, Enter, Escape, and Tab while the list is open are handled on the input
keydownlistener and forwarded toHandleListKey(focus stays on the input per §7.4); there is no separatekeydownlistener on the dropdown panel. - Up to close list (§4.2), both controls: When the list is open and the highlight is on the first item, ArrowUp and ArrowLeft close the list.
- Click input to toggle list (§5.1), both controls: Click on the input toggles the list (open if closed, close if open).
- Arrow keys do not wrap: ArrowDown/ArrowRight on the last item do nothing; ArrowUp/ArrowLeft on the first item close the list (§4.2).
- Dropdown (portal or inline): If the list is rendered in a portal, ensure
:focus/:focus-withinand any custom "keyboard highlight" class apply to the list item (or a focusable child) so the same Bootstrap primary style is used as for hover. - Multi-select: When no custom item template is used, apply right padding and a CSS
::aftercheck-mark on the selected state. - Single-select, prevent de-selection: When
AllowDeselectis false, ignore re-click-on-selected and Enter-on-selected attempts to clear the value. Clear-button behavior when de-selection is prevented is optional (§11). - Lose focus — collapse list; click not lost: When the control loses focus (e.g. Tab or clicking away), the list must collapse (close). When the loss of focus is due to a click (clicking away), the click must not be lost: e.g. if the user clicks a button while the list is open, the list closes and the button receives the click (is activated).
- Dark mode support: All colors use Bootstrap CSS variables (
--bs-body-bg,--bs-body-color,--bs-primary, etc.) so the controls adapt to Bootstrap's dark mode theme automatically. - Accessibility: Uses Bootstrap CSS variables for sizing (e.g.
--bs-body-line-height) so controls scale correctly when users adjust font size or line height for accessibility. WhenLabelis set, the label usesforpointing at the comboboxid(same stable id asdata-bs-select-id). When the list is open, the combobox setsaria-controlsto the listbox element’sid({id}-listbox). - Data binding / attribute splat: Use
Value/ValueChangedor@bind-Valueonly. Do not splatonchange/oninputonto the control (or intoInputAttributes/ unmatched attributes): those are for native<input>elements and useChangeEventArgs, which does not match this control’s typed callbacks. As a safeguard, the implementation stripsonchange/oninputfrom copies used for render so mistaken copy-paste from input components does not attach those handlers to the div combobox.
The following behaviors are optional. Implementations may omit them or defer them to a later revision. When implemented, they should follow the constraints given.
- Clear button (SelectSingle, SelectMulti): A button (e.g. ×) in the control that clears the selection in one action. SelectSingle: when de-selection is prevented (
AllowDeselectfalse), hide the button or make it no-op. SelectMulti: clears all selected. AShowClearButton(or equivalent) parameter may control visibility. Note: This is currently implemented; the clear button is click-only (not focusable, same as the dropdown arrow), and when the input has focus and the clear button is shown, pressing Delete clears the selection. - Select-all (SelectMulti): Optional header or action to select all items in the list (default off). A
ShowSelectAll(or equivalent) parameter may control visibility. - Dropdown icon (SelectMulti, or both): Optional icon (e.g. arrow on the right) so the control matches the single-select. When implemented, same styling as §8.4. Note: This is currently implemented for both Single and Multi.
- Floating label (SelectSingle, SelectMulti): Optional floating-label style for the control. A
FloatingLabel(or equivalent) parameter may control it. - Search / filter (both): Optional text filter in or above the list to narrow items. Not in the current required spec. (Distinct from type-ahead prefix highlight in §4.1a, which does not remove items from the list.)
- Grouping (both): Optional group headers in the list to organize items. Not in the current required spec.
This library (BlazorBootstrapCustomControls) implements this specification:
| Purpose | Path |
|---|---|
| Single-select component | Components/Shared/BlazorBootstrapSelectSingle.razor |
| Multi-select component | Components/Shared/BlazorBootstrapSelectMulti.razor |
| Shared base class | Components/Shared/BlazorBootstrapSelectBase.cs |
| Shared markup | Components/Shared/BlazorBootstrapSelectMarkup.razor |
| Keyboard handling | wwwroot/js/bootstrap-select.js |
| Styles (check-mark, highlight, dropdown) | wwwroot/css/bootstrap-select.css |
Building the RCL copies wwwroot/js/bootstrap-select.js and wwwroot/css/bootstrap-select.css into docs/_content/... and docs/wwwroot/_content/... when a docs folder exists (keeps checked-in GitHub Pages mirrors aligned). Pre-compressed *.gz / *.br siblings under docs/_content (if present) come from Blazor publish static-asset compression and the CI step that copies publish/wwwroot into docs; they are not updated by that sync target.
This spec is implementation-agnostic and requires Bootstrap compatibility. It can be satisfied with any Blazor dropdown or JS-based component; this library is one implementation.
