Props & Attrs¶
Components can define props (properties) to specify which attributes they expect and how to handle undefined attributes. This system provides validation, defaults, and flexible attribute handling.
Defining Props¶
Define props in a comment at the top of your component template:
{# props title, description="", show_icon=True #}
<div class="card">
<h2>{{ title }}</h2>
{% if description %}
<p>{{ description }}</p>
{% endif %}
{% if show_icon %}
<span class="icon">★</span>
{% endif %}
<div class="content">
{{ contents }}
</div>
</div>
Props Syntax¶
Required Props¶
Props without default values are required:
If you use the component without required props, a TemplateSyntaxError
is raised:
<!-- ✅ Correct: All required props provided -->
<include:article title="My Article" author="John Doe">Content</include:article>
Optional Props with Defaults¶
Props with default values are optional:
Default value types:
- String: description="Default text"
- Boolean: visible=True
, hidden=False
- Number: count=0
, rating=5.0
- None: optional_value=None
Usage Example¶
{# props title, subtitle="", priority="normal", show_meta=True #}
<article class="article priority-{{ priority }}">
<header>
<h1>{{ title }}</h1>
{% if subtitle %}
<h2>{{ subtitle }}</h2>
{% endif %}
</header>
<div class="content">
{{ contents }}
</div>
{% if show_meta %}
<footer class="meta">
Priority: {{ priority }}
</footer>
{% endif %}
</article>
Usage:
<!-- Using defaults -->
<include:article title="Hello World">
<p>Article content...</p>
</include:article>
<!-- Overriding defaults -->
<include:article
title="Important News"
subtitle="Breaking Story"
priority="high"
show-meta="false"
>
<p>Important content...</p>
</include:article>
Data Types and Object Passing¶
Passing Objects¶
When using pure variable syntax, objects are passed directly to components without string conversion:
<!-- Objects passed directly -->
<include:user-card user="{{ user_object }}" />
<include:product-grid products="{{ product_queryset }}" />
<include:data-viewer data="{{ complex_data }}" />
In the component, you can access object properties and methods:
{# props user #}
<div class="user-card">
<h3>{{ user.get_full_name }}</h3>
<p>{{ user.email }}</p>
<ul>
{% for group in user.groups.all %}
<li>{{ group.name }}</li>
{% endfor %}
</ul>
</div>
String Conversion¶
Objects are only preserved with pure variable syntax. Any additional content causes string conversion:
<!-- ✅ Object passed directly -->
<include:viewer data="{{ my_object }}" />
<!-- ❌ Converted to string -->
<include:viewer title="Data: {{ my_object }}" />
<include:viewer info="{{ my_object.id }}: {{ my_object.name }}" />
Filters with Objects¶
Filters work correctly with object passing:
<!-- Object or fallback object -->
<include:profile user="{{ current_user|default:anonymous_user }}" />
<!-- First item from queryset -->
<include:featured product="{{ products|first }}" />
The attrs
Variable¶
Attributes not defined in props are collected in the attrs
variable, which can be rendered as HTML attributes.
Basic Usage¶
{# props title #}
<div {{ attrs }}>
<h2>{{ title }}</h2>
<div class="content">{{ contents }}</div>
</div>
Usage:
Output:
Accessing Individual Attributes¶
You can access individual undefined attributes:
{# props title #}
<div class="card">
<h2>{{ title }}</h2>
{% if attrs.class %}
<p>Custom class: {{ attrs.class }}</p>
{% endif %}
{% if attrs.dataId %}
<p>Data ID: {{ attrs.dataId }}</p>
{% endif %}
<div {{ attrs }}>{{ contents }}</div>
</div>
Attribute Name Conversion
Kebab-case attributes are converted to camelCase for access:
- data-id
becomes attrs.dataId
- my-custom-attr
becomes attrs.myCustomAttr
The {% attrs %}
Tag¶
The {% attrs %}
tag provides more control over attribute rendering with fallback values and class handling.
Basic Syntax¶
Providing Defaults¶
{# props title #}
<div {% attrs class="card" id="default-card" %}>
<h2>{{ title }}</h2>
<div class="content">{{ contents }}</div>
</div>
Usage:
<!-- Uses defaults -->
<include:card title="Hello">Content</include:card>
<!-- Output: <div class="card" id="default-card"> -->
<!-- Overrides defaults -->
<include:card title="Hello" class="custom-card" id="my-card">Content</include:card>
<!-- Output: <div class="custom-card" id="my-card"> -->
Class Handling¶
The {% attrs %}
tag provides special handling for CSS classes:
Extending Classes¶
Use "& "
prefix to append classes after user-provided classes:
Usage:
Output:
Prepending Classes¶
Use " &"
suffix to prepend classes before user-provided classes:
Usage:
Output:
Conditional Classes¶
Use the class:
prefix for conditional classes:
{# props active=False, disabled=False #}
<button {% attrs class="btn" class:active=active class:disabled=disabled %}>
{{ contents }}
</button>
Usage:
Output:
Grouped Attributes¶
For complex components, you can group attributes by prefix:
Defining Groups¶
{# props label, value #}
<div {% attrs class="field" %}>
<label>{{ label }}</label>
<input {% attrs.input type="text" value=value %}>
</div>
Using Groups¶
<include:form-field
label="Username"
value="john"
class="form-group"
input.class="form-control"
input.placeholder="Enter username"
input.required="true"
>
</include:form-field>
Output:
<div class="field form-group">
<label>Username</label>
<input type="text" value="john" class="form-control" placeholder="Enter username" required>
</div>
Boolean Attributes¶
HTML boolean attributes are handled correctly:
Usage:
Output:
Optional Attributes¶
Attributes with None
values are completely removed from the output. This is useful for conditionally including attributes:
Usage:
<!-- With all attributes -->
<include:link href="/about" target="_blank">About</include:link>
<!-- Output: <a href="/about" target="_blank" rel="noopener">About</a> -->
<!-- Removing attributes with None -->
<include:link href="/home" target="{{ None }}" rel="{{ None }}">Home</include:link>
<!-- Output: <a href="/home">Home</a> -->
Common Patterns for Optional Attributes¶
<!-- Using conditional expressions -->
<include:button
disabled="{{ is_loading or None }}"
data-id="{{ object.id if object else None }}"
>
Save
</include:button>
<!-- Using the yesno filter for boolean attributes -->
<include:input
required="{{ is_required|yesno:'required,' }}"
readonly="{{ is_readonly|yesno:'readonly,' }}"
/>
<!-- Using template logic -->
<include:link
href="/profile"
target="{% if external %}_blank{% else %}{{ None }}{% endif %}"
>
Profile
</include:link>
Falsy Values
Only None
removes attributes. Other falsy values are rendered as strings:
- False
renders as attr="False"
- ""
(empty string) renders as attr=""
- 0
renders as attr="0"
- True
renders as just attr
(boolean attribute)
Advanced Examples¶
Card Component with Full Props System¶
{# props title, subtitle="", variant="default", collapsible=False #}
<div {% attrs class="card card-{{ variant }}" class:collapsible=collapsible %}>
<div class="card-header">
<h3>{{ title }}</h3>
{% if subtitle %}
<p class="subtitle">{{ subtitle }}</p>
{% endif %}
{% if collapsible %}
<button class="collapse-toggle">Toggle</button>
{% endif %}
</div>
<div class="card-body">
{{ contents }}
</div>
{% if contents.footer %}
<div class="card-footer">
{{ contents.footer }}
</div>
{% endif %}
</div>
Usage:
<include:card
title="User Profile"
subtitle="Manage your account"
variant="primary"
collapsible="true"
data-user-id="{{ user.pk }}"
class="my-custom-card"
>
<p>Profile content here...</p>
<content:footer>
<button>Save Changes</button>
</content:footer>
</include:card>
Form Field Component¶
{# props name, label="", type="text", required=False #}
<div {% attrs class="form-field" class:required=required %}>
{% if label %}
<label for="{{ name }}">
{{ label }}
{% if required %}<span class="required">*</span>{% endif %}
</label>
{% endif %}
<input {% attrs.input
type=type
name=name
id=name
required=required
%}>
{% if contents.help %}
<small class="help-text">{{ contents.help }}</small>
{% endif %}
{% if contents.errors %}
<div class="error-messages">{{ contents.errors }}</div>
{% endif %}
</div>
Usage:
<include:form-field
name="email"
label="Email Address"
type="email"
required="true"
class="mb-3"
input.class="form-control"
input.placeholder="Enter your email"
>
<content:help>We'll never share your email with anyone.</content:help>
</include:form-field>
Validation and Error Handling¶
Missing Required Props¶
{# props title, author #}
<!-- Component definition -->
<!-- ❌ This will raise TemplateSyntaxError -->
<include:article author="John">
Content without required title
</include:article>
Error message:
Type Validation¶
While Django templates don't enforce strict typing, you can add validation in your component:
{# props count=0 #}
{% if count|add:0 != count %}
<div class="error">Count must be a number</div>
{% else %}
<div class="counter">Count: {{ count }}</div>
{% endif %}
Best Practices¶
1. Document Your Props¶
{#
props:
title (required): The main heading text
subtitle (optional): Secondary heading text
variant (optional): Style variant - "default", "primary", "success", "danger"
collapsible (optional): Whether the card can be collapsed
#}
<div class="card">
<!-- component template -->
</div>
2. Provide Sensible Defaults¶
3. Use Descriptive Prop Names¶
<!-- ✅ Good: Clear and descriptive -->
{# props user_name, show_avatar=True, avatar_size="medium" #}
<!-- ❌ Avoid: Vague or abbreviated -->
{# props name, show=True, size="med" #}
4. Group Related Attributes¶
{# props label, value, help_text="" #}
<div class="form-group">
<label>{{ label }}</label>
<input {% attrs.input type="text" value=value %}>
<button {% attrs.button type="button" %}>Helper</button>
{% if help_text %}
<small>{{ help_text }}</small>
{% endif %}
</div>
5. Handle Edge Cases¶
{# props items=[] #}
{% if items %}
<ul {{ attrs }}>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
{% else %}
<p class="empty-message">No items to display</p>
{% endif %}
Common Patterns¶
Wrapper Components¶
Conditional Rendering¶
{# props visible=True, title="" #}
{% if visible %}
<div {{ attrs }}>
{% if title %}<h3>{{ title }}</h3>{% endif %}
{{ contents }}
</div>
{% endif %}
Multi-Variant Components¶
{# props variant="default" #}
<div {% attrs class="alert alert-{{ variant }}" %}>
{% if variant == "success" %}
<span class="icon">✓</span>
{% elif variant == "error" %}
<span class="icon">✗</span>
{% endif %}
{{ contents }}
</div>
Spreading Attributes¶
You can use the ...attrs
syntax to forward undefined attributes from a parent component to its children:
{# components/wrapper.html #}
{# props title #}
<div class="wrapper">
<h2>{{ title }}</h2>
<!-- Forward all undefined attrs to the child -->
<include:card ...attrs>
{{ contents }}
</include:card>
</div>
This is particularly useful for creating flexible wrapper components. For detailed information about attribute spreading, see Advanced Props - Attribute Spreading.
Next Steps¶
- Learn about Advanced Props for enum validation and complex prop handling
- Explore CSS Classes for advanced class management
- Check out Component Patterns for real-world examples