Question: I'm trying to filter data using multiple dropdown bars within a plotly dashboard. There are 5 dropdown options in total. I want the first 3 to operate indepently, while the last two should be chained, both ways, to the first 3. Specifically, the features that I'm aiming to implement are:  A default of all values should always be the initial starting point  The first 3 options (Year, Season and Month) should act independently. As in, any combination of these 3 can be added to the output. If one item is selected, the output should be updated with those values. However, if an item is selected from another dropdown, those values should be added to the output. Example below in i).  Option 4-5 (temp and prec) should be chained, both ways, to the first three dropdown options (Year, Season and Month). This should be reversible or both ways too. If one of the first 3 dropdown options is selected, the table output should be updated with those values and the dropdown lists should be reduced to only allow the user to pick from those values. Example below in ii).   To provide concrete examples; i) 2012 is selected from Year in the first dropdown option. The table output displays the relevant values. The user should be able to select any subsequent values in the Year dropdown list (functional). However, if the user wants to also see Spr values from the second dropdown option, that data should be added to the output. ii) For the 4-5 dropdown options which should be chained to first 3, if Hot and Mild are selected in temp and Wet is selected in prec, then the dropdown lists in the first three options should be reduced to: Year = 2013, 2015; Season = Spr, Fall; Month = Apr, Jun, Oct, Dec. import pandas as pd from dash import Dash, dcc, html, Input, Output, dash_table import dash_bootstrap_components as dbc from itertools import cycle import random Year = cycle(['2012','2013','2014','2015']) Season = cycle(['Win','Spr','Sum','Fall']) Month = cycle(['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']) temp_group = cycle(['Hot','Cold','Mild']) prec_group = cycle(['Dry','Wet']) df = pd.DataFrame(index = range(20)) df['option1'] = [next(Year) for count in range(df.shape[0])] df['option2'] = [next(Season) for count in range(df.shape[0])] df['option3'] = [next(Month) for count in range(df.shape[0])] df['option4'] = [next(temp_group) for count in range(df.shape[0])] df['option5'] = [next(prec_group) for count in range(df.shape[0])] option1_list = sorted(df['option1'].unique().tolist()) option2_list = df['option2'].unique().tolist() option3_list = df['option3'].unique().tolist() option4_list = sorted(df['option4'].unique().tolist()) option5_list = sorted(df['option5'].unique().tolist()) app = Dash(__name__) app.layout = html.Div([ dbc.Card( dbc.CardBody([ dbc.Row([ dbc.Col([ html.P("Option 1"), html.Div([ dcc.Dropdown(id='option1_dropdown', options=option1_list, value=[], placeholder='All', multi=True, clearable=True), ], style={'width': '100%', 'display': 'inline-block'}) ]), dbc.Col([ html.P("Option 2"), html.Div([ dcc.Dropdown(id='option2_dropdown', options=option2_list, value=[], placeholder='All', multi=True, clearable=True), ], style={'width': '100%', 'display': 'inline-block'}) ]), dbc.Col([ html.P("Option 3"), html.Div([ dcc.Dropdown(id='option3_dropdown', options=option3_list, value=[], placeholder='All', multi=True, clearable=True), ], style={'width': '100%', 'display': 'inline-block'}) ]), dbc.Col([ html.P("Option 4"), html.Div([ dcc.Dropdown(id='option4_dropdown', options=option4_list, value=[], placeholder='All', multi=True, clearable=True), ], style={'width': '100%', 'display': 'inline-block'}) ]), dbc.Col([ html.P("Option 5"), html.Div([ dcc.Dropdown(id='option5_dropdown', options=option5_list, value=[], placeholder='All', multi=True, clearable=True), ], style={'width': '100%', 'display': 'inline-block'}) ]), ], align='center'), ]), color='dark' ), dbc.Card( dbc.CardBody([ dbc.Row([ html.Div([ html.Div(id='dd-output-container') ]) ], align='center'), ]), color='dark' ), dbc.Card( dbc.CardBody([ dbc.Row([ html.Div([ dash_table.DataTable( id='table_container', data=df.to_dict('records') ) ]) ], align='center'), ]), color='dark' ) ]) @app.callback( Output('table_container', 'data'), [Input('option1_dropdown', 'value'), Input('option2_dropdown', 'value'), Input('option3_dropdown', 'value'), Input('option4_dropdown', 'value'), Input('option5_dropdown', 'value') ]) def set_dropdown_options(value1, value2, value3, value4, value5): if not value1 or value1 == 'All': value1 = option1_list if not value2 or value2 == 'All': value2 = option2_list if not value3 or value3 == 'All': value3 = option3_list if not value4 or value4 == 'All': value4 = option4_list if not value5 or value5 == 'All': value5 = option5_list ddf = df.query('option1 == @value1 and ' 'option2 == @value2 and ' 'option3 == @value3 and ' 'option4 == @value4 and ' 'option5 == @value5', engine='python') return ddf.to_dict('records') # ====== Using this as a way to view the selections @app.callback( Output('dd-output-container', 'children'), [Input('option1_dropdown', 'value'), Input('option2_dropdown', 'value'), Input('option3_dropdown', 'value'), Input('option4_dropdown', 'value'), Input('option5_dropdown', 'value') ]) def selection(value1, value2, value3, value4, value5): # If value lists are empty or equal to the default of 'All', use the initial df values if not value1 or value1 == 'All': value1 = option1_list if not value2 or value2 == 'All': value2 = option2_list if not value3 or value3 == 'All': value3 = option3_list if not value4 or value4 == 'All': value4 = option4_list if not value5 or value5 == 'All': value5 = option5_list ddf = df.query('option1 == @value1 and ' 'option2 == @value2 and ' 'option3 == @value3 and ' 'option4 == @value4 and ' 'option5 == @value5', engine='python') return if __name__ == '__main__': app.run_server(debug=True, dev_tools_hot_reload = False)  Edit 2: Is there a way to include the original column names without converting to using an integer suffix? Year = cycle(["2012", "2013", "2014", "2015"]) Season = cycle(["Win", "Spr", "Sum", "Fall"]) Month = cycle( ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] ) temp_group = cycle(["Hot", "Cold", "Mild"]) prec_group = cycle(["Dry", "Wet"]) df = pd.DataFrame(index=range(20)) df["Year"] = [next(Year) for count in range(df.shape[0])] df["Season"] = [next(Season) for count in range(df.shape[0])] df["Month"] = [next(Month) for count in range(df.shape[0])] df["Temp"] = [next(temp_group) for count in range(df.shape[0])] df["Prec"] = [next(prec_group) for count in range(df.shape[0])] Year_list = sorted(df["Year"].unique().tolist()) Season_list = df["Season"].unique().tolist() Month_list = df["Month"].unique().tolist() Temp_list = sorted(df["Temp"].unique().tolist()) Prec_list = sorted(df["Prec"].unique().tolist()) df = df.rename(columns = {'Year':'option1', 'Season':'option2', 'Month':'option3', 'Temp':'option4', 'Prec':'option5'}) app = Dash(__name__) app.layout = html.Div( [ dbc.Card( dbc.CardBody( [ dbc.Row( [ dbc.Col( [ html.P("Year"), html.Div( [ dcc.Dropdown( id="Year_dropdown", options=Year_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Season"), html.Div( [ dcc.Dropdown( id="Season_dropdown", options=Season_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Month"), html.Div( [ dcc.Dropdown( id="Month_dropdown", options=Month_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Temp"), html.Div( [ dcc.Dropdown( id="Temp_dropdown", options=Temp_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Prec"), html.Div( [ dcc.Dropdown( id="Prec_dropdown", options=Prec_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), ], align="center", ), ] ), color="dark", ), dbc.Card( dbc.CardBody( [ dbc.Row( [html.Div([html.Div(id="dd-output-container")])], align="center" ), ] ), color="dark", ), dbc.Card( dbc.CardBody( [ dbc.Row( [ html.Div( [ dash_table.DataTable( id="table_container", data=df.to_dict("records") ) ] ) ], align="center", ), ] ), color="dark", ), ] ) df = df.rename(columns = {'Year':'option1', 'Season':'option2', 'Month':'option3', 'Temp':'option4', 'Prec':'option5'}) def construct_query(filter_values): additive_clauses = list() subtractive_clauses = list() for i, filter_value in enumerate(filter_values): if filter_value and filter_value != "All": clause = f"option{i + 1} == @value{i + 1}" if i <= 3: additive_clauses.append(clause) else: subtractive_clauses.append(clause) if len(additive_clauses) > 0 or len(subtractive_clauses) > 0: additive_section = " or ".join(additive_clauses) subtractive_clauses = " and ".join(subtractive_clauses) if additive_section and subtractive_clauses: query = f"({additive_section}) and {subtractive_clauses}" else: query = additive_section or subtractive_clauses return query @app.callback( [ Output("Year_dropdown", "options"), Output("Season_dropdown", "options"), Output("Month_dropdown", "options"), ], [ Input("Temp_dropdown", "value"), Input("Prec_dropdown", "value"), ], ) def update_additive_options(value4, value5): query = None option4_query = "option4 == @value4" option5_query = "option5 == @value5" if value4 and value4 != "All" and value5 and value5 != "All": query = f"{option4_query} and {option5_query}" elif value4 and value4 != "All": query = option4_query elif value5 and value5 != "All": query = option5_query if query: df_filtered = df.query( query, engine="python", ) else: df_filtered = df return ( sorted(df_filtered["option1"].unique().tolist()), df_filtered["option2"].unique().tolist(), df_filtered["option3"].unique().tolist(), ) @app.callback( [Output("Temp_dropdown", "options"), Output("Prec_dropdown", "options")], [ Input("Year_dropdown", "options"), Input("Season_dropdown", "options"), Input("Month_dropdown", "options"), ], ) def update_subtractive_options(value1, value2, value3): query = None additive_clauses = [] for i, filter_value in enumerate([value1, value2, value3]): if filter_value and filter_value != "All": clause = f"option{i + 1} == @value{i + 1}" additive_clauses.append(clause) if len(additive_clauses) > 0: query = " or ".join(additive_clauses) if query: df_filtered = df.query( query, engine="python", ) else: df_filtered = df return ( sorted(df_filtered["option4"].unique().tolist()), sorted(df_filtered["option5"].unique().tolist()), ) @app.callback( Output("table_container", "data"), [ Input("Year_dropdown", "value"), Input("Season_dropdown", "value"), Input("Month_dropdown", "value"), Input("Temp_dropdown", "value"), Input("Prec_dropdown", "value"), ], ) def update_table(value1, value2, value3, value4, value5): query = construct_query(filter_values=[value1, value2, value3, value4, value5]) if query: df_filtered = df.query( query, engine="python", ) else: df_filtered = df return df_filtered.to_dict("records") # ====== Using this as a way to view the selections @app.callback( Output("dd-output-container", "children"), [ Input("Year_dropdown", "value"), Input("Season_dropdown", "value"), Input("Month_dropdown", "value"), Input("Temp_dropdown", "value"), Input("Prec_dropdown", "value"), ], ) def selection(value1, value2, value3, value4, value5): # If value lists are empty or equal to the default of 'All', use the initial df values if not value1 or value1 == "All": value1 = Year_list if not value2 or value2 == "All": value2 = Season_list if not value3 or value3 == "All": value3 = Month_list if not value4 or value4 == "All": value4 = Temp_list if not value5 or value5 == "All": value5 = Prec_list ddf = df.query( "option1 == @value1 and " "option2 == @value2 and " "option3 == @value3 and " "option4 == @value4 and " "option5 == @value5", engine="python", ) return if __name__ == "__main__": app.run_server(debug=True, dev_tools_hot_reload=False) 

Answer: That can be accomplished with plotly dash. The trick is to use or operators between the first three filters to make them additive and and operators between the last two to make them subtractive. The first three options need to be resolved as one block before involving the last two options. Example query structure: (option1 == @value1 or option2 == @value2 or option3 == @value3) and option4 == @value4 and option5 == @value5 I chose to build the query programmatically based on which filters had values to make the or logic work correctly. See the construct_query() function in the example below. def construct_query(filter_values): additive_clauses = list() subtractive_clauses = list() for i, filter_value in enumerate(filter_values): if filter_value and filter_value != "All": clause = f"option{i + 1} == @value{i + 1}" if i <= 3: additive_clauses.append(clause) else: subtractive_clauses.append(clause) if len(additive_clauses) > 0 or len(subtractive_clauses) > 0: additive_section = " or ".join(additive_clauses) subtractive_clauses = " and ".join(subtractive_clauses) if additive_section and subtractive_clauses: query = f"({additive_section}) and {subtractive_clauses}" else: query = additive_section or subtractive_clauses return query  Another challenge is to avoid creating circular callbacks that have the same input and output components. One way to accomplish this is to break large callbacks into multiple separate callbacks so that the inputs and outputs aren't circular. In the example below, I separated the updating of the first three dropdowns into update_additive_options() and the last two dropdowns into update_subtractive_options(). Plotly also describes another way to manage circular callbacks in their advanced-callbacks docs with the context functionality. Example i:  Example ii:  Here is the full version of my code: import pandas as pd from dash import Dash, dcc, html, Input, Output, dash_table import dash_bootstrap_components as dbc from itertools import cycle import random Year = cycle(["2012", "2013", "2014", "2015"]) Season = cycle(["Win", "Spr", "Sum", "Fall"]) Month = cycle( ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] ) temp_group = cycle(["Hot", "Cold", "Mild"]) prec_group = cycle(["Dry", "Wet"]) df = pd.DataFrame(index=range(20)) df["option1"] = [next(Year) for count in range(df.shape[0])] df["option2"] = [next(Season) for count in range(df.shape[0])] df["option3"] = [next(Month) for count in range(df.shape[0])] df["option4"] = [next(temp_group) for count in range(df.shape[0])] df["option5"] = [next(prec_group) for count in range(df.shape[0])] option1_list = sorted(df["option1"].unique().tolist()) option2_list = df["option2"].unique().tolist() option3_list = df["option3"].unique().tolist() option4_list = sorted(df["option4"].unique().tolist()) option5_list = sorted(df["option5"].unique().tolist()) app = Dash(__name__) app.layout = html.Div( [ dbc.Card( dbc.CardBody( [ dbc.Row( [ dbc.Col( [ html.P("Option 1"), html.Div( [ dcc.Dropdown( id="option1_dropdown", options=option1_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Option 2"), html.Div( [ dcc.Dropdown( id="option2_dropdown", options=option2_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Option 3"), html.Div( [ dcc.Dropdown( id="option3_dropdown", options=option3_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Option 4"), html.Div( [ dcc.Dropdown( id="option4_dropdown", options=option4_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), dbc.Col( [ html.P("Option 5"), html.Div( [ dcc.Dropdown( id="option5_dropdown", options=option5_list, value=[], placeholder="All", multi=True, clearable=True, ), ], style={ "width": "100%", "display": "inline-block", }, ), ] ), ], align="center", ), ] ), color="dark", ), dbc.Card( dbc.CardBody( [ dbc.Row( [html.Div([html.Div(id="dd-output-container")])], align="center" ), ] ), color="dark", ), dbc.Card( dbc.CardBody( [ dbc.Row( [ html.Div( [ dash_table.DataTable( id="table_container", data=df.to_dict("records") ) ] ) ], align="center", ), ] ), color="dark", ), ] ) def construct_query(filter_values): additive_clauses = list() subtractive_clauses = list() for i, filter_value in enumerate(filter_values): if filter_value and filter_value != "All": clause = f"option{i + 1} == @value{i + 1}" if i <= 3: additive_clauses.append(clause) else: subtractive_clauses.append(clause) if len(additive_clauses) > 0 or len(subtractive_clauses) > 0: additive_section = " or ".join(additive_clauses) subtractive_clauses = " and ".join(subtractive_clauses) if additive_section and subtractive_clauses: query = f"({additive_section}) and {subtractive_clauses}" else: query = additive_section or subtractive_clauses return query @app.callback( [ Output("option1_dropdown", "options"), Output("option2_dropdown", "options"), Output("option3_dropdown", "options"), ], [ Input("option4_dropdown", "value"), Input("option5_dropdown", "value"), ], ) def update_additive_options(value4, value5): query = None option4_query = "option4 == @value4" option5_query = "option5 == @value5" if value4 and value4 != "All" and value5 and value5 != "All": query = f"{option4_query} and {option5_query}" elif value4 and value4 != "All": query = option4_query elif value5 and value5 != "All": query = option5_query if query: df_filtered = df.query( query, engine="python", ) else: df_filtered = df return ( sorted(df_filtered["option1"].unique().tolist()), df_filtered["option2"].unique().tolist(), df_filtered["option3"].unique().tolist(), ) @app.callback( [Output("option4_dropdown", "options"), Output("option5_dropdown", "options")], [ Input("option1_dropdown", "value"), Input("option2_dropdown", "value"), Input("option3_dropdown", "value"), ], ) def update_subtractive_options(value1, value2, value3): query = None additive_clauses = [] for i, filter_value in enumerate([value1, value2, value3]): if filter_value and filter_value != "All": clause = f"option{i + 1} == @value{i + 1}" additive_clauses.append(clause) if len(additive_clauses) > 0: query = " or ".join(additive_clauses) if query: df_filtered = df.query( query, engine="python", ) else: df_filtered = df return ( sorted(df_filtered["option4"].unique().tolist()), sorted(df_filtered["option5"].unique().tolist()), ) @app.callback( Output("table_container", "data"), [ Input("option1_dropdown", "value"), Input("option2_dropdown", "value"), Input("option3_dropdown", "value"), Input("option4_dropdown", "value"), Input("option5_dropdown", "value"), ], ) def update_table(value1, value2, value3, value4, value5): query = construct_query(filter_values=[value1, value2, value3, value4, value5]) if query: df_filtered = df.query( query, engine="python", ) else: df_filtered = df return df_filtered.to_dict("records") # ====== Using this as a way to view the selections @app.callback( Output("dd-output-container", "children"), [ Input("option1_dropdown", "value"), Input("option2_dropdown", "value"), Input("option3_dropdown", "value"), Input("option4_dropdown", "value"), Input("option5_dropdown", "value"), ], ) def selection(value1, value2, value3, value4, value5): # If value lists are empty or equal to the default of 'All', use the initial df values if not value1 or value1 == "All": value1 = option1_list if not value2 or value2 == "All": value2 = option2_list if not value3 or value3 == "All": value3 = option3_list if not value4 or value4 == "All": value4 = option4_list if not value5 or value5 == "All": value5 = option5_list ddf = df.query( "option1 == @value1 and " "option2 == @value2 and " "option3 == @value3 and " "option4 == @value4 and " "option5 == @value5", engine="python", ) return if __name__ == "__main__": app.run_server(debug=True, dev_tools_hot_reload=False)