HTML attributes
New in version 0.74:
You can use the {% html_attrs %} tag to render various data as key="value" HTML attributes.
{% html_attrs %} tag is versatile, allowing you to define HTML attributes however you need:
- Define attributes within the HTML template
- Define attributes in Python code
- Merge attributes from multiple sources
- Boolean attributes
- Append attributes
- Remove attributes
- Define default attributes
From v0.135 onwards, {% html_attrs %} tag also supports merging style and class attributes the same way how Vue does.
To get started, let's consider a simple example. If you have a template:
<div class="{{ classes }}" data-id="{{ my_id }}">
</div>
You can rewrite it with the {% html_attrs %} tag:
<div {% html_attrs class=classes data-id=my_id %}>
</div>
The {% html_attrs %} tag accepts any number of keyword arguments, which will be merged and rendered as HTML attributes:
<div class="text-red" data-id="123">
</div>
Moreover, the {% html_attrs %} tag accepts two positional arguments:
attrs- a dictionary of attributes to be rendereddefaults- a dictionary of default attributes
You can use this for example to allow users of your component to add extra attributes. We achieve this by capturing the extra attributes and passing them to the {% html_attrs %} tag as a dictionary:
@register("my_comp")
class MyComp(Component):
# Pass all kwargs as `attrs`
def get_template_data(self, args, kwargs, slots, context):
return {
"attrs": kwargs,
"classes": "text-red",
"my_id": 123,
}
template: t.django_html = """
{# Pass the extra attributes to `html_attrs` #}
<div {% html_attrs attrs class=classes data-id=my_id %}>
</div>
"""
This way you can render MyComp with extra attributes:
Either via Django template:
{% component "my_comp"
id="example"
class="pa-4"
style="color: red;"
%}
Or via Python:
MyComp.render(
kwargs={
"id": "example",
"class": "pa-4",
"style": "color: red;",
}
)
In both cases, the attributes will be merged and rendered as:
<div id="example" class="text-red pa-4" style="color: red;" data-id="123"></div>
Composing attribute dictionaries with {% attrs %}
New in version 0.152.0
Use the {% attrs %} tag when your inputs are already attribute dictionaries and you want to compose them in a defined order:
<div {% attrs [base_attrs, [theme_attrs, state_attrs]] {'class': 'local'} %}></div>
Each argument may be a mapping (such as a dictionary), or a list or tuple nested to any depth. Every terminal value must be a mapping. The mappings are applied from left to right:
- Ordinary attributes use the last value.
- All
classvalues are combined and normalized. - All
stylevalues are combined by CSS property, with later properties taking precedence. - The first occurrence of a key determines its position in the result.
For example, with these inputs:
base_attrs = {"id": "first", "class": "button", "title": "Open"}
theme_attrs = {"class": {"primary": True}}
state_attrs = {"id": "last", "disabled": True}
the tag above renders:
<div id="last" class="button primary local" title="Open" disabled></div>
Attribute values are escaped. True renders a bare boolean attribute, while False and None are omitted. Attribute names must be non-empty strings and cannot contain whitespace, =, /, >, <, or the template-comment opener ({ followed by #).
The tag can also produce a dictionary for a component input. Put it by itself in a quoted nested template, with no surrounding text or whitespace:
{% component "table"
table_attrs="{% attrs [base_attrs, [theme_attrs]] {'class': 'compact'} %}"
/ %}
Here, table_attrs is an AttrsDict, a dict subclass, rather than a string. It supports normal mapping operations. Calling str(table_attrs) produces the escaped, space-delimited HTML attributes. If the nested template has surrounding text, whitespace, or any other node, django-components passes the serialized string instead.
The new {% attrs %} tag supersedes {% html_attrs %} for attribute composition. Prefer {% attrs %} in new code. {% html_attrs %} remains supported for backward compatibility, retaining its attrs / defaults and keyword-argument interface. Likewise, the legacy merge_attributes() helper retains its existing behavior of space-joining every duplicate ordinary attribute; it is not an alias for {% attrs %}.
Summary
The two arguments,
attrsanddefaults, can be passed as positional args:{% html_attrs attrs defaults key=val %}or as kwargs:
{% html_attrs key=val defaults=defaults attrs=attrs %}Both
attrsanddefaultsare optional and can be omitted.Both
attrsanddefaultsare dictionaries. As such, there's multiple ways to define them:By referencing a variable:
{% html_attrs attrs=attrs %}By defining a literal dictionary:
{% html_attrs attrs={"key": value} %}Or by defining the dictionary keys:
{% html_attrs attrs:key=value %}
All other kwargs are merged and can be repeated.
{% html_attrs class="text-red" class="pa-4" %}Will render:
<div class="text-red pa-4"></div>
Usage
Boolean attributes
In HTML, boolean attributes are usually rendered with no value. Consider the example below where the first button is disabled and the second is not:
<button disabled>Click me!</button>
<button>Click me!</button>
HTML rendering with html_attrs tag or format_attributes works the same way - an attribute set to True is rendered without the value, and an attribute set to False is not rendered at all.
So given this input:
attrs = {
"disabled": True,
"autofocus": False,
}
And template:
<div {% html_attrs attrs %}>
</div>
Then this renders:
<div disabled></div>
Removing attributes
Given how the boolean attributes work, you can "remove" or prevent an attribute from being rendered by setting it to False or None.
So given this input:
attrs = {
"class": "text-green",
"required": False,
"data-id": None,
}
And template:
<div {% html_attrs attrs %}>
</div>
Then this renders:
<div class="text-green"></div>
Default attributes
Sometimes you may want to specify default values for attributes. You can pass a second positional argument to set the defaults.
<div {% html_attrs attrs defaults %}>
...
</div>
In the example above, if attrs contains a certain key, e.g. the class key, {% html_attrs %} will render:
<div class="{{ attrs.class }}">
...
</div>
Otherwise, {% html_attrs %} will render:
<div class="{{ defaults.class }}">
...
</div>
Appending attributes
For the class HTML attribute, it's common that we want to join multiple values, instead of overriding them.
For example, if you're authoring a component, you may want to ensure that the component will ALWAYS have a specific class. Yet, you may want to allow users of your component to supply their own class attribute.
We can achieve this by adding extra kwargs. These values will be appended, instead of overwriting the previous value.
So if we have a variable attrs:
attrs = {
"class": "my-class pa-4",
}
And on {% html_attrs %} tag, we set the key class:
<div {% html_attrs attrs class="some-class" %}>
</div>
Then these will be merged and rendered as:
<div data-value="my-class pa-4 some-class"></div>
To simplify merging of variables, you can supply the same key multiple times, and these will be all joined together:
{# my_var = "class-from-var text-red" #}
<div {% html_attrs attrs class="some-class another-class" class=my_var %}>
</div>
Renders:
<div
data-value="my-class pa-4 some-class another-class class-from-var text-red"
></div>
Merging class attributes
The class attribute can be specified as a string of class names as usual.
If you want granular control over individual class names, you can use a dictionary.
String: Used as is.
{% html_attrs class="my-class other-class" %}Renders:
<div class="my-class other-class"></div>Dictionary: Keys are the class names, and values are booleans. Only keys with truthy values are rendered.
{% html_attrs class={ "extra-class": True, "other-class": False, } %}Renders:
<div class="extra-class"></div>
If a certain class is specified multiple times, it's the last instance that decides whether the class is rendered or not.
Example:
In this example, the other-class is specified twice. The last instance is {"other-class": False}, so the class is not rendered.
{% html_attrs
class="my-class other-class"
class={"extra-class": True, "other-class": False}
%}
Renders:
<div class="my-class extra-class"></div>
Merging style attributes
The style attribute can be specified as a string of style properties as usual.
If you want granular control over individual style properties, you can use a dictionary.
String: Used as is.
{% html_attrs style="color: red; background-color: blue;" %}Renders:
<div style="color: red; background-color: blue;"></div>Dictionary: Keys are the style properties, and values are their values.
{% html_attrs style={ "color": "red", "background-color": "blue", } %}Renders:
<div style="color: red; background-color: blue;"></div>
If a style property is specified multiple times, the last value is used.
- Properties set to
Noneare ignored. - If the last non-
Noneinstance of the property is set toFalse, the property is removed.
Example:
In this example, the width property is specified twice. The last instance is {"width": False}, so the property is removed.
Secondly, the background-color property is also set twice. But the second time it's set to None, so that instance is ignored, leaving us only with background-color: blue.
The color property is set to a valid value in both cases, so the latter (green) is used.
{% html_attrs
style="color: red; background-color: blue; width: 100px;"
style={"color": "green", "background-color": None, "width": False}
%}
Renders:
<div style="color: green; background-color: blue;"></div>
Usage outside of templates
In some cases, you want to prepare HTML attributes outside of templates.
Composing attributes
compose_attrs() is the Python counterpart to the {% attrs %} tag. It accepts mappings and arbitrarily nested lists or tuples of mappings, and returns an AttrsDict:
from django_components import compose_attrs
table_attrs = compose_attrs(
[base_attrs, [theme_attrs, state_attrs]],
{"class": "compact", "aria-label": "Results"},
)
Ordinary keys use the last value, while class and style are composed. Use the result as a normal dictionary, or call str(table_attrs) to serialize it as escaped HTML attributes.
Legacy merging for {% html_attrs %}
merge_attributes() accepts any number of dictionaries and merges them together, using the same merge strategy as {% html_attrs %}. Unlike compose_attrs(), duplicate ordinary attributes are joined with a space instead of using the last value.
from django_components import merge_attributes
merge_attributes(
{"class": "my-class", "data-id": 123},
{"class": "extra-class"},
{"class": {"cool-class": True, "uncool-class": False} },
)
Which will output:
{
"class": "my-class extra-class cool-class",
"data-id": 123,
}
Warning
Unlike {% html_attrs %}, where you can pass extra kwargs, merge_attributes() requires each argument to be a dictionary.
Formatting attributes
format_attributes() serializes attributes the same way as {% html_attrs %} tag does. It also accepts structured class and style values and validates attribute names using the rules above.
from django_components import format_attributes
format_attributes({
"class": "my-class text-red pa-4",
"data-id": 123,
"required": True,
"disabled": False,
"ignored-attr": None,
})
Which will output:
'class="my-class text-red pa-4" data-id="123" required'
Note
Prior to v0.135, the format_attributes() function was named attributes_to_string().
This function is now deprecated and will be removed in v1.0.
Cheat sheet
Assuming that:
class_from_var = "from-var"
attrs = {
"class": "from-attrs",
"type": "submit",
}
defaults = {
"class": "from-defaults",
"role": "button",
}
Then:
Empty tag
<div {% html_attr %}></div>renders nothing:
<div></div>Only kwargs
<div {% html_attr class="some-class" class=class_from_var data-id="123" %}></div>renders:
<div class="some-class from-var" data-id="123"></div>Only attrs
<div {% html_attr attrs %}></div>renders:
<div class="from-attrs" type="submit"></div>Attrs as kwarg
<div {% html_attr attrs=attrs %}></div>renders:
<div class="from-attrs" type="submit"></div>Only defaults (as kwarg)
<div {% html_attr defaults=defaults %}></div>renders:
<div class="from-defaults" role="button"></div>Attrs using the
prefix:key=valueconstruct<div {% html_attr attrs:class="from-attrs" attrs:type="submit" %}></div>renders:
<div class="from-attrs" type="submit"></div>Defaults using the
prefix:key=valueconstruct<div {% html_attr defaults:class="from-defaults" %}></div>renders:
<div class="from-defaults" role="button"></div>All together (1) - attrs and defaults as positional args:
<div {% html_attrs attrs defaults class="added_class" class=class_from_var data-id=123 %}></div>renders:
<div class="from-attrs added_class from-var" type="submit" role="button" data-id=123></div>All together (2) - attrs and defaults as kwargs args:
<div {% html_attrs class="added_class" class=class_from_var data-id=123 attrs=attrs defaults=defaults %}></div>renders:
<div class="from-attrs added_class from-var" type="submit" role="button" data-id=123></div>All together (3) - mixed:
<div {% html_attrs attrs defaults:class="default-class" class="added_class" class=class_from_var data-id=123 %}></div>renders:
<div class="from-attrs added_class from-var" type="submit" data-id=123></div>
Full example
@register("my_comp")
class MyComp(Component):
template: t.django_html = """
<div
{% html_attrs attrs
defaults:class="pa-4 text-red"
class="my-comp-date"
class=class_from_var
data-id="123"
%}
>
Today's date is <span>{{ date }}</span>
</div>
"""
def get_template_data(self, args, kwargs, slots, context):
return {
"date": kwargs["date"],
"attrs": kwargs.get("attrs", {}),
"class_from_var": "extra-class"
}
@register("parent")
class Parent(Component):
template: t.django_html = """
{% component "my_comp"
date=date
attrs:class="pa-0 border-solid border-red"
attrs:data-json=json_data
attrs:@click="(e) => onClick(e, 'from_parent')"
/ %}
"""
def get_template_data(self, args, kwargs, slots, context):
return {
"date": datetime.now(),
"json_data": json.dumps({"value": 456})
}
Note: For readability, we've split the tags across multiple lines.
Inside MyComp, we defined a default attribute
defaults:class="pa-4 text-red"
So if attrs includes key class, the default above will be ignored.
MyComp also defines class key twice. It means that whether the class attribute is taken from attrs or defaults, the two class values will be appended to it.
So by default, MyComp renders:
<div class="pa-4 text-red my-comp-date extra-class" data-id="123">...</div>
Next, let's consider what will be rendered when we call MyComp from Parent component.
MyComp accepts a attrs dictionary, that is passed to html_attrs, so the contents of that dictionary are rendered as the HTML attributes.
In Parent, we make use of passing dictionary key-value pairs as kwargs to define individual attributes as if they were regular kwargs.
So all kwargs that start with attrs: will be collected into an attrs dict.
attrs:class="pa-0 border-solid border-red"
attrs:data-json=json_data
attrs:@click="(e) => onClick(e, 'from_parent')"
And get_template_data of MyComp will receive a kwarg named attrs with following keys:
attrs = {
"class": "pa-0 border-solid",
"data-json": '{"value": 456}',
"@click": "(e) => onClick(e, 'from_parent')",
}
attrs["class"] overrides the default value for class, whereas other keys will be merged.
So in the end MyComp will render:
<div
class="pa-0 border-solid my-comp-date extra-class"
data-id="123"
data-json='{"value": 456}'
@click="(e) => onClick(e, 'from_parent')"
>
...
</div>