labb 0.5.0 is out

Writing components

Build components for a labb project with c-vars, props, bindings, slots, and forwarded attributes.

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.

templates/cotton/product_card.html
<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']" />

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.

templates/cotton/panel.html
<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.