17 KiB
Dropdown Component
Introduction
The Dropdown component provides an interactive dropdown menu that toggles open or closed when clicking a trigger button. It handles positioning, automatic closing behavior, and keyboard navigation out of the box.
Key features:
- Toggle open/close on button click
- Automatic close when clicking outside
- Keyboard support (ESC to close)
- Configurable vertical position (above or below the button)
- Configurable horizontal alignment (left, right, or center)
- Session-based state management
- HTMX-powered updates without page reload
Common use cases:
- Navigation menus
- User account menus
- Action menus (edit, delete, share)
- Filter or sort options
- Context-sensitive toolbars
- Settings quick access
Quick Start
Here's a minimal example showing a dropdown menu with navigation links:
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.core.instances import RootInstance
# Create root instance and dropdown
root = RootInstance(session)
dropdown = Dropdown(
parent=root,
button=Button("Menu", cls="btn"),
content=Ul(
Li(A("Home", href="/")),
Li(A("Settings", href="/settings")),
Li(A("Logout", href="/logout"))
)
)
# Render the dropdown
return dropdown
This creates a complete dropdown with:
- A "Menu" button that toggles the dropdown
- A list of navigation links displayed below the button
- Automatic closing when clicking outside the dropdown
- ESC key support to close the dropdown
Note: The dropdown opens below the button and aligns to the left by default. Users can click anywhere outside the dropdown to close it, or press ESC on the keyboard.
Basic Usage
Visual Structure
The Dropdown component consists of a trigger button and a content panel:
Closed state:
┌──────────────┐
│ Button ▼ │
└──────────────┘
Open state (position="below", align="left"):
┌──────────────┐
│ Button ▼ │
├──────────────┴─────────┐
│ Dropdown Content │
│ - Option 1 │
│ - Option 2 │
│ - Option 3 │
└────────────────────────┘
Open state (position="above", align="right"):
┌────────────────────────┐
│ Dropdown Content │
│ - Option 1 │
│ - Option 2 │
├──────────────┬─────────┘
│ Button ▲ │
└──────────────┘
Component details:
| Element | Description |
|---|---|
| Button | Trigger element that toggles the dropdown |
| Content | Panel containing the dropdown menu items |
| Wrapper | Container with relative positioning for anchor |
Creating a Dropdown
The Dropdown is a MultipleInstance, meaning you can create multiple independent dropdowns in your application. Create it by providing a parent instance:
dropdown = Dropdown(parent=root_instance, button=my_button, content=my_content)
# Or with a custom ID
dropdown = Dropdown(parent=root_instance, button=my_button, content=my_content, _id="my-dropdown")
Button and Content
The dropdown requires two main elements:
Button: The trigger element that users click to toggle the dropdown.
# Simple text button
dropdown = Dropdown(
parent=root,
button=Button("Click me", cls="btn btn-primary"),
content=my_content
)
# Button with icon
dropdown = Dropdown(
parent=root,
button=Div(
icon_svg,
Span("Options"),
cls="flex items-center gap-2"
),
content=my_content
)
# Just an icon
dropdown = Dropdown(
parent=root,
button=icon_svg,
content=my_content
)
Content: Any FastHTML element to display in the dropdown panel.
# Simple list
content = Ul(
Li("Option 1"),
Li("Option 2"),
Li("Option 3"),
cls="menu"
)
# Complex content with sections
content = Div(
Div("User Actions", cls="font-bold p-2"),
Hr(),
Button("Edit Profile", cls="btn btn-ghost w-full"),
Button("Settings", cls="btn btn-ghost w-full"),
Hr(),
Button("Logout", cls="btn btn-error w-full")
)
Positioning Options
The Dropdown supports two positioning parameters:
position - Vertical position relative to the button:
"below"(default): Dropdown appears below the button"above": Dropdown appears above the button
align - Horizontal alignment relative to the button:
"left"(default): Dropdown aligns to the left edge of the button"right": Dropdown aligns to the right edge of the button"center": Dropdown is centered relative to the button
# Default: below + left
dropdown = Dropdown(parent=root, button=btn, content=menu)
# Above the button, aligned right
dropdown = Dropdown(parent=root, button=btn, content=menu, position="above", align="right")
# Below the button, centered
dropdown = Dropdown(parent=root, button=btn, content=menu, position="below", align="center")
Visual examples of all combinations:
position="below", align="left" position="below", align="center" position="below", align="right"
┌────────┐ ┌────────┐ ┌────────┐
│ Button │ │ Button │ │ Button │
├────────┴────┐ ┌────┴────────┴────┐ ┌────────────┴────────┤
│ Content │ │ Content │ │ Content │
└─────────────┘ └──────────────────┘ └─────────────────────┘
position="above", align="left" position="above", align="center" position="above", align="right"
┌─────────────┐ ┌──────────────────┐ ┌─────────────────────┐
│ Content │ │ Content │ │ Content │
├────────┬────┘ └────┬────────┬────┘ └────────────┬────────┤
│ Button │ │ Button │ │ Button │
└────────┘ └────────┘ └────────┘
Advanced Features
Automatic Close Behavior
The Dropdown automatically closes in two scenarios:
Click outside: When the user clicks anywhere outside the dropdown, it closes automatically. This is handled by the Mouse component listening for global click events.
ESC key: When the user presses the ESC key, the dropdown closes. This is handled by the Keyboard component.
# Both behaviors are enabled by default - no configuration needed
dropdown = Dropdown(parent=root, button=btn, content=menu)
How it works internally:
- The
Mousecomponent detects clicks and sendsis_insideandis_buttonparameters - If
is_buttonis true, the dropdown toggles - If
is_insideis false (clicked outside), the dropdown closes - The
Keyboardcomponent listens for ESC and triggers the close command
Programmatic Control
You can control the dropdown programmatically using its methods and commands:
# Toggle the dropdown state
dropdown.toggle()
# Close the dropdown
dropdown.close()
# Access commands for use with other controls
close_cmd = dropdown.commands.close()
click_cmd = dropdown.commands.click()
Using commands with buttons:
from myfasthtml.controls.helpers import mk
# Create a button that closes the dropdown
close_button = mk.button("Close", command=dropdown.commands.close())
# Add it to the dropdown content
dropdown = Dropdown(
parent=root,
button=Button("Menu"),
content=Div(
Ul(Li("Option 1"), Li("Option 2")),
close_button
)
)
CSS Customization
The Dropdown uses CSS classes that you can customize:
| Class | Element |
|---|---|
mf-dropdown-wrapper |
Container with relative positioning |
mf-dropdown-btn |
Button wrapper |
mf-dropdown |
Dropdown content panel |
mf-dropdown-below |
Applied when position="below" |
mf-dropdown-above |
Applied when position="above" |
mf-dropdown-left |
Applied when align="left" |
mf-dropdown-right |
Applied when align="right" |
mf-dropdown-center |
Applied when align="center" |
is-visible |
Applied when dropdown is open |
Example customization:
/* Change dropdown background and border */
.mf-dropdown {
background-color: #1f2937;
border: 1px solid #374151;
border-radius: 0.5rem;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
/* Add animation */
.mf-dropdown {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.mf-dropdown.is-visible {
opacity: 1;
transform: translateY(0);
}
/* Style for above position */
.mf-dropdown-above {
transform: translateY(10px);
}
.mf-dropdown-above.is-visible {
transform: translateY(0);
}
Examples
Example 1: Navigation Menu
A simple navigation dropdown menu:
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown
dropdown = Dropdown(
parent=root,
button=Button("Navigation", cls="btn btn-ghost"),
content=Ul(
Li(A("Dashboard", href="/dashboard", cls="block p-2 hover:bg-base-200")),
Li(A("Projects", href="/projects", cls="block p-2 hover:bg-base-200")),
Li(A("Tasks", href="/tasks", cls="block p-2 hover:bg-base-200")),
Li(A("Reports", href="/reports", cls="block p-2 hover:bg-base-200")),
cls="menu p-2"
)
)
return dropdown
Example 2: User Account Menu
A user menu aligned to the right, typically placed in a header:
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown
# User avatar button
user_button = Div(
Img(src="/avatar.png", cls="w-8 h-8 rounded-full"),
Span("John Doe", cls="ml-2"),
cls="flex items-center gap-2 cursor-pointer"
)
# Account menu content
account_menu = Div(
Div(
Div("John Doe", cls="font-bold"),
Div("john@example.com", cls="text-sm opacity-60"),
cls="p-3 border-b"
),
Ul(
Li(A("Profile", href="/profile", cls="block p-2 hover:bg-base-200")),
Li(A("Settings", href="/settings", cls="block p-2 hover:bg-base-200")),
Li(A("Billing", href="/billing", cls="block p-2 hover:bg-base-200")),
cls="menu p-2"
),
Div(
A("Sign out", href="/logout", cls="block p-2 text-error hover:bg-base-200"),
cls="border-t"
),
cls="w-56"
)
# Align right so it doesn't overflow the viewport
dropdown = Dropdown(
parent=root,
button=user_button,
content=account_menu,
align="right"
)
return dropdown
Example 3: Action Menu Above Button
A dropdown that opens above the trigger, useful when the button is at the bottom of the screen:
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown
# Action button with icon
action_button = Button(
Span("+", cls="text-xl"),
cls="btn btn-circle btn-primary"
)
# Quick actions menu
actions_menu = Div(
Button("New Document", cls="btn btn-ghost btn-sm w-full justify-start"),
Button("Upload File", cls="btn btn-ghost btn-sm w-full justify-start"),
Button("Create Folder", cls="btn btn-ghost btn-sm w-full justify-start"),
Button("Import Data", cls="btn btn-ghost btn-sm w-full justify-start"),
cls="flex flex-col p-2 w-40"
)
# Open above and center-aligned
dropdown = Dropdown(
parent=root,
button=action_button,
content=actions_menu,
position="above",
align="center"
)
return dropdown
Example 4: Dropdown with Commands
A dropdown containing action buttons that execute commands:
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
# Define actions
def edit_item():
return "Editing..."
def delete_item():
return "Deleted!"
def share_item():
return "Shared!"
# Create commands
edit_cmd = Command("edit", "Edit item", edit_item)
delete_cmd = Command("delete", "Delete item", delete_item)
share_cmd = Command("share", "Share item", share_item)
# Build menu with command buttons
actions_menu = Div(
mk.button("Edit", command=edit_cmd, cls="btn btn-ghost btn-sm w-full justify-start"),
mk.button("Share", command=share_cmd, cls="btn btn-ghost btn-sm w-full justify-start"),
Hr(cls="my-1"),
mk.button("Delete", command=delete_cmd, cls="btn btn-ghost btn-sm w-full justify-start text-error"),
cls="flex flex-col p-2"
)
dropdown = Dropdown(
parent=root,
button=Button("Actions", cls="btn btn-sm"),
content=actions_menu
)
return dropdown
Developer Reference
This section contains technical details for developers working on the Dropdown component itself.
State
The Dropdown component maintains its state via DropdownState:
| Name | Type | Description | Default |
|---|---|---|---|
opened |
boolean | Whether dropdown is open | False |
Commands
Available commands for programmatic control:
| Name | Description |
|---|---|
close() |
Closes the dropdown |
click() |
Handles click events (toggle or close behavior) |
Command details:
close(): SetsopenedtoFalseand returns updated contentclick(): Receivescombination,is_inside, andis_buttonparameters- If
is_buttonisTrue: toggles the dropdown - If
is_insideisFalse: closes the dropdown
- If
Public Methods
| Method | Description | Returns |
|---|---|---|
toggle() |
Toggles open/closed state | Content tuple |
close() |
Closes the dropdown | Content tuple |
render() |
Renders complete component | Div |
Constructor Parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
parent |
Instance | Parent instance (required) | - |
content |
Any | Content to display in dropdown | None |
button |
Any | Trigger element | None |
_id |
str | Custom ID for the instance | None |
position |
str | Vertical position: "below"/"above" | "below" |
align |
str | Horizontal align: "left"/"right"/"center" | "left" |
High Level Hierarchical Structure
Div(id="{id}")
├── Div(cls="mf-dropdown-wrapper")
│ ├── Div(cls="mf-dropdown-btn")
│ │ └── [Button content]
│ └── Div(id="{id}-content", cls="mf-dropdown mf-dropdown-{position} mf-dropdown-{align} [is-visible]")
│ └── [Dropdown content]
├── Keyboard(id="{id}-keyboard")
│ └── ESC → close command
└── Mouse(id="{id}-mouse")
└── click → click command
Element IDs
| Name | Description |
|---|---|
{id} |
Root dropdown container |
{id}-content |
Dropdown content panel |
{id}-keyboard |
Keyboard handler component |
{id}-mouse |
Mouse handler component |
Note: {id} is the Dropdown instance ID (auto-generated or custom _id).
Internal Methods
| Method | Description |
|---|---|
_mk_content() |
Renders the dropdown content panel |
on_click() |
Handles click events from Mouse component |
Method details:
-
_mk_content():- Builds CSS classes based on
positionandalign - Adds
is-visibleclass whenopenedisTrue - Returns a tuple containing the content
Div
- Builds CSS classes based on
-
on_click(combination, is_inside, is_button):- Called by Mouse component on click events
is_button:Trueif click was on the buttonis_inside:Trueif click was inside the dropdown- Returns updated content for HTMX swap