Your components use the same conventions as labb’s. Put a template under templates/cotton/, then call it as a tag from another template. It can receive props and nested markup. django-cotton documents the wider API.
Create the template
Create a file in templates/cotton/. Its path determines the tag name.
<c-vars title="" price="" />
<c-lb.card border>
<c-lb.card.body>
<c-lb.card.title>{{ title }}</c-lb.card.title>
<p class="text-sm text-base-content/60">{{ price }}</p>
</c-lb.card.body>
</c-lb.card>
<c-product-card title="Air Max Pro" price="£120" />
Hyphens become underscores in the filename and dots become directories. <c-product-card> maps to cotton/product_card.html. <c-shop.product.card> maps to cotton/shop/product/card.html. Check this mapping first when Django cannot find a component.
Declare props with c-vars
List each prop in <c-vars> and provide a default. Undeclared attributes pass through to attrs.
<c-vars title="" variant="primary" border />
A bare declaration creates a boolean prop. Include the attribute to enable it.
<c-product-card title="Air Max Pro" border />
Omit the attribute to leave it off. You do not need border="true" or :border="False".
Bind values with a leading colon
A plain attribute passes text. A leading : passes the evaluated Python value. Use it for objects, numbers, lists, and context variables.
<!-- The literal string "product" -->
<c-product-card product="product" />
<!-- The Product object -->
<c-product-card :product=product />
<!-- Python literals also work -->
<c-product-card :count=3 :tags="['new', 'sale']" />
{{ }} inside a component attribute. Django turns it into text before the component receives it. Write :product=product.
Give a bound prop a typed default, such as <c-vars :product="{}" :count=0 />.
Accept markup with slots
{{ slot }} holds the content between the opening and closing tags.
<c-vars title="" />
<section class="rounded-xl border border-base-300 p-4">
<h2 class="font-semibold">{{ title }}</h2>
{{ slot }}
</section>
<c-panel title="Shipping">
<p>Anything here lands in the slot.</p>
</c-panel>
Name a slot when the component needs more than one content area. <c-slot name="header"> fills {{ header }}.
<c-panel>
<c-slot name="header"><strong>Shipping</strong></c-slot>
Body content goes in the default slot.
</c-panel>
Use a named slot for markup. Use a prop for a short text value.
Forward the rest with attrs
{{ attrs }} outputs attributes that the component did not declare. Add it to the root element so callers can pass id, data-*, and aria-*. Declare and merge class so callers can extend the component’s styling.
<c-vars title="" class="" />
<div class="rounded-xl border p-4 {{ class }}" {{ attrs }}>
{{ slot }}
</div>
<c-panel id="shipping" class="mt-6" data-testid="panel">…</c-panel>
Reactive attributes also pass through attrs. In data-on:click and data-bind:*, the colon is part of the attribute name. Only a leading colon binds a Python value.
Compound components
A directory with index.html and sibling templates creates a component family. labb’s card uses this structure.
templates/cotton/shop/product/index.html -> <c-shop.product>
templates/cotton/shop/product/price.html -> <c-shop.product.price>
Use this structure when a component has distinct pieces instead of adding a long list of props.