PopupControlExtender

March 15, 2026 · View on GitHub

The PopupControlExtender attaches a popup panel to a target control, displaying it when the target is clicked. It is lighter than the ModalPopupExtender — no overlay backdrop, no focus trap. The popup supports positional placement, commit property/script support, and outside-click dismissal.

Original Ajax Control Toolkit documentation: https://www.asp.net/ajax/ajaxcontroltoolkit/PopupControlExtender

Features Supported in Blazor

  • TargetControlID — ID of the control that triggers the popup on click
  • PopupControlID — ID of the panel element to display as a popup
  • Position — Position of the popup relative to the target control
  • OffsetX — Horizontal offset in pixels from the calculated position
  • OffsetY — Vertical offset in pixels from the calculated position
  • CommitProperty — Property on the target control to set when popup is committed
  • CommitScript — JavaScript to execute when the popup is committed
  • ExtenderControlID — ID for programmatic access to the extender
  • Enabled — Enable or disable the extender behavior
  • BehaviorID — Optional identifier for JavaScript behavior lookup

PopupPosition Enum

enum PopupPosition
{
    Left = 0,     // To the left of target
    Right = 1,    // To the right of target
    Top = 2,      // Above target
    Bottom = 3,   // Below target (default)
    Center = 4    // Centered on target
}

Web Forms Syntax

<asp:TextBox ID="txtColor" runat="server" />

<asp:Panel ID="pnlColorPicker" runat="server" style="display: none;">
    <div class="color-grid">
        <span style="background: red;" onclick="commitColor('red')"></span>
        <span style="background: blue;" onclick="commitColor('blue')"></span>
        <span style="background: green;" onclick="commitColor('green')"></span>
    </div>
</asp:Panel>

<ajaxToolkit:PopupControlExtender
    ID="pce1"
    runat="server"
    TargetControlID="txtColor"
    PopupControlID="pnlColorPicker"
    Position="Bottom"
    CommitProperty="value"
    CommitScript="onColorCommit()" />

Blazor Migration

<TextBox ID="txtColor" />

<div ID="pnlColorPicker" style="display: none;">
    <div class="color-grid">
        <span style="background: red;" onclick="commitColor('red')"></span>
        <span style="background: blue;" onclick="commitColor('blue')"></span>
        <span style="background: green;" onclick="commitColor('green')"></span>
    </div>
</div>

<PopupControlExtender
    TargetControlID="txtColor"
    PopupControlID="pnlColorPicker"
    Position="PopupPosition.Bottom"
    CommitProperty="value"
    CommitScript="onColorCommit()" />

Migration is simple: Remove the ajaxToolkit: prefix and runat="server". Use full enum type name for Position. Everything else stays the same!

Properties Reference

PropertyTypeDefaultDescription
TargetControlIDstring(required)ID of the control that triggers the popup on click
PopupControlIDstring""ID of the panel element to display as a popup
PositionPopupPositionBottomPosition of the popup relative to the target: Left, Right, Top, Bottom, Center
OffsetXint0Horizontal offset in pixels from the calculated position
OffsetYint0Vertical offset in pixels from the calculated position
CommitPropertystring""Property name on the target control to set when the popup is committed (e.g., "value")
CommitScriptstring""JavaScript to execute when the popup is committed
ExtenderControlIDstring""ID for programmatic access to the extender
BehaviorIDstringTargetControlIDOptional identifier for JavaScript behavior lookup
EnabledbooltrueWhether the extender is active

Usage Examples

Basic Popup

@rendermode InteractiveServer

<TextBox ID="txtInput" />

<div ID="popupPanel" style="display: none; background: white; border: 1px solid #ccc; padding: 10px; border-radius: 4px;">
    <p>Select an option:</p>
    <button onclick="selectOption('Option A')">Option A</button>
    <button onclick="selectOption('Option B')">Option B</button>
</div>

<PopupControlExtender
    TargetControlID="txtInput"
    PopupControlID="popupPanel"
    Position="PopupPosition.Bottom" />
@rendermode InteractiveServer

<TextBox ID="txtNote" />

<div ID="notePopup" style="display: none; background: #fffbcc; border: 1px solid #e0d870; padding: 15px; width: 250px;">
    <h4>Quick Notes</h4>
    <textarea rows="3" style="width: 100%;"></textarea>
    <button onclick="saveNote()">Save</button>
</div>

<PopupControlExtender
    TargetControlID="txtNote"
    PopupControlID="notePopup"
    Position="PopupPosition.Right"
    OffsetX="10"
    OffsetY="-5" />

When a value is selected in the popup, it can be automatically set on the target control:

@rendermode InteractiveServer

<TextBox ID="txtCategory" />

<div ID="categoryPicker" style="display: none; background: white; border: 1px solid #ddd; padding: 8px;">
    <ul style="list-style: none; padding: 0; margin: 0;">
        <li style="padding: 4px 8px; cursor: pointer;" onclick="commitCategory('Electronics')">Electronics</li>
        <li style="padding: 4px 8px; cursor: pointer;" onclick="commitCategory('Clothing')">Clothing</li>
        <li style="padding: 4px 8px; cursor: pointer;" onclick="commitCategory('Books')">Books</li>
    </ul>
</div>

<PopupControlExtender
    TargetControlID="txtCategory"
    PopupControlID="categoryPicker"
    Position="PopupPosition.Bottom"
    CommitProperty="value" />

HTML Output

The PopupControlExtender produces no HTML itself — it attaches JavaScript behavior to show/hide the popup panel and position it relative to the target control.

JavaScript Interop

The PopupControlExtender loads popup-control-extender.js as an ES module. JavaScript handles:

  • Showing the popup when the target control is clicked
  • Positioning the popup relative to the target using the configured position and offsets
  • Dismissing the popup on outside click
  • Setting the commit property on the target when a value is committed
  • Executing the commit script

Render Mode Requirements

The PopupControlExtender requires InteractiveServer render mode:

@rendermode InteractiveServer

Graceful Degradation

  • SSR/Static mode: The extender silently skips initialization. The popup panel remains hidden.
  • JavaScript disabled: Same as SSR — target control works normally without popup.

Migration Notes

From Web Forms Ajax Toolkit

  1. Remove ajaxToolkit: prefix

    - <ajaxToolkit:PopupControlExtender
    + <PopupControlExtender
    
  2. Remove runat="server" and ID attributes

  3. Use full enum type name for Position

    - Position="Bottom"
    + Position="PopupPosition.Bottom"
    

Before (Web Forms)

<asp:TextBox ID="txtColor" runat="server" />

<asp:Panel ID="pnlPicker" runat="server" style="display: none;">
    <div>Color picker content</div>
</asp:Panel>

<ajaxToolkit:PopupControlExtender
    ID="pce1"
    TargetControlID="txtColor"
    PopupControlID="pnlPicker"
    Position="Bottom"
    CommitProperty="value"
    runat="server" />

After (Blazor)

<TextBox ID="txtColor" />

<div ID="pnlPicker" style="display: none;">
    <div>Color picker content</div>
</div>

<PopupControlExtender
    TargetControlID="txtColor"
    PopupControlID="pnlPicker"
    Position="PopupPosition.Bottom"
    CommitProperty="value" />

Best Practices

  1. Hide the popup initially — Set style="display: none;" on the popup element
  2. Use appropriate positioning — Choose Bottom for dropdowns, Right for sidebars
  3. Fine-tune with offsets — Use OffsetX/OffsetY to avoid visual overlap
  4. Keep popup content focused — Avoid overloading the popup with too much content
  5. Use CommitProperty for form integration — Automatically set values on the target control

Troubleshooting

IssueSolution
Popup not appearingVerify TargetControlID and PopupControlID match their elements' IDs. Ensure @rendermode InteractiveServer is set.
Popup positioned incorrectlyTry a different Position value or adjust OffsetX/OffsetY.
Popup not dismissingEnsure there are no event propagation issues (e.g., stopPropagation calls) in the popup content.
Value not committedVerify CommitProperty matches the target's property name (typically "value" for inputs).

See Also