Slots
django-components has the most extensive slot system of all the popular Python templating engines.
The slot system is based on Vue, and works across both Django templates and Python code.
What are slots?ยค
Components support something called 'slots'.
When you write a component, you define its template. The template will always be the same each time you render the component.
However, sometimes you may want to customize the component slightly to change the content of the component. This is where slots come in.
Slots allow you to insert parts of HTML into the component. This makes components more reusable and composable.
<div class="calendar-component">
<div class="header">
{# This is where the component will insert the content #}
{% slot "header" / %}
</div>
</div>
Slot anatomyยค
Slots consists of two parts:
{% slot %}
tag - Inside your component you decide where you want to insert the content.{% fill %}
tag - In the parent template (outside the component) you decide what content to insert into the slot. It "fills" the slot with the specified content.
Let's look at an example:
First, we define the component template. This component contains two slots, header
and body
.
<!-- calendar.html -->
<div class="calendar-component">
<div class="header">
{% slot "header" %}
Calendar header
{% endslot %}
</div>
<div class="body">
{% slot "body" %}
Today's date is <span>{{ date }}</span>
{% endslot %}
</div>
</div>
Next, when using the component, we can insert our own content into the slots. It looks like this:
{% component "calendar" date="2020-06-06" %}
{% fill "body" %}
Can you believe it's already
<span>{{ date }}</span>??
{% endfill %}
{% endcomponent %}
Since the 'header'
fill is unspecified, it's default value is used.
When rendered, notice that:
- The body is filled with the content we specified,
- The header is still the default value we defined in the component template.
<div class="calendar-component">
<div class="header">
Calendar header
</div>
<div class="body">
Can you believe it's already <span>2020-06-06</span>??
</div>
</div>
Slots overviewยค
Slot definitionยค
Slots are defined with the {% slot %}
tag:
Single component can have multiple slots:
And you can even define the same slot in multiple places:
<div>
{% slot "name" %}
First content
{% endslot %}
</div>
<div>
{% slot "name" %}
Second content
{% endslot %}
</div>
Info
If you define the same slot in multiple places, you must mark each slot individually when setting default
or required
flags, e.g.:
Slot fillingยค
Fill can be defined with the {% fill %}
tag:
{% component "calendar" %}
{% fill "name" %}
Filled content
{% endfill %}
{% fill "other_name" %}
Filled content
{% endfill %}
{% endcomponent %}
Or in Python with the slots
argument:
Default slotยค
You can make the syntax shorter by marking the slot as default
:
This allows you to fill the slot directly in the {% component %}
tag, omitting the {% fill %}
tag:
To target the default slot in Python, you can use the "default"
slot name:
Accessing default slot in Python
Since the default slot is stored under the slot name default
, you can access the default slot in Python under the "default"
key:
Warning
Only one {% slot %}
can be marked as default
. But you can have multiple slots with the same name all marked as default
.
If you define multiple different slots as default
, this will raise an error.
โ Don't do this
{% slot "name" default %}
Default content
{% endslot %}
{% slot "other_name" default %}
Default content
{% endslot %}
โ Do this instead
Warning
Do NOT combine default fills with explicit named {% fill %}
tags.
The following component template will raise an error when rendered:
โ Don't do this
{% component "calendar" date="2020-06-06" %}
{% fill "header" %}Totally new header!{% endfill %}
Can you believe it's already <span>{{ date }}</span>??
{% endcomponent %}
โ Do this instead
Warning
You cannot double-fill a slot.
That is, if both {% fill "default" %}
and {% fill "header" %}
point to the same slot, this will raise an error when rendered.
Required slotยค
You can make the slot required by adding the required
keyword:
This will raise an error if the slot is not filled.
Access fillsยค
You can access the fills with the {{ component_vars.slots.<name> }}
template variable:
{% if component_vars.slots.my_slot %}
<div>
{% fill "my_slot" %}
Filled content
{% endfill %}
</div>
{% endif %}
And in Python with the Component.slots
property:
class Calendar(Component):
# `get_template_data` receives the `slots` argument directly
def get_template_data(self, args, kwargs, slots, context):
if "my_slot" in slots:
content = "Filled content"
else:
content = "Default content"
return {
"my_slot": content,
}
# In other methods you can still access the slots with `Component.slots`
def on_render_before(self, context, template):
if "my_slot" in self.slots:
# Do something
Dynamic fillsยค
The slot and fill names can be set as variables. This way you can fill slots dynamically:
{% with "body" as slot_name %}
{% component "calendar" %}
{% fill slot_name %}
Filled content
{% endfill %}
{% endcomponent %}
{% endwith %}
You can even use {% if %}
and {% for %}
tags inside the {% component %}
tag to fill slots with more control:
{% component "calendar" %}
{% if condition %}
{% fill "name" %}
Filled content
{% endfill %}
{% endif %}
{% for item in items %}
{% fill item.name %}
Item: {{ item.value }}
{% endfill %}
{% endfor %}
{% endcomponent %}
You can also use {% with %}
or even custom tags to generate the fills dynamically:
{% component "calendar" %}
{% with item.name as name %}
{% fill name %}
Item: {{ item.value }}
{% endfill %}
{% endwith %}
{% endcomponent %}
Warning
If you dynamically generate {% fill %}
tags, be careful to render text only inside the {% fill %}
tags.
Any text rendered outside {% fill %}
tags will be considered a default fill and will raise an error if combined with explicit fills. (See Default slot)
Slot dataยค
Sometimes the slots need to access data from the component. Imagine an HTML table component which has a slot to configure how to render the rows. Each row has a different data, so you need to pass the data to the slot.
Similarly to Vue's scoped slots, you can pass data to the slot, and then access it in the fill.
This consists of two steps:
- Passing data to
{% slot %}
tag - Accessing data in
{% fill %}
tag
The data is passed to the slot as extra keyword arguments. Below we set two extra arguments: first_name
and job
.
{# Pass data to the slot #}
{% slot "name" first_name="John" job="Developer" %}
{# Fallback implementation #}
Name: {{ first_name }}
Job: {{ job }}
{% endslot %}
Note
name
kwarg is already used for slot name, so you cannot pass it as slot data.
To access the slot's data in the fill, use the data
keyword. This sets the name of the variable that holds the data in the fill:
{# Access data in the fill #}
{% component "profile" %}
{% fill "name" data="d" %}
Hello, my name is <h1>{{ d.first_name }}</h1>
and I'm a <h2>{{ d.job }}</h2>
{% endfill %}
{% endcomponent %}
To access the slot data in Python, use the data
attribute in slot functions.
def my_slot(ctx):
return f"""
Hello, my name is <h1>{ctx.data["first_name"]}</h1>
and I'm a <h2>{ctx.data["job"]}</h2>
"""
Profile.render(
slots={
"name": my_slot,
},
)
Slot data can be set also when rendering a slot in Python:
slot = Slot(lambda ctx: f"Hello, {ctx.data['name']}!")
# Render the slot
html = slot({"name": "John"})
Info
To access slot data on a default slot, you have to explictly define the {% fill %}
tags with name "default"
.
Warning
You cannot set the data
attribute and fallback
attribute to the same name. This raises an error:
Slot fallbackยค
The content between the {% slot %}..{% endslot %}
tags is the fallback content that will be rendered if no fill is given for the slot.
Sometimes you may want to keep the fallback content, but only wrap it in some other content.
To do so, you can access the fallback content via the fallback
kwarg. This sets the name of the variable that holds the fallback content in the fill:
{% component "profile" %}
{% fill "name" fallback="fb" %}
Original content:
<div>
{{ fb }} <!-- fb = 'Hello, my name...' -->
</div>
{% endfill %}
{% endcomponent %}
To access the fallback content in Python, use the fallback
attribute in slot functions.
The fallback value is rendered lazily. Coerce the fallback to a string to render it.
def my_slot(ctx):
# Coerce the fallback to a string
fallback = str(ctx.fallback)
return f"Original content: " + fallback
Profile.render(
slots={
"name": my_slot,
},
)
Fallback can be set also when rendering a slot in Python:
slot = Slot(lambda ctx: f"Hello, {ctx.data['name']}!")
# Render the slot
html = slot({"name": "John"}, fallback="Hello, world!")
Info
To access slot fallback on a default slot, you have to explictly define the {% fill %}
tags with name "default"
.
Warning
You cannot set the data
attribute and fallback
attribute to the same name. This raises an error:
Slot functionsยค
In Python code, slot fills can be defined as strings, functions, or Slot
instances that wrap the two. Slot functions have access to slot data
, fallback
, and context
.
def row_slot(ctx):
if ctx.data["disabled"]:
return ctx.fallback
item = ctx.data["item"]
if ctx.data["type"] == "table":
return f"<tr><td>{item}</td></tr>"
else:
return f"<li>{item}</li>"
Table.render(
slots={
"prepend": "Ice cream selection:",
"append": Slot("ยฉ 2025"),
"row": row_slot,
"column_title": Slot(lambda ctx: f"<th>{ctx.data['name']}</th>"),
},
)
Inside the component, these will all be normalized to Slot
instances:
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
assert isinstance(slots["prepend"], Slot)
assert isinstance(slots["row"], Slot)
assert isinstance(slots["header"], Slot)
assert isinstance(slots["footer"], Slot)
You can render Slot
instances by simply calling them with data:
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
prepend_slot = slots["prepend"]
return {
"prepend": prepend_slot({"item": "ice cream"}),
}
Filling slots with functionsยค
You can "fill" slots by passing a string or Slot
instance directly to the {% fill %}
tag:
class Table(Component):
def get_template_data(self, args, kwargs, slots, context):
def my_fill(ctx):
return f"Hello, {ctx.data['name']}!"
return {
"my_fill": Slot(my_fill),
}
Note
Django automatically executes functions when it comes across them in templates.
Because of this you MUST wrap the function in Slot
instance to prevent it from being called.
Read more about Django's do_not_call_in_templates
.
Slot classยค
The Slot
class is a wrapper around a function that can be used to fill a slot.
from django_components import Component, Slot
def footer(ctx):
return f"Hello, {ctx.data['name']}!"
Table.render(
slots={
"footer": Slot(footer),
},
)
Slot class can be instantiated with a function or a string:
Warning
Passing a Slot
instance to the Slot
constructor results in an error:
Rendering slotsยค
Python
You can render a Slot
instance by simply calling it with data:
slot = Slot(lambda ctx: f"Hello, {ctx.data['name']}!")
# Render the slot with data
html = slot({"name": "John"})
Optionally you can pass the fallback value to the slot. Fallback should be a string.
Template
Alternatively, you can pass the Slot
instance to the {% fill %}
tag:
Slot contextยค
If a slot function is rendered by the {% slot %}
tag, you can access the current Context using the context
attribute.
class Table(Component):
template = """
{% with "abc" as my_var %}
{% slot "name" %}
Hello!
{% endslot %}
{% endwith %}
"""
def slot_func(ctx):
return f"Hello, {ctx.context['my_var']}!"
slot = Slot(slot_func)
html = slot()
Warning
While available, try to avoid using the context
attribute in slot functions.
Instead, prefer using the data
and fallback
attributes.
Access to context
may be removed in future versions (v2, v3).
Slot metadataยค
When accessing slots from within Component
methods, the Slot
instances are populated with extra metadata:
These are populated the first time a slot is passed to a component.
So if you pass the same slot through multiple nested components, the metadata will still point to the first component that received the slot.
You can use these for debugging, such as printing out the slot's component name and slot name.
Fill node
Components or extensions can use Slot.fill_node
to handle slots differently based on whether the slot was defined in the template with {% fill %}
and {% component %}
tags, or in the component's Python code.
If the slot was created from a {% fill %}
tag, this will be the FillNode
instance.
If the slot was a default slot created from a {% component %}
tag, this will be the ComponentNode
instance.
You can use this to find the Component
in whose template the {% fill %}
tag was defined:
class MyTable(Component):
def get_template_data(self, args, kwargs, slots, context):
footer_slot = slots.get("footer")
if footer_slot is not None and footer_slot.fill_node is not None:
owner_component = footer_slot.fill_node.template_component
# ...
Extra
You can also pass any additional data along with the slot by setting it in Slot.extra
:
When you create a slot, you can set any of these fields too:
# Either at slot creation
slot = Slot(
lambda ctx: f"Hello, {ctx.data['name']}!",
# Optional
component_name="table",
slot_name="name",
extra={},
)
# Or later
slot.component_name = "table"
slot.slot_name = "name"
slot.extra["foo"] = "bar"
Read more in Pass slot metadata.
Slot contentsยค
Whether you create a slot from a function, a string, or from the {% fill %}
tags, the Slot
class normalizes its contents to a function.
Use Slot.contents
to access the original value that was passed to the Slot constructor.
If the slot was created from a string or from the {% fill %}
tags, the contents will be accessible also as a Nodelist under Slot.nodelist
.
Escaping slots contentยค
Slots content are automatically escaped by default to prevent XSS attacks.
In other words, it's as if you would be using Django's escape()
on the slot contents / result:
from django.utils.html import escape
class Calendar(Component):
template = """
<div>
{% slot "date" default date=date / %}
</div>
"""
Calendar.render(
slots={
"date": escape("<b>Hello</b>"),
}
)
To disable escaping, you can wrap the slot string or slot result in Django's mark_safe()
:
Calendar.render(
slots={
# string
"date": mark_safe("<b>Hello</b>"),
# function
"date": lambda ctx: mark_safe("<b>Hello</b>"),
}
)
Info
Read more about Django's format_html
and mark_safe
.
Examplesยค
Pass through all the slotsยค
You can dynamically pass all slots to a child component. This is similar to passing all slots in Vue:
class MyTable(Component):
template = """
<div>
{% component "child" %}
{% for slot_name, slot in component_vars.slots.items %}
{% fill name=slot_name body=slot / %}
{% endfor %}
{% endcomponent %}
</div>
"""
Required and default slotsยค
Since each {% slot %}
is tagged with required
and default
individually, you can have multiple slots with the same name but different conditions.
In this example, we have a component that renders a user avatar - a small circular image with a profile picture or name initials.
If the component is given image_src
or name_initials
variables, the image
slot is optional.
But if neither of those are provided, you MUST fill the image
slot.
<div class="avatar">
{# Image given, so slot is optional #}
{% if image_src %}
{% slot "image" default %}
<img src="{{ image_src }}" />
{% endslot %}
{# Image not given, but we can make image from initials, so slot is optional #}
{% elif name_initials %}
{% slot "image" default %}
<div style="
border-radius: 25px;
width: 50px;
height: 50px;
background: blue;
">
{{ name_initials }}
</div>
{% endslot %}
{# Neither image nor initials given, so slot is required #}
{% else %}
{% slot "image" default required / %}
{% endif %}
</div>
Dynamic slots in table componentยค
Sometimes you may want to generate slots based on the given input. One example of this is Vuetify's table component, which creates a header and an item slots for each user-defined column.
So if you pass columns named name
and age
to the table component:
Then the component will accept fills named header-name
and header-age
(among others):
{% fill "header-name" data="data" %}
<b>{{ data.value }}</b>
{% endfill %}
{% fill "header-age" data="data" %}
<b>{{ data.value }}</b>
{% endfill %}
In django-components you can achieve the same, simply by using a variable or a template expression instead of a string literal:
<table>
<tr>
{% for header in headers %}
<th>
{% slot "header-{{ header.key }}" value=header.title %}
{{ header.title }}
{% endslot %}
</th>
{% endfor %}
</tr>
</table>
When using the component, you can either set the fill explicitly:
{% component "table" headers=headers items=items %}
{% fill "header-name" data="data" %}
<b>{{ data.value }}</b>
{% endfill %}
{% endcomponent %}
Or also use a variable:
{% component "table" headers=headers items=items %}
{% fill "header-{{ active_header_name }}" data="data" %}
<b>{{ data.value }}</b>
{% endfill %}
{% endcomponent %}
Note
It's better to use literal slot names whenever possible for clarity. The dynamic slot names should be reserved for advanced use only.
Spread operatorยค
Lastly, you can also pass the slot name through the spread operator.
When you define a slot name, it's actually a shortcut for a name
keyword argument.
So this:
is the same as:
So it's possible to define a name
key on a dictionary, and then spread that onto the slot tag:
Full example:
class MyTable(Component):
template = """
{% slot ...slot_props / %}
"""
def get_template_data(self, args, kwargs, slots, context):
return {
"slot_props": {"name": "content", "extra_field": 123},
}
Info
This applies for both {% slot %}
and {% fill %}
tags.
Legacy conditional slotsยค
Since version 0.70, you could check if a slot was filled with
{{ component_vars.is_filled.<name> }}
Since version 0.140, this has been deprecated and superseded with
{% component_vars.slots.<name> %}
The
component_vars.is_filled
variable is still available, but will be removed in v1.0.NOTE:
component_vars.slots
no longer escapes special characters in slot names.
You can use {{ component_vars.is_filled.<name> }}
together with Django's {% if / elif / else / endif %}
tags to define a block whose contents will be rendered only if the component slot with the corresponding 'name' is filled.
This is what our example looks like with component_vars.is_filled
.
<div class="frontmatter-component">
<div class="title">
{% slot "title" %}
Title
{% endslot %}
</div>
{% if component_vars.is_filled.subtitle %}
<div class="subtitle">
{% slot "subtitle" %}
{# Optional subtitle #}
{% endslot %}
</div>
{% elif component_vars.is_filled.title %}
...
{% elif component_vars.is_filled.<name> %}
...
{% endif %}
</div>
Accessing is_filled
of slot names with special charactersยค
To be able to access a slot name via component_vars.is_filled
, the slot name needs to be composed of only alphanumeric characters and underscores (e.g. this__isvalid_123
).
However, you can still define slots with other special characters. In such case, the slot name in component_vars.is_filled
is modified to replace all invalid characters into _
.
So a slot named "my super-slot :)"
will be available as component_vars.is_filled.my_super_slot___
.
Same applies when you are accessing is_filled
from within the Python, e.g.: