Advanced Props¶
Advanced prop features including enum validation, complex prop types, and sophisticated validation patterns.
Enum Props¶
Define props with a limited set of allowed values for better validation and developer experience.
Basic Enum Syntax¶
{# props variant=primary,secondary,danger #}
<button {% attrs class="btn btn-{{ variant }}" %}>
    {{ contents }}
</button>
Usage:
<include:button variant="primary">Save</include:button>
<include:button variant="danger">Delete</include:button>
Generated Boolean Variables¶
When an enum prop is set, the component receives both the value and boolean flags:
{# props variant=primary,secondary,danger #}
<button {% attrs 
    class="btn" 
    class:btn-primary=variantPrimary 
    class:btn-secondary=variantSecondary 
    class:btn-danger=variantDanger 
%}>
    {{ contents }}
</button>
Context variables created:
- variant - The prop value ("primary")
- variantPrimary - Boolean (True when variant="primary")
- variantSecondary - Boolean (False when variant="primary")
- variantDanger - Boolean (False when variant="primary")
Optional Enums¶
Start with an empty value to make the enum optional:
{# props size=,small,medium,large #}
<div {% attrs class="box" class:box-small=sizeSmall class:box-medium=sizeMedium class:box-large=sizeLarge %}>
    {{ contents }}
</div>
Usage:
<!-- No size specified - uses empty default -->
<include:box>Default size content</include:box>
<!-- Explicit size -->
<include:box size="large">Large content</include:box>
Kebab-Case Enum Values¶
Enum values with hyphens are converted to camelCase for boolean variables:
{# props theme=light-mode,dark-mode,high-contrast #}
<div {% attrs 
    class="container" 
    class:light-theme=themeLightMode 
    class:dark-theme=themeDarkMode 
    class:high-contrast=themeHighContrast 
%}>
    {{ contents }}
</div>
Multiple Enum Values¶
You can specify multiple enum values separated by spaces:
{# props variant=primary,secondary,accent,icon,large #}
<button {% attrs 
    class="btn" 
    class:btn-primary=variantPrimary 
    class:btn-secondary=variantSecondary 
    class:btn-accent=variantAccent
    class:btn-icon=variantIcon
    class:btn-large=variantLarge
%}>
    {{ contents }}
</button>
Usage:
<!-- Single value -->
<include:button variant="primary">Save</include:button>
<!-- Multiple values -->
<include:button variant="primary icon">Save</include:button>
<include:button variant="secondary large">Cancel</include:button>
<include:button variant="accent icon large">Featured</include:button>
Context variables created for variant="primary icon":
- variant - The full value ("primary icon")
- variantPrimary - Boolean (True)
- variantIcon - Boolean (True)
- variantSecondary - Boolean (False)
- variantAccent - Boolean (False)
- variantLarge - Boolean (False)
This is particularly useful for combining visual modifiers like size, appearance, and behavior flags.
Complex Props¶
Default Value Types¶
Props support various default value types:
Dynamic Defaults¶
Use template expressions for dynamic defaults:
{# props user, show_avatar=True, avatar_size="medium" #}
{% if user.is_premium %}
    {% with default_size="large" %}
        <!-- Use premium default -->
    {% endwith %}
{% endif %}
Validation Patterns¶
Required Prop Validation¶
Components automatically validate required props:
{# props title, author, content #}
<!-- Will raise TemplateSyntaxError if any required prop is missing -->
Custom Validation¶
Add validation logic in your component:
{# props count=0 #}
{% if count < 0 %}
    <div class="error">Count cannot be negative</div>
{% elif count > 100 %}
    <div class="error">Count too large (max 100)</div>
{% else %}
    <div class="counter">{{ count }}</div>
{% endif %}
Type Checking¶
While Django templates don't enforce strict typing, you can add checks:
{# props items=[] #}
{% if items|length_is:"0" %}
    <p class="empty">No items to display</p>
{% else %}
    <ul>
        {% for item in items %}
            <li>{{ item }}</li>
        {% endfor %}
    </ul>
{% endif %}
Advanced Examples¶
Multi-Variant Component¶
{# props 
    variant=primary,secondary,success,warning,danger,
    size=,small,medium,large,
    outline=False,
    disabled=False 
#}
<button {% attrs 
    class="btn"
    class:btn-primary=variantPrimary
    class:btn-secondary=variantSecondary
    class:btn-success=variantSuccess
    class:btn-warning=variantWarning
    class:btn-danger=variantDanger
    class:btn-outline=outline
    class:btn-sm=sizeSmall
    class:btn-lg=sizeLarge
    disabled=disabled
%}>
    {{ contents }}
</button>
Complex Form Component¶
{# props 
    name,
    label="",
    type=text,email,password,number,tel,url,
    required=False,
    placeholder="",
    help_text="",
    validation_state=,valid,invalid,warning
#}
<div {% attrs 
    class="form-group" 
    class:has-error=validationStateInvalid
    class:has-warning=validationStateWarning
    class:has-success=validationStateValid
%}>
    {% if label %}
        <label for="{{ name }}" class="form-label">
            {{ label }}
            {% if required %}<span class="required">*</span>{% endif %}
        </label>
    {% endif %}
    <input {% attrs.input
        type=type
        name=name
        id=name
        placeholder=placeholder
        required=required
        class="form-control"
        class:is-valid=validationStateValid
        class:is-invalid=validationStateInvalid
    %}>
    {% if help_text %}
        <small class="form-text">{{ help_text }}</small>
    {% endif %}
    {% if contents.errors %}
        <div class="invalid-feedback">{{ contents.errors }}</div>
    {% endif %}
</div>
Theme-Aware Component¶
{# props 
    theme=auto,light,dark,
    color=blue,red,green,yellow,purple,
    intensity=50,100,200,300,400,500,600,700,800,900
#}
<div {% attrs 
    class="alert"
    class:alert-light=themeLight
    class:alert-dark=themeDark
    class:alert-auto=themeAuto
    class:text-{{ color }}-{{ intensity }}=True
    data-theme=theme
%}>
    {% if themeAuto %}
        <script>
            // Auto theme detection logic
            if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
                this.classList.add('alert-dark');
            } else {
                this.classList.add('alert-light');
            }
        </script>
    {% endif %}
    {{ contents }}
</div>
Error Handling¶
Invalid Enum Values¶
Usage that causes error:
Error message:
TemplateSyntaxError: Invalid value 'invalid' for enum prop 'variant'. 
Must be one of: primary, secondary, danger
Missing Required Props¶
Usage that causes error:
Error message:
Best Practices¶
1. Use Semantic Enum Values¶
<!-- ✅ Good: Clear meaning -->
{# props status=draft,published,archived #}
<!-- ❌ Avoid: Unclear abbreviations -->
{# props status=d,p,a #}
2. Provide Sensible Defaults¶
<!-- ✅ Good: Reasonable defaults -->
{# props variant=primary,secondary, size=medium,small,large, disabled=False #}
<!-- ❌ Avoid: No defaults for optional props -->
{# props variant=primary,secondary, size=medium,small,large #}
3. Document Complex Props¶
{# 
props:
  variant (enum): Button style - primary, secondary, danger
  size (enum, optional): Button size - small, medium (default), large  
  loading (bool): Show loading spinner
  href (string, optional): Make button a link
#}
4. Group Related Enums¶
{# props 
    text_size=xs,sm,base,lg,xl,2xl,3xl,
    text_weight=thin,light,normal,medium,semibold,bold,black,
    text_color=gray,red,blue,green,yellow,purple
#}
5. Use Validation for Critical Props¶
{# props percentage=50 #}
{% if percentage < 0 or percentage > 100 %}
    <div class="error">Percentage must be between 0 and 100</div>
{% else %}
    <div class="progress-bar" style="width: {{ percentage }}%"></div>
{% endif %}
Migration from Simple Props¶
Before (Simple Props)¶
{# props variant="primary", size="medium" #}
<button class="btn btn-{{ variant }} btn-{{ size }}">
    {{ contents }}
</button>
After (Enum Props)¶
{# props variant=primary,secondary,danger, size=small,medium,large #}
<button {% attrs 
    class="btn"
    class:btn-primary=variantPrimary
    class:btn-secondary=variantSecondary  
    class:btn-danger=variantDanger
    class:btn-small=sizeSmall
    class:btn-medium=sizeMedium
    class:btn-large=sizeLarge
%}>
    {{ contents }}
</button>
Benefits of enum props: - Validation: Invalid values are caught at render time - IDE support: Better autocomplete and error detection - Documentation: Clear list of allowed values - Type safety: Boolean flags prevent typos in conditionals
Attribute Spreading¶
The ...attrs syntax allows you to forward undefined attributes from a parent component to child components. This is a powerful feature for creating flexible wrapper components that don't need to explicitly define every possible attribute.
Basic Spreading¶
{# components/wrapper.html #}
{# props title #}
<div class="wrapper">
    <h2>{{ title }}</h2>
    <!-- Forward all undefined attrs to the child component -->
    <include:card ...attrs>
        {{ contents }}
    </include:card>
</div>
Usage:
<include:wrapper 
    title="My Wrapper" 
    class="custom-card" 
    id="card-123"
    data-value="456"
>
    Card content
</include:wrapper>
In this example, class, id, and data-value are passed through to the card component because they're not defined as props in the wrapper.
How It Works¶
- The parent component receives attributes
 - Defined props are extracted and used by the parent
 - Undefined attributes are collected in the 
attrsvariable ...attrsspreads these undefined attributes to the child component- The child merges spread attrs with its own attrs
 
Spreading with Props¶
When spreading attrs to a component that defines props, only the undefined attributes are spread:
{# components/filtered-wrapper.html #}
{# props #}
<div class="wrapper">
    <include:user-card name="John" ...attrs>
        Additional info
    </include:user-card>
</div>
{# components/user-card.html #}
{# props name, role="user" #}
<div {{ attrs }} class="user-card">
    <h3>{{ name }}</h3>
    <span class="role">{{ role }}</span>
    {{ contents }}
</div>
Usage:
<include:filtered-wrapper 
    name="Jane"
    role="admin" 
    data-id="123"
    class="highlight"
>
</include:filtered-wrapper>
Here:
- name is explicitly set to "John" in the wrapper (overrides "Jane")
- role="admin" flows through attrs to the child
- data-id and class are spread as undefined attributes
Spreading Attribute Groups¶
You can spread specific groups of attributes using the dot notation:
{# components/form.html #}
{# props #}
<form {{ attrs }}>
    {{ contents }}
    <include:submit-button ...attrs.button>
        Submit
    </include:submit-button>
</form>
Usage:
<include:form 
    method="POST" 
    action="/submit"
    button.type="submit"
    button.class="btn btn-primary"
    button.disabled="{{ is_processing }}"
>
    <input name="email" type="email">
</include:form>
Only the button.* attributes are spread to the submit button component.
Precedence Rules¶
Local attributes take precedence over spread attributes:
{# components/precedence-wrapper.html #}
{# props #}
<div>
    <!-- Local class overrides any class from spread attrs -->
    <include:card ...attrs class="local-override">
        {{ contents }}
    </include:card>
</div>
Practical Examples¶
1. Layout Wrapper¶
{# components/section.html #}
{# props title #}
<section class="content-section">
    <h2>{{ title }}</h2>
    <include:content-area ...attrs>
        {{ contents }}
    </include:content-area>
</section>
2. Conditional Wrapper¶
{# components/maybe-link.html #}
{# props href=None #}
{% if href %}
    <a href="{{ href }}" ...attrs>{{ contents }}</a>
{% else %}
    <span ...attrs>{{ contents }}</span>
{% endif %}
3. Form Field Wrapper¶
{# components/field-wrapper.html #}
{# props label, name, required=False #}
<div class="field">
    <label for="{{ name }}">
        {{ label }}
        {% if required %}<span class="required">*</span>{% endif %}
    </label>
    <include:input name="{{ name }}" ...attrs />
    {% if contents.error %}
        <div class="error">{{ contents.error }}</div>
    {% endif %}
</div>
4. Multi-Level Component Architecture¶
{# components/card-header.html #}
{# props title #}
<header {{ attrs }}>
    <h3>{{ title }}</h3>
    {{ contents }}
</header>
{# components/card.html #}
{# props #}
<article class="card">
    <include:card-header ...attrs.header title="{{ attrs.title|default:'Untitled' }}">
        {% if contents.actions %}
            {{ contents.actions }}
        {% endif %}
    </include:card-header>
    <div class="card-body" {{ attrs.body }}>
        {{ contents }}
    </div>
</article>
{# Usage #}
<include:card 
    title="User Profile"
    header.class="bg-primary text-white"
    header.id="profile-header"
    body.class="p-4"
>
    <content:actions>
        <button>Edit</button>
    </content:actions>
    Profile content here...
</include:card>
Best Practices¶
- 
Use for Wrapper Components: Spreading is most useful for components that wrap other components
 - 
Document Expected Attrs: Even though attrs are undefined, document common ones:
 - 
Be Explicit When Needed: Sometimes it's clearer to pass specific attrs:
 - 
Consider Performance: Spreading attrs has minimal overhead, but avoid deeply nested spreading chains
 - 
Type Safety: Remember that spread attrs bypass prop validation - validate critical attributes when needed
 
Advanced Patterns¶
Dynamic Spreading¶
{# props spread_to="card" #}
{% if spread_to == "wrapper" %}
    <div ...attrs>
        <include:card>{{ contents }}</include:card>
    </div>
{% else %}
    <div>
        <include:card ...attrs>{{ contents }}</include:card>
    </div>
{% endif %}
Component Chain with Spreading¶
{# components/themed-card.html #}
{# props theme="light" #}
<div class="theme-{{ theme }}">
    <include:styled-card ...attrs>
        {{ contents }}
    </include:styled-card>
</div>
{# components/styled-card.html #}
{# props bordered=True #}
<div class="styled-wrapper">
    <include:base-card bordered="{{ bordered }}" ...attrs>
        {{ contents }}
    </include:base-card>
</div>
Next Steps¶
- Learn about CSS Classes for advanced styling
 - Explore Component Patterns for real-world examples
 - Check out the API Reference for complete syntax details