How to create custom components
Vizro's public API is kept small to enable quick and easy configuration of a dashboard. However, at the same time, Vizro is extensible, so that you can tweak any component to your liking or even create entirely new ones.
If you can't find a component that you would like to have in the code basis, or if you would like to alter/enhance an existing component, then you are in the right place. This guide shows you how to create custom components that are completely new, or enhancements of existing ones.
In general, you can create a custom component based on any dash-compatible component (for example, dash-core-components, dash-bootstrap-components, dash-html-components).
All our components are based on Dash
, and they are shipped with a set of sensible defaults that can be modified. If you would like to overwrite one of those defaults,
or if you would like to use extra args
or kwargs
of those components, then this is the correct way to include those. You can use any existing attribute of any underlying Dash component with this method.
Note
There are always three general steps to consider to create a custom component:
- Sub-class to create your component
- Enhance or build the component (for example, to add/change model fields, overwrite pre-build/build method) to your desire
- Check if your component will be part of a discriminated union1. If yes, then
- you must ensure your component has a
type
field - you must register the new type with its parent model's relevant field (where the new component is entered into) with
add_type
- you must ensure your component has a
We will refer back to these three steps in the two examples below.
Extend an existing component
You may want to use this strategy to:
- extend an existing component (for example, to add a button to
Card
) - change configurations we have set by default (for example, to set
allowCross=False
inRangeSlider
) - change any fields of any models (for example, to change the title field from
Optional
to have a default)
You can extend an existing component by sub-classing the component you want to alter. Remember that when sub-classing a component you have access to all fields of all parent models, but you can choose to overwrite any field or method, or define new ones.
The aim for this example is to enhance the RangeSlider
model so that
one slider handle cannot cross the other, and to have a permanent tooltip showing the current value. You will note that it is often easier to call super()
when overriding a complex method
such as the build
method in the below example instead of attempting to write it from scratch.
In this case, the general three steps translate into:
-
Sub-class existing
RangeSlider
: -
Enhance the component by changing the underlying parent
These lines are highlighted in the example below. They are the only material change to the originaldcc.RangeSlider
:build
method. -
Since the new model will be inserted into the
selectors
argument of theFilter
orParameter
, it will be part of the discriminated union describing the allowed types for that argument, in this case theSelectorType
. Hence we must:- define a new type:
- register the type with the parent model(s):
Example based on existing component
Run and edit this code in Py.Cafe
- Here we provide a new type for the new component, so it can be distinguished in the discriminated union.
- Here we override the
build
method by altering the output ofsuper().build()
. Alternatively one could copy the source code of the build method and alter it directly. range_slider_build_obj[self.id]
then fetches the underlyingdcc.RangeSlider
object.- This change prevents the
RangeSlider
from crossing itself when moving the handle. - This change displays the tooltip below the handle.
- Remember! If part of a discriminated union, you must add the new component to the parent model where it will be inserted. In this case the new
TooltipNonCrossRangeSlider
will be inserted into theselector
argument of theFilter
model, and thus must be added as an allowed type. - Remember! If part of a discriminated union, you must add the new component to the parent model where it will be inserted. In this case the new
TooltipNonCrossRangeSlider
will be inserted into theselector
argument of theParameter
model, and thus must be added as an allowed type. - The new component can now be inserted into a regular dashboard.
Create a new component
You may want to use this strategy to:
- create a new component that does not exist in our library yet
- make extensive changes to an existing component
You can create an entirely new component by sub-classing our VizroBaseModel. Note that
using VizroBaseModel
is mandatory if you want the new component to work in the Vizro framework.
The aim of the example is to create a Jumbotron
, a component that currently does not exist in Vizro's existing component range. It is a lightweight container to call attention to featured content or information.
The build
and pre_build
methods
When creating new components, you will need to define a build
method like in the below example if it is a visual component that is rendered on the page. Examples of components with a build
method are:
selector
type:Checklist
,Dropdown
,RadioItems
.component
type:Graph
,Card
.
For components that only create other components, you do not need to define a build
method, for example, for Filter
and Parameter
.
If you would like to have access to other components, you may want to define a pre_build
method. This method is automatically run for all models and makes them internally consistent. Notable existing models with pre_build
methods are Filter
and Parameter
.
General steps
-
Create new component, by sub-classing VizroBaseModel:
-
Build the component using existing
dash
components. - Since the new model will be inserted into the
components
argument of thePage
, it will be part of the discriminated union describing the allowed types for that argument, in this case theComponentType
. Hence we must:- define a new type:
- register the type with the parent model(s):
Example of new component creation
from typing import Literal
from dash import html
import vizro.models as vm
from vizro import Vizro
# 1. Create new custom component
class Jumbotron(vm.VizroBaseModel): # (1)!
"""New custom component `Jumbotron`."""
type: Literal["jumbotron"] = "jumbotron" # (2)!
title: str # (3)!
subtitle: str
text: str
def build(self): # (4)!
return html.Div(
[
html.H2(self.title),
html.H3(self.subtitle),
html.P(self.text),
]
)
# 2. Add new components to expected type - here the selector of the parent components
vm.Page.add_type("components", Jumbotron) # (5)!
page = vm.Page(
title="Custom Component",
components=[
Jumbotron( # (6)!
title="Jumbotron",
subtitle="This is a subtitle to summarize some content.",
text="This is the main body of text of the Jumbotron.",
)
],
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()
Run and edit this code in Py.Cafe
- Here we subclass the VizroBaseModel because we are creating a new component from scratch.
- Here we provide a new type for the new component, so it can be distinguished in the discriminated union.
- Here we provide other fields we think are useful for our components. These fields are the main way the new component can be configured. A lot of different functionality is possible here; see the Pydantic documentation for more details.
- As we are creating a new visual component, we have to define a
build
method. - Don't forget! If part of a discriminated union, you must add the new component to the parent model where it will be inserted. In this case the new
Jumbotron
will be inserted into thecomponents
argument of thePage
model, and thus must be added as an allowed type. - The new component can now be inserted into a regular dashboard.
Using custom components with custom actions
Custom components can be used as inputs
to, outputs
of, or as a trigger
of custom actions. In the examples below we will explore both options.
Custom components as inputs/outputs of custom actions
Following the instructions above to create a custom component, results in this OffCanvas
component:
class OffCanvas(vm.VizroBaseModel):
type: Literal["offcanvas"] = "offcanvas"
title: str
content: str
def build(self):
return html.Div(
[
dbc.Offcanvas(
children=html.P(self.content),
id=self.id,
title=self.title,
is_open=False,
),
]
)
After you have completed the steps above, it is time to write your custom action.
@capture("action")
def open_offcanvas(n_clicks, is_open):
if n_clicks:
return not is_open
return is_open
Add the custom action open_offcanvas
as a function
argument inside the Action
model.
Example of the use of custom component with actions
from typing import Literal
import dash_bootstrap_components as dbc
import vizro.models as vm
import vizro.plotly.express as px
from dash import html
from vizro import Vizro
from vizro.models import Action
from vizro.models.types import capture
# 1. Create new custom component
class OffCanvas(vm.VizroBaseModel):
type: Literal["offcanvas"] = "offcanvas"
title: str
content: str
def build(self):
return html.Div(
[
dbc.Offcanvas(
children=html.P(self.content),
id=self.id,
title=self.title,
is_open=False,
),
]
)
# 2. Add new components to expected type - here the selector of the parent components
vm.Page.add_type("components", OffCanvas)
# 3. Create custom action
@capture("action")
def open_offcanvas(n_clicks, is_open):
if n_clicks:
return not is_open
return is_open
page = vm.Page(
title="Custom Component",
components=[
vm.Button(
text="Open Offcanvas",
id="open_button",
actions=[
vm.Action(
function=open_offcanvas(),
inputs=["open_button.n_clicks", "offcanvas.is_open"],
outputs=["offcanvas.is_open"],
)
],
),
OffCanvas(
id="offcanvas",
content="OffCanvas content",
title="Offcanvas Title",
),
],
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()
Run and edit this code in Py.Cafe
Trigger actions with a custom component
As mentioned above, custom components can trigger action. To enable the custom component to trigger the action, we need to add some extra code:
- Add the
actions
argument to your custom component. The type of theactions
argument islist[Action]
. - Set the action through
_set_actions
. In doing so, any change in the"active_index"
property of the custom component triggers the action.
Example of triggering action with custom component
from typing import Annotated, Literal
import dash_bootstrap_components as dbc
import vizro.models as vm
from pydantic import AfterValidator, Field, PlainSerializer
from vizro import Vizro
from vizro.models import Action
from vizro.models._action._actions_chain import _action_validator_factory
from vizro.models.types import capture
# 1. Create new custom component
class Carousel(vm.VizroBaseModel):
type: Literal["carousel"] = "carousel"
items: list
actions: Annotated[
list[Action],
# Here we set the action so a change in the active_index property of the custom component triggers the action
AfterValidator(_action_validator_factory("active_index")),
# Here we tell the serializer to only serialize the actions field
PlainSerializer(lambda x: x[0].actions),
Field(default=[]),
]
def build(self):
return dbc.Carousel(
id=self.id,
items=self.items,
)
# 2. Add new components to expected type - here the selector of the parent components
vm.Page.add_type("components", Carousel)
# 3. Create custom action
@capture("action")
def slide_next_card(active_index):
if active_index:
return "Second slide"
return "First slide"
page = vm.Page(
title="Custom Component",
components=[
vm.Card(text="First slide", id="carousel-card"),
Carousel(
id="carousel",
items=[
{"key": "1", "src": "assets/slide_1.jpg"},
{"key": "2", "src": "assets/slide_2.jpg"},
],
actions=[
vm.Action(
function=slide_next_card(),
inputs=["carousel.active_index"],
outputs=["carousel-card.children"],
)
],
),
],
)
dashboard = vm.Dashboard(pages=[page])
Vizro().build(dashboard).run()

Security warning
Note that users of this package are responsible for the content of any custom-created component, function or integration they write. You should be cautious about leaking any sensitive information and potential security threats.
By default, all Dash components in Vizro that persist client-side data set persistence_type="session"
to use window.SessionStorage
, which is cleared upon closing the browser.
Be careful when using any custom components that persist data beyond this scope: it is your responsibility to ensure compliance with any legal requirements affecting jurisdictions in which your app operates.
-
You can check if your new component will be part of a discriminated union by consulting our API reference on models. Check whether the relevant model field (for example,
selectors
inFilter
orParameter
) is described as a discriminated union (in this case theSelectorType
is, but for exampleOptionsType
is not). ↩