Can we add Font Awesome icons in tabs name alongside the name?

Hello all!

Is it possible to add Font Awesome icons in tabs name alongside the name (pn.Tabs)?
Something like this:
image

Thank you!

I think it can not be included inside the tabs, because the input are sanitized or something like that. Adding the icons with javascript does the trick, but I do not know if it is a good way

import panel as pn

css_files = ["https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css"]

pn.extension(css_files=css_files)

html = pn.pane.HTML('')

def update_icons():
    html.object =     """
    <script>
    document.getElementsByClassName('bk-tab')[0].innerHTML = '<i class="fa fa-address-book"></i> Tab 01'
    document.getElementsByClassName('bk-tab')[1].innerHTML = '<i class="fa fa-anchor"></i> Tab 01'
    document.getElementsByClassName('bk-tab')[2].innerHTML = '<i class="fa fa-balance-scale"></i> Tab 01'
    </script>
    """
    html.param.trigger('object')

pn.state.onload(update_icons)

t = pn.Tabs(('<i class="fa-solid fa-mug-saucer"></i> tab001','Hola001'),
        ('tab002','Hola002'),
        ('tab003','Hola003'))

pn.Column(t,html).servable()

image

Thank you for your suggestion, @nghenzi
It seems to work for tabs aligned “above”

image

However, when the tabs are aligned “left”, the text is not properly visible

image

One other aspect is using the html component - it adds a new row after the section dedicated to the tabs


I am wondering if there is a possibility to hide it somehow, to not affect the intended design.

Here is the code, for reference:

import panel as pn

css_files = ["https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"]
pn.extension(css_files=css_files)

html = pn.pane.HTML('')

def update_icons():
    html.object =     """
    <script>
    document.getElementsByClassName('bk-tab')[0].innerHTML = '<i class="fa-solid fa-house"></i> Home'
    document.getElementsByClassName('bk-tab')[1].innerHTML = '<i class="fa-solid fa-percent"></i> Failure rate'
    document.getElementsByClassName('bk-tab')[2].innerHTML = '<i class="fa-solid fa-file-waveform"></i> Reports'
    </script>
    """
    html.param.trigger('object')

pn.state.onload(update_icons)

tabs = pn.Tabs(
    ("Tab1", pn.pane.Str("Space reserved for tab 1", width=500, height=500)),
    ("Tab2", pn.pane.Str("Space reserved for tab 2", width=500, height=500)),
    ("Tab3", pn.pane.Str("Space reserved for tab 3", width=500, height=500)),
    tabs_location='left'
)

template = pn.template.FastListTemplate(
    site="Multiple tabs",
    title="",
    main=pn.Column(tabs, html)
)

template.servable()

I did not know the left orientation :), it is great. The problem is the width is calculated by panel with the original text of the tabs defined in python. One option is write the text with extra spaces in the python definition of the tabs. The second options, as shown below, is emit a resize signal to the browser, then the width of the tabs headers is calculated again. I added the html component to the header with “visible =False”. I do not use much the fast list template, but it seeems it add a new card for each component.

import panel as pn

css_files = ["https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"]
pn.extension(css_files=css_files)

html = pn.pane.HTML('', visible=False, height=0)

def update_icons():
    html.object =     """
    <script>
    let tab0 = document.getElementsByClassName('bk-tab')[0]
    tab0.innerHTML = '<i class="fa-solid fa-house"></i>&nbsp;&nbsp;&nbsp;' + tab0.innerHTML;
    let icon0 = document.getElementsByClassName('fa-solid')[0]
    icon0.addEventListener('click', function t0(){tab0.click()}  )    

    let tab1 = document.getElementsByClassName('bk-tab')[1]
    tab1.innerHTML = '<i class="fa-solid fa-percent"></i>&nbsp;&nbsp;&nbsp;' + tab1.innerHTML;
    let icon1 = document.getElementsByClassName('fa-solid')[1]
    icon1.addEventListener('click', function t1(){tab1.click()}  )    

    let tab2 = document.getElementsByClassName('bk-tab')[2]
    tab2.innerHTML = '<i class="fa-solid fa-file-waveform"></i>&nbsp;&nbsp;&nbsp;' + tab2.innerHTML;
    let icon2 = document.getElementsByClassName('fa-solid')[2]
    icon2.addEventListener('click', function t2(){tab2.click()}  )    

    
    window.dispatchEvent(new Event('resize'))
    </script>
    """
    html.param.trigger('object')

pn.state.onload(update_icons)

tabs = pn.Tabs(
    ("Home", pn.pane.Str("Space reserved for tab 1", width=500, height=500, margin=50)),
    ("Failure rate", pn.pane.Str("Space reserved for tab 2", width=500, height=500)),
    ("Reports", pn.pane.Str("Space reserved for tab 3", width=500, height=500)),
    tabs_location='left'
)

template = pn.template.FastListTemplate(
    site="Multiple tabs",
    title="",
    main= tabs
)

template.header.append(html)

template.servable()

There is some problem with the alignment yet , and the new added icons inside the i elements does not trigger a tab change when they are clicked just in the middle of the icon. I do not know the details yet, but it seems the event listeners are not attached to these new i elements.

@nghenzi, thank you for trying - I will give up on having the left tabs for now.
Here is what I’ve been trying:

import panel as pn

css = """
.tab_active,.tab_inactive {
    font-size: 18px;
}

.tab_active {
    color: #5f9ed1;
}
"""

css_files = ["https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"]
pn.extension(
    css_files=css_files,
    raw_css=[css]
)

tab1_icon = pn.pane.HTML("<i class='fa-solid fa-house tab_active'>")
tab2_icon = pn.pane.HTML("<i class='fa-solid fa-percent tab_active'>")
tab3_icon = pn.pane.HTML("<i class='fa-solid fa-file-waveform tab_active'>")

icons_col = pn.Column(
    tab1_icon,
    tab2_icon,
    tab3_icon
)

tab_section = pn.Row()


def tab1():
    tab_section[:] = [pn.pane.Str("Space reserved for tab 1", width=500, height=500)]
    tab1_icon.object = "<i class='fa-solid fa-house tab_active'>"
    tab2_icon.object = "<i class='fa-solid fa-percent tab_inactive'>"
    tab3_icon.object = "<i class='fa-solid fa-file-waveform tab_inactive'>"
    return tab_section


def tab2():
    tab_section[:] = [pn.pane.Str("Space reserved for tab 2", width=500, height=500)]
    tab1_icon.object = "<i class='fa-solid fa-house tab_inactive'>"
    tab2_icon.object = "<i class='fa-solid fa-percent tab_active'>"
    tab3_icon.object = "<i class='fa-solid fa-file-waveform tab_inactive'>"
    return tab_section


def tab3():
    tab_section[:] = [pn.pane.Str("Space reserved for tab 3", width=500, height=500)]
    tab1_icon.object = "<i class='fa-solid fa-house tab_inactive'>"
    tab2_icon.object = "<i class='fa-solid fa-percent tab_inactive'>"
    tab3_icon.object = "<i class='fa-solid fa-file-waveform tab_active'>"
    return tab_section


tabs_object = pn.Tabs(
    ("Home", tab1()),
    ("Failure rate", tab2()),
    ("Reports", tab3()),
    tabs_location='left'
)

template = pn.template.FastListTemplate(
    site="Multiple tabs",
    title="",
    main=pn.Column(
        pn.Row(
            icons_col,
            tabs_object
        )

    )
)

template.servable()

but at start it gives the correct active tab, although the icon is updated with the css from the latest tab and changing the tabs doesn’t update the icons and tab section, as I hoped.
image
Probably I am somewhat close, but not sure…

The tabs are really hard of customize, My final try is below. It is working, but I could not make it nicer due to the complexity of css.

import panel as pn

css = """
#main{ 
    padding-top : 0px !important;
    padding-left : 0px !important;
    
}
.bk {
  //  display: block !important;
}
.bk .bk-headers{
    top: 50px !important;
}

.bk-root .bk-tabs-header.bk-left .bk-tab {
    border-radius: 4px 0 0 4px;
    width: 230px !important;
//    color: red;
}
.bk-tab:hover{
    color: red;
    width: 300px !important;
    left: 10px;
    position: relative;
    z-index: 99999999999 !important;   
}
bk-root .bk-headers :hover{
    //color: #007bff !important;
}
.bk .bk-headers-wrapper:has(>.bk .bk-tab) :hover{
 //   width: 200px !important;
 //   color: red;
 //   background: blue;
 //   left: 0px;
 //   z-index:9999;
}
.tab_content:first-child {
    left:10px !important;
    border-style: solid;
   // background: red !important;
}
"""
css_files = ["https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"]

pn.config.sizing_mode = 'stretch_width'
pn.extension(css_files=css_files, raw_css=[css])

html = pn.pane.HTML('', visible=False, height=0)

def update_icons():
    html.object =     """
    <script>
    let tab0 = document.getElementsByClassName('bk-tab')[0]
    tab0.innerHTML = '<i class="fa-solid fa-house"></i>&nbsp;&nbsp;&nbsp;' + tab0.innerHTML;
    let icon0 = document.getElementsByClassName('fa-solid')[0]
    icon0.addEventListener('click', function t0(){tab0.click()}  )    
    let tab1 = document.getElementsByClassName('bk-tab')[1]
    tab1.innerHTML = '<i class="fa-solid fa-percent"></i>&nbsp;&nbsp;&nbsp;' + tab1.innerHTML;
    let icon1 = document.getElementsByClassName('fa-solid')[1]
    icon1.addEventListener('click', function t1(){tab1.click()}  )    
    let tab2 = document.getElementsByClassName('bk-tab')[2]
    tab2.innerHTML = '<i class="fa-solid fa-file-waveform"></i>&nbsp;&nbsp;&nbsp;' + tab2.innerHTML;
    let icon2 = document.getElementsByClassName('fa-solid')[2]
    icon2.addEventListener('click', function t2(){tab2.click()}  )    
    
    window.dispatchEvent(new Event('resize'))
    </script>
    """
    html.param.trigger('object')

pn.state.onload(update_icons)

tabs = pn.Tabs(
    ("Home", pn.pane.Str("Space reserved for tab 1", height=500)),
    ("Failure rate", pn.pane.Str("Space reserved for tab 2", height=500)),
    ("Reports", pn.pane.Str("Space reserved for tab 3",  height=500)),
    tabs_location='left', css_classes=['tab_content']
)

template = pn.template.BootstrapTemplate(
    site="Multiple tabs",
    title="",
    main= tabs
)

template.header.append(html)

template.servable()

3 Likes

Maybe someone should request improved tabs as a feature request on Github. Or we should try to build some custom ones using ReactiveHTML.

1 Like

Done (my very first issue to panel GitHub :slight_smile:)

I hope the text is good enough.