Dropdown
A composable dropdown with built-in open/close state, value selection, keyboard navigation, and full
ARIA semantics — all in pure C#, no JavaScript required. Style every part with Tailwind utilities via
the Class parameter.
Overview
The MDropdown family manages all behavior so you can focus on styling.
MDropdownTrigger toggles open state; MDropdownContent renders the menu panel
with keyboard handling; MDropdownItem handles selection, auto-close, and ARIA;
MDropdownLabel and MDropdownSeparator provide structural markup.
Apply Class to every part to compose the visual design you need.
Usage
Add the namespace import to use the component.
@using Marai.UI.Components.MDropdownBasic Usage
A value dropdown with explicit Tailwind classes on every part:
<MDropdown @bind-Value="_selectedStatus" Class="relative inline-block w-56">
<MDropdownTrigger
Class="flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
Placeholder="Select status">
<SelectedValueContent>
@context
</SelectedValueContent>
</MDropdownTrigger>
<MDropdownContent
Class="absolute left-0 top-full z-50 mt-1 w-full min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 shadow-md"
OverlayClass="fixed inset-0 z-40">
<MDropdownItem Value="All"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
All
</MDropdownItem>
<MDropdownItem Value="Unreconciled"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Unreconciled
</MDropdownItem>
<MDropdownItem Value="Reconciled"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Reconciled
</MDropdownItem>
</MDropdownContent>
</MDropdown>
@code {
private string? _selectedStatus;
}
Examples
Action Menu
Use as an action menu. Items close the dropdown automatically after firing OnClick:
<MDropdown @bind-Value="_lastAction" Class="relative inline-block w-48">
<MDropdownTrigger
Class="flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
Placeholder="Choose action">
<SelectedValueContent>
@context
</SelectedValueContent>
</MDropdownTrigger>
<MDropdownContent
Class="absolute left-0 top-full z-50 mt-1 w-full min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 shadow-md"
OverlayClass="fixed inset-0 z-40">
<MDropdownItem Value="Copy"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Copy
</MDropdownItem>
<MDropdownItem Value="Paste"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Paste
</MDropdownItem>
<MDropdownSeparator Class="my-1 border-t border-border" />
<MDropdownItem Value="Delete"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Delete
</MDropdownItem>
</MDropdownContent>
</MDropdown>
@code {
private string? _lastAction;
}
Labels and Separators
Use MDropdownLabel for section headings and MDropdownSeparator to divide groups. Style both with Class:
<MDropdown @bind-Value="_accountAction" Class="relative inline-block w-48">
<MDropdownTrigger
Class="flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
Placeholder="My Account">
<SelectedValueContent>
@context
</SelectedValueContent>
</MDropdownTrigger>
<MDropdownContent
Class="absolute left-0 top-full z-50 mt-1 w-full min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 shadow-md"
OverlayClass="fixed inset-0 z-40">
<MDropdownLabel Class="px-2 py-1.5 text-xs font-semibold text-muted-foreground">Account</MDropdownLabel>
<MDropdownSeparator Class="my-1 border-t border-border" />
<MDropdownItem Value="Profile"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Profile
</MDropdownItem>
<MDropdownItem Value="Billing"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Billing
</MDropdownItem>
<MDropdownItem Value="Settings"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Settings
</MDropdownItem>
<MDropdownSeparator Class="my-1 border-t border-border" />
<MDropdownItem Value="Sign out"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Sign out
</MDropdownItem>
</MDropdownContent>
</MDropdown>
@code {
private string? _accountAction;
}
Disabled Items
Set Disabled="true" on MDropdownItem. The component adds aria-disabled="true" and data-disabled="true",
allowing Tailwind's aria-disabled: modifier to style the item without extra conditional logic:
<MDropdown @bind-Value="_fileAction" Class="relative inline-block w-48">
<MDropdownTrigger
Class="flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
Placeholder="File options">
<SelectedValueContent>
@context
</SelectedValueContent>
</MDropdownTrigger>
<MDropdownContent
Class="absolute left-0 top-full z-50 mt-1 w-full min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 shadow-md"
OverlayClass="fixed inset-0 z-40">
<MDropdownItem Value="Edit"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground aria-disabled:pointer-events-none aria-disabled:opacity-50">
Edit
</MDropdownItem>
<MDropdownItem Value="Export" Disabled="true"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground aria-disabled:pointer-events-none aria-disabled:opacity-50">
Export (unavailable)
</MDropdownItem>
<MDropdownSeparator Class="my-1 border-t border-border" />
<MDropdownItem Value="Delete"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground aria-disabled:pointer-events-none aria-disabled:opacity-50">
Delete
</MDropdownItem>
</MDropdownContent>
</MDropdown>
@code {
private string? _fileAction;
}
Custom Trigger Icon
Supply any icon or content via ChildContent on MDropdownTrigger. No built-in chevron is rendered:
<MDropdown @bind-Value="_selectedOption" Class="relative inline-block w-56">
<MDropdownTrigger
Class="flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
@if (!string.IsNullOrEmpty(_selectedOption))
{
<span>@_selectedOption</span>
}
else
{
<span class="text-muted-foreground">Select option</span>
}
<svg class="ml-2 h-4 w-4 opacity-60" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 9 6 6 6-6"/>
</svg>
</MDropdownTrigger>
<MDropdownContent
Class="absolute left-0 top-full z-50 mt-1 w-full min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 shadow-md"
OverlayClass="fixed inset-0 z-40">
<MDropdownItem Value="Profile"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Profile
</MDropdownItem>
<MDropdownItem Value="Settings"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Settings
</MDropdownItem>
<MDropdownItem Value="Sign out"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Sign out
</MDropdownItem>
</MDropdownContent>
</MDropdown>
@code {
private string? _selectedOption;
}
Outside-Click Dismissal
Pass OverlayClass to MDropdownContent to render a click-capture layer behind the menu.
Use fixed inset-0 to cover the full viewport. Without OverlayClass no overlay is rendered:
<MDropdown @bind-Value="_action" Class="relative inline-block w-48">
<MDropdownTrigger
Class="flex w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
Placeholder="Pick action">
<SelectedValueContent>@context</SelectedValueContent>
</MDropdownTrigger>
<MDropdownContent
Class="absolute left-0 top-full z-50 mt-1 w-full min-w-32 overflow-hidden rounded-md border border-border bg-popover p-1 shadow-md"
OverlayClass="fixed inset-0 z-40">
<MDropdownItem Value="Edit"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Edit
</MDropdownItem>
<MDropdownItem Value="Archive"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Archive
</MDropdownItem>
<MDropdownItem Value="Delete"
Class="flex w-full cursor-pointer items-center rounded-sm px-2 py-1.5 text-sm text-foreground outline-none hover:bg-muted focus:bg-muted data-selected:bg-primary data-selected:text-primary-foreground">
Delete
</MDropdownItem>
</MDropdownContent>
</MDropdown>
@code {
private string? _action;
}
API Reference
MDropdown
| Parameter | Type | Default | Description |
|---|---|---|---|
| ChildContent | RenderFragment? | null |
Slot content — typically MDropdownTrigger and MDropdownContent. |
| Value | string? | null |
The currently selected value. Use with @bind-Value for two-way binding. |
| ValueChanged | EventCallback<string?> | — | Callback invoked when the selected value changes. |
| Class | string? | null |
CSS classes applied to the root container element. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the root element. |
MDropdownTrigger
| Parameter | Type | Default | Description |
|---|---|---|---|
| ChildContent | RenderFragment? | null |
Full trigger content. When provided, overrides SelectedValueContent and Placeholder. |
| SelectedValueContent | RenderFragment<string>? | null |
Template rendered when a value is selected and ChildContent is null. Context is the selected value string. |
| Placeholder | string? | null |
Text rendered when no value is selected and no ChildContent is provided. |
| Class | string? | null |
CSS classes applied to the trigger button element. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the button element. |
MDropdownContent
| Parameter | Type | Default | Description |
|---|---|---|---|
| ChildContent | RenderFragment? | null |
Items, labels, and separators rendered inside the panel. |
| Class | string? | null |
CSS classes applied to the menu panel element. |
| OverlayClass | string? | null |
CSS classes applied to the outside-click capture layer. When null, no overlay is rendered and clicking outside does not close the menu. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the menu panel element. |
MDropdownItem
| Parameter | Type | Default | Description |
|---|---|---|---|
| ChildContent | RenderFragment? | null |
Item label or content. |
| Value | string? | null |
The value set on the dropdown when this item is clicked. |
| Disabled | bool | false |
Prevents interaction. Sets disabled, aria-disabled="true", and data-disabled="true" on the element. |
| OnClick | EventCallback | — | Callback invoked when the item is clicked. The menu closes automatically after invocation. |
| Class | string? | null |
CSS classes applied to the item element. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through to the item element. |
MDropdownItem adds data-selected="true" and aria-selected="true" when its value matches the dropdown's selected value.
Use Tailwind's data-[selected]:... or aria-selected:... modifiers to style selected state without conditional logic.
MDropdownLabel
| Parameter | Type | Default | Description |
|---|---|---|---|
| ChildContent | RenderFragment? | null |
Group heading text or content. |
| Class | string? | null |
CSS classes applied to the label element. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through. |
MDropdownSeparator
| Parameter | Type | Default | Description |
|---|---|---|---|
| Class | string? | null |
CSS classes applied to the separator element. |
| AdditionalAttributes | Dictionary<string, object>? | null |
Arbitrary HTML attributes passed through. |
Keyboard Navigation
| Key | Context | Action |
|---|---|---|
| ArrowDown | Trigger focused, menu closed | Open menu and focus first enabled item. |
| ArrowUp | Trigger focused, menu closed | Open menu and focus last enabled item. |
| Enter / Space | Trigger focused | Toggle menu open/closed (native button behavior). |
| ArrowDown | Menu open | Move focus to next enabled item (wraps). |
| ArrowUp | Menu open | Move focus to previous enabled item (wraps). |
| Home | Menu open | Move focus to first enabled item. |
| End | Menu open | Move focus to last enabled item. |
| Enter / Space | Item focused | Activate item (native button behavior). |
| Escape | Menu open | Close menu and return focus to trigger. |
| Tab | Menu open | Close menu and move focus naturally. |
Accessibility
MDropdownTriggerrenders witharia-haspopup="menu",aria-expanded, andaria-controlspointing to the menu panel IDMDropdownContentrenders withrole="menu"and a stable generatedidMDropdownItemrenders withrole="menuitem",tabindex="-1", andaria-disabledwhen disabled- Disabled items are skipped during keyboard navigation
- Escape closes the menu and returns focus to the trigger automatically
- ArrowDown / ArrowUp from the trigger open the menu and land focus on the first or last enabled item respectively