DEV Community

Cover image for Introducing Django Cotton: Revolutionizing UI Composition in Django! 🚀
williamabbott
williamabbott

Posted on • Edited on • Originally published at Medium

Introducing Django Cotton: Revolutionizing UI Composition in Django! 🚀

Goodbye {% extends, block, include, custom_tag %}?

Hello <c-component />

I’m excited to introduce Django Cotton, a game-changer for those who love Django but crave a more modern, component-based design for templates.

Here’s why I built it and what it offers:

🌟 Key Features:

  • Modern UI Composition: Efficiently compose and reuse UI components.
  • HTML-like Syntax: HTML-like tags for better editor support, semantic structure and expressive coding.
  • Interoperable with Django: Enhances Django’s existing template system.
  • Minimal Overhead: Compiles to native Django components with dynamic caching.
  • Ideal for Tailwind Usage: Encapsulates content and style in one file.
  • Complements HTMX: Reduces repetition and enhances maintainability.

The problem with Django’s native tags

Whilst you can build frontends with Django’s native tags, there are a few things that hold us back when we want to apply modern practices:

  • Django’s {% block %} and {% extends %} system strongly couples child and parent templates. This makes it hard to create a truly re-usable component that can be used in places without it having a related base template.

  • What about {% include %}? Modern libraries allow components to be highly configurable, whether it’s by attributes, passing variables, passing HTML with default and named slots. {% include %} tags, whilst they have the ability to pass simple variables and text, they will not allow you to easily send HTML blocks with template expressions let alone other niceties such as boolean attributes, named slots etc.

  • We can only get so far using {% with %} tags. Whilst it allows us to provide variables and strings it quickly busies up your code and has the same limitations about passing more complex types.

  • Can custom templatetags help us? They get us so far in being able to create something like a re-usable component but managing things like variable scoping, nesting, slots, dynamic attributes can quickly increase complexity. You’re also still writing that verbose syntax that comes with Django templates.

Your first component

Cotton allows you to craft your interfaces using HTML-like tags.

A component saved in cotton/button.html:

<!-- cotton/button.html -->
<a href="#" class="bg-blue-500 text-white">
    {{ slot }}
</a>
Enter fullscreen mode Exit fullscreen mode

Can be used in a view or other component like:

<!-- in your view -->
<c-button>Click!</c-button>
Enter fullscreen mode Exit fullscreen mode

This is already covering the slot mechanism which simply outputs the content between the opening and closing tags. This means we’re already able to pass HTML to the component.

<c-button>
    <svg id="pointer" ... />
    Click!
</c-button>
Enter fullscreen mode Exit fullscreen mode

Named slots

What if we wanted to position content in a predefined area and style within the component? Consider a ‘card’ component:

<!-- cotton/card.html -->
<div class="border rounded p-5">
    <div class="border-b">{{ title }}</div>
    {{ slot  }}
</div>
Enter fullscreen mode Exit fullscreen mode

We can target this content in a couple of ways, either as a title attribute:

<!-- in your view -->
<c-card title="Furniture">
    Hand made
</c-card>
Enter fullscreen mode Exit fullscreen mode

Or should the title contain HTML or require some Django template logic, then we can use a named slot:

<c-card>
    <c-slot name="title">
        <strong>Really</strong> awesome furniture

        {% if on_sale %}
                <span class="text-red-500">SALE!</span>
        {% endif %}
    </c-slot>
</c-card>
Enter fullscreen mode Exit fullscreen mode

Native tags inside attributes

We are free to use {{ and {% tags inside attributes like this:

<!-- button.html -->
<a href="{{ url }}" class="rounded p-5">
    {{ slot }}
</a>
Enter fullscreen mode Exit fullscreen mode
<!-- in your view -->
<c-button url="{% url 'product' product.id %}">
    Product title
</c-button>
Enter fullscreen mode Exit fullscreen mode

Be more flexible with {{ attrs }}

{{ attrs }} will provide a string of all key=”value” items specified on the component as attributes. This is useful for increasing versatility of your components:

<a {{ attrs }} class="rounded p-5">
    {{ slot }}
</a>
Enter fullscreen mode Exit fullscreen mode

Then we change the implementation like:

<c-button 
    href="{% url 'product' product.id %}" 
    target="_blank">
        Product title
</c-button>
Enter fullscreen mode Exit fullscreen mode

This will add the href and target attributes to the underlying element without having to specifically declare them in the component template.

An example using HTMX

<c-button 
    hx-get="/product-list/?page=2"
    hx-target="#replaceMe"
    hx-swap="outerHTML"
>Next page</c-button>
Enter fullscreen mode Exit fullscreen mode

There are many more topics to cover, including:

  • c-vars to define in-component variables to create default attributes
  • Dynamic Attributes to pass python types and variables by reference
  • Boolean Attributes to simplify boilerplate

For more, checkout the docs site + repo:

http://django-cotton.com
https://github.com/wrabit/django-cotton

Why I Built Cotton

During ~20 years of building for the web, I’ve found that tools like Svelte, Vue.js, and Laravel’s blade components — with their expressive, semantic HTML-like syntax — coupled with utility-first styling tools like Tailwind CSS, provide an unbeatable combination for spinning up modular, reusable UIs. Recently, moving to Python and Django, I couldn’t find a similar package, so I built Cotton to fill that gap.

Top comments (0)