Combine Existing Components#
This guide addresses how to build custom components that are combinations of existing components.
The simplest way to extend Panel is to implement a so called Viewer component that can wrap multiple existing Panel components into an easily reusable unit that behaves like a native Panel component.
Let’s create a composite EditableRange component made up of two FloatInput widgets. First, we will create the widgets:
import param
import panel as pn
from panel.viewable import Viewer
pn.extension() # for notebook
class EditableRange(Viewer):
    value = param.Range(doc="A numeric range.")
    width = param.Integer(default=300)
    def __init__(self, **params):
        self._start_input = pn.widgets.FloatInput()
        self._end_input = pn.widgets.FloatInput(align='end')
        super().__init__(**params)
        self._layout = pn.Row(self._start_input, self._end_input)
Then, we set up callbacks to sync the parameters on the underlying widgets with the parameters on the Viewer component.
import param
import panel as pn
from panel.viewable import Viewer
pn.extension() # for notebook
class EditableRange(Viewer):
    value = param.Range(doc="A numeric range.")
    width = param.Integer(default=300)
    def __init__(self, **params):
        self._start_input = pn.widgets.FloatInput()
        self._end_input = pn.widgets.FloatInput(align='end')
        super().__init__(**params)
        self._layout = pn.Row(self._start_input, self._end_input)
        self._sync_widgets()
    @param.depends('value', 'width', watch=True)
    def _sync_widgets(self):
        self._start_input.name = self.name
        self._start_input.value = self.value[0]
        self._end_input.value = self.value[1]
        self._start_input.width = self.width//2
        self._end_input.width = self.width//2
    @param.depends('_start_input.value', '_end_input.value', watch=True)
    def _sync_params(self):
        self.value = (self._start_input.value, self._end_input.value)
Finally, we’ll implement the required __panel__ method, which returns the Panel layout to be rendered. Panel will call this method when displaying the component.
import param
import panel as pn
from panel.viewable import Viewer
pn.extension() # for notebook
class EditableRange(Viewer):
    value = param.Range(doc="A numeric range.")
    width = param.Integer(default=300)
    def __init__(self, **params):
        self._start_input = pn.widgets.FloatInput()
        self._end_input = pn.widgets.FloatInput(align='end')
        super().__init__(**params)
        self._layout = pn.Row(self._start_input, self._end_input)
        self._sync_widgets()
    def __panel__(self):
        return self._layout
    @param.depends('value', 'width', watch=True)
    def _sync_widgets(self):
        self._start_input.name = self.name
        self._start_input.value = self.value[0]
        self._end_input.value = self.value[1]
        self._start_input.width = self.width//2
        self._end_input.width = self.width//2
    @param.depends('_start_input.value', '_end_input.value', watch=True)
    def _sync_params(self):
        self.value = (self._start_input.value, self._end_input.value)
range_widget = EditableRange(name='Range', value=(0, 10))
pn.Column(
    '#### This is a custom widget',
    range_widget
)