Skip to content

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:

{# props title, author #}

If you use the component without required props, a TemplateSyntaxError is raised:

<!-- ❌ Error: Missing required props -->
<include:article>Content</include:article>
<!-- ✅ 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:

{# props title, show_date=True, priority="normal" #}

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:

<include:card title="Hello" class="my-card" data-id="123">
    Content
</include:card>

Output:

<div class="my-card" data-id="123">
    <h2>Hello</h2>
    <div class="content">Content</div>
</div>

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

{% attrs attribute="fallback_value" %}

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:

{# props #}
<button {% attrs class="& btn btn-default" %}>
    {{ contents }}
</button>

Usage:

<include:button class="my-button">Click me</include:button>

Output:

<button class="my-button btn btn-default">Click me</button>

Prepending Classes

Use " &" suffix to prepend classes before user-provided classes:

{# props #}
<div {% attrs class="container &" %}>
    {{ contents }}
</div>

Usage:

<include:wrapper class="my-content">Content</include:wrapper>

Output:

<div class="container my-content">Content</div>

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:

<include:button active="true" disabled="false">Active Button</include:button>

Output:

<button class="btn active">Active Button</button>

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:

{# props #}
<input {% attrs type="text" %}>

Usage:

<include:input required disabled readonly>

Output:

<input type="text" required disabled readonly>

Optional Attributes

Attributes with None values are completely removed from the output. This is useful for conditionally including attributes:

{# props #}
<a {% attrs href="#" target="_blank" rel="noopener" %}>
    {{ contents }}
</a>

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:

TemplateSyntaxError: Missing required prop 'title' for component 'article'

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

{# props title, size="medium", color="blue", rounded=True #}

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" #}
{# 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

{# props tag="div" #}
<{{ tag }} {% attrs class="wrapper" %}>
    {{ contents }}
</{{ tag }}>

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