Files
MyFastHtml/docs/Dropdown.md

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 Mouse component detects clicks and sends is_inside and is_button parameters
  • If is_button is true, the dropdown toggles
  • If is_inside is false (clicked outside), the dropdown closes
  • The Keyboard component 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(): Sets opened to False and returns updated content
  • click(): Receives combination, is_inside, and is_button parameters
    • If is_button is True: toggles the dropdown
    • If is_inside is False: closes the dropdown

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 position and align
    • Adds is-visible class when opened is True
    • Returns a tuple containing the content Div
  • on_click(combination, is_inside, is_button):

    • Called by Mouse component on click events
    • is_button: True if click was on the button
    • is_inside: True if click was inside the dropdown
    • Returns updated content for HTMX swap