Nested tabs with routing

when I started adding Ansible content into new host detail page, I found out the page currently does not support routing for the second level of tabs (tabs within tabs). I believe we should invest our time in having routed subtabs as it is not difficult to achieve and it gives us a good control over the active tabs and therefore content that is displayed. Based on information from @MariSvirik, reflecting tab changes in URL is valuable from a usability point of view.

There is currently no clear consensus on the scheme that we would use. Traditionally, tabs are addressed with # sign, which we currently have on smart proxies show page and new version of content view pages in lab features. Examples: /smart_proxies/4-centos-proxy#properties, /labs/content_views/2#versions

However, using # multiple times is not possible so we cannot have /hosts/:id#tab#subtab. It is possible to use query params instead of the second # and that is also what RoutedTabs component in Katello mentions, so the resulting URL would be /host/:id#tab?subtab=foo.

My personal preference is to replace # with regular path segments in the same way as the Katello pages currently have it, so we would end up with /hosts/:id/:tab/:subtab. I have opened a PR which shows how that might look like. There were also suggestions to push # one level down and have it for the last level of tabs, that is /host/:id/:tab#subtab

What do you think we should use?

  • /hosts/:id#tab?subtab=foo
  • /hosts/:id/:tab#subtab
  • /hosts/:id/:tab/:subtab
  • something else, I’ll explain in a comment below

0 voters


I’m inclined to use :tab#subtab, but I think it is not that simple, I think we need to always take in account what does the tab really mean, if it is more of a standalone page, or just content division.

If the tab splits larger chunks of funcionality, it should use /:tab on the other hand if it’s just a way how to visually divide content that would even make sense on one page, I think it should be #tab

For host detail I think it make sense as /:tab#subtab although it migh not be the case for all pages.
As a subthough I think if we are splitting larger features by tabs, we are doing tabs wrong, but I do not know enough about UX.

I would suggest sticking with only #subtab as is the case for some existing pages.
Not all pages use client side routing yet, and fragments (#something) are the only part of a url that is not sent to the server or trigger a page reload when navigated to. This is the standard method for indicating which part of a page should be displayed. We also already have code that knows how to handle finding subtabs and navigating to them on legacy pages.
If needed to disambiguate two subtabs with the same name on different tabs, you could use #tab-subtab as a unique identifier for the specific subtab.


Could you point me to an existing Foreman page has 2 levels of tabs and uses only #subtab for a (reliable) navigation? I did not find any.

Even though the chance of name collision is not high, avoiding clash on subtab name would require a synchronization across all plugins that add tabs. Developer adding a tab would have to be aware of all other names to choose a name that is not yet used. Same goes for reviewers. And if we find out that 2 plugins added a tab with the same name when testing the next Foreman RC, how do we decide which plugin should rename its tab?

The smart proxy page has this for puppet CA, when selecting “certificate” or “autosign” from the action dropdown on the index or show page. This was the original reason that code was created. There may be other places as well that use it.

I’d be fine with normalizing on #tab-subtab if we want to make sure we don’t run into this issue.

I’d really hate that. #... should be id of element to be selected, if it is not, I’d rather have any other solution than having custom hybrid that only works if our custom JS takes care of it, but looks like standartized solution :slight_smile:

After discussing with UX enthusiast friend of mine, the Host page is not really having sub-tabs, but it has sub-menu (first level of tabs) that should be in url, and only the sub tabs are real tabs. This should also be differentiated in the UI as the menu should look more like menu and the tabs within the sub-menu items should be displayed as tabs. But it’s just external opinion obviously and probably should be left to more experienced people like @MariSvirik

Though I liked that opinion and so I quite strongly incline to /<tab(host-menu-item)>#<subtab(real tab)> in Host detail page usecase.

My suggestion is to use #tab-subtab as the id of the subtab to be selected if we’re concerned about name conflicts, not to break it down to parts in our code and select them. Finding the parent tab of the tab to display should be easy to do in case there is one and shouldn’t rely on having the #tab part correctly set.

I disagree, some of the tabs don’t have subtabs, and in any case the data is loaded when the whole page is loaded, even if it isn’t displayed directly.

This would require front end routing to know which paths are for tabs and what are for subactions (think about endpoints like /hosts/4/externalNodes or /hosts/4/clone which are endpoints vs. /hosts/4/ansible or /hosts/4/details which may be tab names).
It would also make our code for finding the right tab name more complex, since If we stick with only using the fragment, finding what tab to display is a simple uri.fragment(), no need to combine tab = uri.path().split('/')[-1]; subtab = uri.fragment() and handle all edge cases of mismatches. Just find the tab with the right id, display it, and if it has a parent, display that as well.

You raised a good point about actions vs. tabs. But we are currently using ‘/’ between host id and tab (e.g. /content_hosts/53/tasks) So how come we haven’t encountered that problem yet?

What about this:
hosts/ host id/ #tab-subtab
or just
hosts/ host id/ tab#subtab

Finding the parent tab of the tab to display will not be difficult as long as we maintain an information about parent for each subtab, so we would need a custom logic for that. And what should we use for tabs that do not have subtabs?

True for tabs (on host detail page) and to change that is a very simple. For subtabs, it is possible today to load only when the tab becomes active.

Looking at the poll, 4 out of 7 who voted chose the first option. 3 chose something different, yet I see only 2 additional suggestions (feel free to correct/complete me if I misunderstood):

@ezr-ondrej suggests /tab#subtab, because he considers the first level of tabs to be a menu

@tbrisker suggests #tab-subtab as an identifier for the subtab and infer the parent tab from that

So, how do we move forward?

My vote was one of those something else, to be specific the #tab-subtab. If we have 7 votes only, people may not care that much, Pick one of the two most popular solution unless one of them has major technical drawbacks. Perhaps PoC on the one you picked will tell us whether it’s good or whether we should go with the other one.

I also think that a unique element ID is best. In HTML/Javascript the element must be unique anyway. That feels most natural to me. So :+1: to what @Marek_Hulan said.

I liked but did not vote as I have no technical experience that helps here!

But from working with Icinga Web 2 where the developers did spend much effort on making URLs navigate-able back and forth, shareable and so on and where you can feel the pain if some module developer did not care this much or failed for some other reason to do so, I am a big fan of having a predictable route to some information that always works.

So while I do not care about the technical implementation, I totally agree on it is worth the effort!

Thank you all for your input. Right now, there are 2 POCs: one for the /:tab/:subtab and the other for #tab-subtab

1 Like

My apologies for joining this discussion only now, but better late than never.

I have added another POC, which adds HashRouter - this is the way how react-router handles hash navigation. The navigation looks like #/:tab/:subtub. The benefit here is that we won’t need to manipulate the history object, react router do it for us.