cotton
for
Docs syntax
<c> tags: Snippets will be shown in Cotton's HTML-like tag syntax.
Native: Snippets will be shown in native Django template syntax.

Attribute Proxying

The :attrs dynamic attribute enables us to create wrapper components that proxy all their attributes to an inner component:

view.html
<c-outer
    class="outer-class"
    :count="42"
    :enabled="False">
    Content passed to inner component
</c-outer>
{% cotton outer class="outer-class" :count="42" :enabled="False" %}
    Content passed to inner component
{% endcotton %}
cotton/outer.html
<c-inner :attrs="attrs">{{ slot }}</c-inner>
{% cotton inner :attrs="attrs" %}{{ slot }}{% endcotton %}
cotton/inner.html
{{ class }}     <!-- "outer-class" -->
{{ count }}     <!-- 42 -->
{{ enabled }}   <!-- False -->
{{ slot }}      <!-- Content passed to inner component -->

The attributes are passed through to the inner component with their original types preserved (strings, numbers, booleans, lists, etc.), making this pattern ideal for creating higher-order components.

This pattern is particularly useful for Django form fields, where you might create a component hierarchy that passes Django widget attributes through multiple layers while adding labels, error handling and styling at each level.

A real-world example

Say you want one styled text input you can drop in anywhere, while still passing through any native input attribute (type, placeholder, required, even hx-*). Declare the bits the component owns in <c-vars /> and proxy everything else straight onto the inner <input>:

cotton/input.html
<c-vars label />

<label class="block">
    <span>{{ label }}</span>
    <input {{ attrs }} class="border rounded px-3 py-2 w-full" />
</label>
{% cotton:vars label %}

<label class="block">
    <span>{{ label }}</span>
    <input {{ attrs }} class="border rounded px-3 py-2 w-full" />
</label>

Every caller now gets the label and styling for free, and anything else they pass lands on the actual input:

signup.html
<c-input label="Email" name="email" type="email" placeholder="you@example.com" required />
<c-input label="Username" name="username" maxlength="20" />
{% cotton input label="Email" name="email" type="email" placeholder="you@example.com" required %}{% endcotton %}
{% cotton input label="Username" name="username" maxlength="20" %}{% endcotton %}

label is consumed by the component because it's declared in <c-vars />, while name, type, placeholder, required and maxlength all proxy through to the <input> untouched.