How to do a Multi-Level Dropdowns

Well this question has been raised before several times. I am assuming the functionality is still not available…

Example of a multiple level dropdown

I am not an expert of html, the biggest question which keeps bugging me is that I can reuse many different html elements online. I do not know how to to fetch these possible values. Is there a way to communicate with python with the hyperlink? Or is Javascript needed?

Have you checked out dash-bootstrap-components? I suspect this would work as DropdownMenu could be a child-member who’s parent is also of the same type. I haven’t tried it but it seems possible.

1 Like

Thank you for your solution. Indeed it is working.

html.Div(
dbc.DropdownMenu(
label="Menu",
children=[
    dbc.DropdownMenu(
    children=[
        dbc.DropdownMenuItem("Item 1"),
        dbc.DropdownMenuItem("Item 2"),
    ],
    ),
    dbc.DropdownMenuItem("Item 2"),
    dbc.DropdownMenuItem("Item 3"),
],
))

Will give a dropdown within the dropdown with the default dbc.themes.BOOTSTRAP. There is only one problem of the inner dropdown does not seem to close/collapse itself.

Yeah…i haven’t tried doing this before - you may need to create a couple callbacks to handle the closing of the inner dropdown(s).

Hi, I was wondering if you manage to figure out some way to make the inner dropdown collapse.

Hey all. I was also wondering if this issue has been solved or a workaround found??

I’m trying to get nested menus going with dbc.Dropdownmenu. Same issue that I can’t get the parent menu to collapse when you click on an element in a nested dropdown.

I’ve tried being cheeky with an client side javascript query in my Dash coded. I can get it to change focus to a different element on detection of a click event from a nested item, BUT it still doesn’t collapse the first menu which is really annoying.

My thinking is this is probably best solved with an embedded client side javascript callback, I just can’t seem to get it to work. One thought was emulating the tab key (which definitely collapses the menu if you physcially press it) but I thought .focus() was basically the same as this

Note also to get JS clientside callbacks to work in Dash you can just declare them, but you also need to call them manually in your dash code.

in dash main app code somewhere, I also call this, to initialise it:

callback_clientside_menublur()

def callback_clientside_menublur():
app.clientside_callback(
“”"
function() {
//alert(“Test menu nesting bullshit”);
document.activeElement.blur();
document.getElementById(“about-button”).focus();
document.getElementById(“about-button”).blur();
//(“input”).trigger( ‘keypress’, [{preventDefault:function(){},keyCode:9}] );

        return {};
    }
    """,
    Output('blur-hidden-div-menu', 'style'),
    # catch click event for nested menu
    Input('nest-test1', 'n_clicks'),
    Input('nest-test2', 'n_clicks'),
    prevent_initial_call=True
)
return

This will physically refocus on an element of my choice upon detecting a nested menu click BUT it still doesn’t shut the damn parent dropdown menu :frowning: I don’t really know what I’m doing with javascript so if any gurus out there can comment it would be appreciated

Give this a try:

import dash
import dash_bootstrap_components as dbc

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])


level3_items = [
    dbc.DropdownMenuItem("Item 5"),
    dbc.DropdownMenuItem("Item 6"),
]

level2_items = [
    dbc.DropdownMenuItem("Item 3"),
    dbc.DropdownMenuItem("Item 4"),
    dbc.DropdownMenu(level3_items),
]

level1_items = [
    dbc.DropdownMenuItem("Item 1"),
    dbc.DropdownMenuItem("Item 2"),
    dbc.DropdownMenu(level2_items),
]


app.layout = dbc.Container(
    [
        dbc.DropdownMenu(
            label="Menu", bs_size="lg", children=level1_items, className="mb-3",
        ),
    ],
    fluid=True,
)

if __name__ == "__main__":
    app.run_server(debug=True)

dropdown

Thanks AnnMarieW

It’s cool I didn’t realise you can go as many levels as you want. The issue is, when you click on any nested menu item, the entire menu still stays open (i.e. the parent).

So it’s still not collapsing the upstream menu.

I.e. when you click item 3, I want the main menu also to close (but it doesn’t). I can catch the input event, and I can change focus to a different component, but the damn thing stays open unless you physically click the mouse somewhere else on the page (or on the root menu), or hit tab!

Any thoughts welcomed!

Hi @dan_baker

Oh, I see what you mean. When you click on an item, it will close the current menu and sub-menus below, but not the ones above.

Personally, I don’t think it’s a bad UI, but I sense that you find this annoying :wink:

Here is one work around. It will close the all the sub-menus, but then you have to click on the menu button twice to get the sub-menus to show again. (And that might be even more annoying?) Or maybe you know a solution for that.

import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])


level3_items = [
    dbc.DropdownMenuItem("Item 5", id="item_5"),
]
level2_items = [
    dbc.DropdownMenuItem("Item 3", id="item_3"),
    dbc.DropdownMenuItem("Item 4", id="item_4"),
    dbc.DropdownMenu(level3_items),
]
level1_items = [
    html.Div(
        id="submenu",
        children=[
            dbc.DropdownMenuItem("Item 1"),
            dbc.DropdownMenuItem("Item 2"),
            dbc.DropdownMenu(level2_items),
        ],
    )
]

app.layout = dbc.Container(
    html.Div(
        id="menu",
        children=dbc.DropdownMenu(label="Menu", bs_size="lg", children=level1_items,),
    )
)


@app.callback(
    Output("submenu", "className"),
    Input("item_3", "n_clicks"),
    Input("item_4", "n_clicks"),
    Input("item_5", "n_clicks"),
    Input("menu", "n_clicks"),
)
def hide_show_submenu(*_):
    ctx = dash.callback_context
    input_id = ctx.triggered[0]["prop_id"].split(".")[0]
    return "d-block" if input_id == "menu" else "d-none"


if __name__ == "__main__":
    app.run_server(debug=True)

And you may have to add this to the css file:

.dropdown-menu {
    min-width: 0rem;
    padding: 0rem 0;
1 Like

Thanks Ann

That definitely hides the menu for me by changing the className, but it doesn’t just close the menu.

I’m using each child/subschild as a link to load something, so basically once anything is clicked (even a nested sub child) I want the entire menu structure to collapse, just like if you click somewhere else on the screen or hit tab.

Still no luck getting this to happen. I’m wondering what the low level event is that tells the menus to collapse, essentially I just need to call that somehow, but I suspect it’s quite hard and possibly not possible.

Thanks for all your help
Dan

Hi @dan_baker

Did you run the code? Yes, it works by hiding the sub-menus, but I think it does what you want.

I think the best solution would be to have access to the isOpen property in the Dropdown menu. I’ll see if I can make it work. It would be a fairly minor pull request in dash-bootstrap-components.

You might also be able to set this prop in a clientside callback.

dropdown2

Hi Ann, so sorry for the delay on this. The truth is I have tried lots of things you suggested but actually I still have not solved the problem and have lived with it as MVP while I brute force other features of my project.

Can you fire an event to close the menu somehow with isOpen?

The example you showed above is effectively all I really need to do, but I can’t replicate it. Any dodgy work-around that makes it work is fine in my book. Or can you share the exact code snippet for that example?

Dan

The code for the gif shown above is in this post. It’s a complete app that you should be able to just copy and paste. I just ran it again and it still works for me. What happens when you run it?