Panel 1.4 has just been released! Checkout the release notes and support Panel by giving it a 🌟 on Github.


Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).

import panel as pn

Tabs allow switching between multiple objects by clicking on the corresponding tab header. Tab labels may be defined explicitly as part of a tuple or will be inferred from the name parameter of the tab’s contents. Like Column and Row, Tabs has a list-like API with methods to append, extend, clear, insert, pop, remove and __setitem__, which make it possible to interactively update and modify the tabs.


For details on other options for customizing the component see the layout and styling how-to guides.

  • active (int, default=0): The index of the currently selected tab. Updates when a tab is selected and may also be set programmatically to flip between tabs.

  • dynamic (boolean, default=False): Dynamically populate only the active Tab.

  • closable (boolean, default=False): Whether it should be allowed to close tabs using the GUI, which deletes them from the list of objects.

  • objects (list): The list of objects to display in the Column. Should not generally be modified directly except when replaced in its entirety.

  • tabs_location (str, default=’above’): The location of the tabs relative to the content. Must be one of ‘left’, ‘right’, ‘below’ or ‘above’.


A Tabs layout can either be instantiated as empty and be populated after the fact, or using a list of objects provided as positional arguments. If the objects are not already Panel components they will each be converted to one using the pn.panel conversion method. Unlike other panel Tabs also accepts tuples to specify the title of each tab, if no name is supplied explicitly the name of the underlying object will be used.

from bokeh.plotting import figure

p1 = figure(width=300, height=300, name='Scatter')
p1.scatter([0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 2, 1, 0])

p2 = figure(width=300, height=300, name='Line')
p2.line([0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 2, 1, 0])

tabs = pn.Tabs(('Scatter', p1), p2)

The Tabs objects should never be modified directly. Instead, it is recommended to modify tabs using the provided methods, except when replacing the list of objects entirely. Using the methods ensures that the rendered views of the Tabs are rerendered in response to the change, but even more importantly it ensures the tab titles are kept in sync with the objects. As a simple example we might add an additional widget to the tabs using the append method:

p3 = figure(width=300, height=300, name='Square')
p3.square([0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 2, 1, 0], size=10)

BokehDeprecationWarning: 'square() method' was deprecated in Bokeh 3.4.0 and will be removed, use "scatter(marker='square', ...) instead" instead.

On a live server or in a notebook the tabs displayed above will dynamically expand to include the new tab. To see the effect in a statically rendered page, we will display the tabs a second time:



In addition to being able to modify the objects using methods we can also get and set the currently active tab as an integer, which will update any rendered views of the object:

print( = 0


When enabled the dynamic option ensures that only the active tab is actually rendered, only when switching to a new Tab are the contents loaded. This can be very helpful in a server context or notebook context when displaying a lot of tabs or when rendering the individual objects are very large or expensive to render. Note however that without a live server the contents of the non-active tab will never load:

tabs = pn.Tabs(p1, p2, p3, dynamic=True)


If you want the Tabs to be completely lazy when rendering some output you can leverage a ParamFunction or ParamMethod to ensure that the output is not computed until you navigate to the tab:

import time
import numpy as np

def plot():
    time.sleep(1) # some long running calculation
    xs, ys = np.random.randn(2, 100)
    p = figure(width=300, height=300, name=f'Scatter Seed {}')
    p.scatter(xs, ys)
    return p

p1 = pn.param.ParamFunction(plot, lazy=True, name='Seed 0')
p2 = pn.param.ParamFunction(plot, lazy=True, name='Seed 1')
p3 = pn.param.ParamFunction(plot, lazy=True, name='Seed 2')

tabs = pn.Tabs(p1, p2, p3, dynamic=True)



Tabs may also be initialized as closable, which provides an x widget in the GUI that makes it possible to remove tabs and therefore remove them from the list of objects:

tabs = pn.Tabs(
    ('red', pn.Spacer(styles=dict(background='red'), width=100, height=100)),
    ('blue', pn.Spacer(styles=dict(background='blue'), width=100, height=100)),
    ('green', pn.Spacer(styles=dict(background='green'), width=100, height=100)),



Lastly, it is possible to modify the location of the tabs header relative to the content using the tabs_location parameter:

pn.Row(tabs, tabs.clone(active=1, tabs_location='right'), tabs.clone(active=2, tabs_location='below'), tabs.clone(tabs_location='left'))

Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).