Enabling Modern UI Composition in Django
See the codeBringing component-based design to Django templates.
Why?
Install
Usage Basics
Your First component
Attributes
Named Slots
Pass Template Variables
Template expressions in attributes
Boolean attributes
Passing Python data types
Increase Re-usability with {{ attrs }}
Merging and Proxying Attributes with :attrs
In-component Variables with <c-vars>
Organizing in Subfolders
HTMX Example
Limitations in Django that Cotton overcomes
Template Syntax Options
Configuration
Caching
Tools
Version support
Changelog
Comparison with other packages
Cotton aims to overcome certain limitations that exist in the django template system that hold us back when we want to apply modern practices to compose UIs in a modular and reusable way.
pip install django-cotton
INSTALLED_APPS = [
'django_cotton'
]
If you have previously specified a custom loader, you should perform manual setup.
templates/cotton folder (or define a custom folder).<c-my-component />my_component.html (or configure for kebab-case)<!-- cotton/button.html -->
<a href="/" class="...">{{ slot }}</a>
<!-- in view -->
<c-button>Contact</c-button>
<!-- html output -->
<a href="/" class="...">Contact</a>
Everything provided between the opening and closing tag is provided to the component as {{ slot }}. It can contain any content, HTML or Django template expression.
<!-- cotton/button.html -->
<a href="{{ url }}" class="...">
{{ slot }}
</a>
<!-- in view -->
<c-button url="/contact">Contact</c-button>
<!-- html output -->
<a href="/contact" class="...">
Contact
</a>
Named slots are a powerful concept. They allow us to provide HTML to appear in one or more areas in the component. Here we allow the button to optionally display an svg icon:
<!-- cotton/button.html -->
<a href="{{ url }}" class="...">
{{ slot }}
{% if icon %}
<span class="some-class">
{{ icon }}
</span>
{% endif %}
</a>
<!-- in view -->
<c-button url="/contact">
Contact
<c-slot name="icon">
<svg>...</svg>
</c-slot>
</c-button>
Named slots can also contain any html or django template expression:
<!-- in view -->
<c-button url="/contact">
<c-slot name="icon">
{% if mode == 'edit' %}
<svg id="pencil">...</svg>
{% else %}
<svg id="disk">...</svg>
{% endif %}
</c-slot>
</c-button>
To pass a template variable you prepend the attribute name with a colon :. Consider a bio card component:
<!-- in view -->
<c-bio-card :user="user" />
That has a component definition like:
<!-- cotton/bio_card.html -->
<div class="...">
<img src="{{ user.avatar }}" alt="...">
{{ user.username }} {{ user.country_code }}
</div>
You can use template expression statements inside attributes.
<c-weather icon="fa-{{ icon }}"
unit="{{ unit|default:'c' }}"
condition="very {% get_intensity %}"
/>
Boolean attributes reduce boilerplate when we just want to indicate a certain attribute should be True or not.
<!-- in view -->
<c-button url="/contact" external>Contact</c-button>
By passing just the attribute name without a value, it will automatically be provided to the component as True
<!-- cotton/button.html -->
<a href="{{ url }}" {% if external %} target="_blank" {% endif %} class="...">
{{ slot }}
</a>
Cotton supports two syntaxes for passing dynamic values:
attr=value - for simple values without spaces (like native Django):attr="value" - works for any expression<!-- Quoteless: for simple literals and variables -->
<c-button enabled=True />
<c-button enabled=False />
<c-input value=my_variable />
<c-counter start=42 />
<!-- Colon prefix: required for complex expressions with spaces/quotes -->
<c-select :options="['yes', 'no', 'maybe']" />
<c-card :config="{'open': True}" />
Both approaches support:
This benefits a number of use-cases, for example if you have a select component that you want to provide the possible options from the parent:
<!-- cotton/select.html -->
<select {{ attrs }}>
{% for option in options %}
<option value="{{ option }}">{{ option }}</option>
{% endfor %}
</select>
<c-select name="q1" :options="['yes', 'no', 'maybe']" />
<!-- source code output -->
<select name="q1">
<option value="yes">yes</option>
<option value="no">no</option>
<option value="maybe">maybe</option>
</select>
{{ attrs }}{{ attrs }} is a special variable that contains all the attributes passed to the component (except those defined as c-vars) in a key="value" format. This is useful when you want to pass all attributes to a child element without having to explicitly define them in the component template. For example, you have inputs that can have any number of attributes defined:
<!-- cotton/input.html -->
<input type="text" class="..." {{ attrs }} />
<!-- example usage -->
<c-input placeholder="Enter your name" />
<c-input name="country" id="country" value="Japan" required />
<!-- html output -->
<input type="text" class="..." placeholder="Enter your name" />
<input type="text" class="..." name="country" id="country" value="Japan" required />
:attrsWhile {{ attrs }} is great for outputting all attributes passed to a component, Cotton provides more control with the special dynamic attribute :attrs.
Merge a dictionary of attributes
You can pass a dictionary of attributes (e.g., from your Django view's context) to a component using the :attrs syntax. These attributes are then merged with any other attributes passed directly and become available in the component's attrs variable (for use with {{ attrs }}). This is useful for applying a pre-defined set of attributes.
<!-- cotton/input.html -->
<input type="text" {{ attrs }} />
# In your view context
context = {
'widget_attrs': {
'placeholder': 'Enter your name',
'data-validate': 'true',
'size': '40'
}
}
<!-- In your template (e.g., form_view.html) -->
<c-input :attrs="widget_attrs" required />
<!-- HTML output -->
<input type="text" placeholder="Enter your name" data-validate="true" size="40" required />
Notice how required (passed directly to <c-input>) is merged with attributes from widget_attrs.
Proxy attributes to a nested component
The :attrs attribute also allows you to pass all attributes received by a wrapper component directly to a nested component. This is powerful for creating higher-order components or wrapping existing ones. When you use <c-child :attrs="attrs">, the child component receives the attrs dictionary of the parent.
<!-- cotton/outer_wrapper.html -->
<c-vars message /> <!-- 'message' is for outer_wrapper, not to be passed via attrs -->
<p>Outer message: {{ message }}</p>
<c-inner-component :attrs="attrs">
{{ slot }}
</c-inner-component>
<!-- cotton/inner_component.html -->
<div class="inner {{ class }}">
{{ slot }}
</div>
<!-- In view -->
<c-outer-wrapper message="Hello from outside"
class="special-class">
Inner content
</c-outer-wrapper>
<!-- HTML output -->
<p>Outer message: Hello from outside</p>
<div class="inner special-class">
Inner content
</div>
Attributes like class are passed from <c-outer_wrapper> to <c-inner_component> via its attrs variable, while message (declared in <c-vars>) is used by outer-wrapper itself and excluded from the attrs passed down.
<c-vars>Django templates adhere quite strictly to the MVC model and does not permit a lot of data manipulation in views. Fair enough, but what if we want to handle data for the purpose of UI state only? Having presentation related variables defined in the back is overkill and can quickly lead to higher maintenance cost and loses encapsulation of the component. Cotton allows you define in-component variables for the following reasons:
<c-vars> for default attributesIn this example we have a button component with a default "theme" but it can be overridden.
<!-- cotton/button.html -->
<c-vars theme="bg-purple-500" />
<a href="..." class="{{ theme }}">
{{ slot }}
</a>
<!-- in view -->
<c-button>I'm a purple button</c-button>
<!-- html output -->
<a href="..." class="bg-purple-500">
I'm a purple button
</a>
Now we have a default theme for our button, but it is overridable:
<!-- in view -->
<c-button theme="bg-green-500">But I'm green</c-button>
<!-- html output -->
<a href="..." class="bg-green-500">
But I'm green
</a>
<c-vars> to govern {{ attrs }}Using {{ attrs }} to pass all attributes from parent scope onto an element in the component, you'll sometimes want to provide additional properties to the component which are not intended to be an attributes. In this case you can declare them in <c-vars /> and it will prevent it from being in {{ attrs }}
Take this example where we want to provide any number of attributes to an input but also an icon setting which is not intened to be an attribute on <input>:
<!-- in view -->
<c-input type="password" id="password" icon="padlock" />
<!-- cotton/input.html -->
<c-vars icon />
<img src="icons/{{ icon }}.png" />
<input {{ attrs }} />
Input will have all attributes provided apart from the icon:
<input type="password" id="password" />
Use dot notation in component names to organize components in subfolders. This keeps large projects easier to navigate and maintain.
cotton/sidebar/menu/link.html is used as <c-sidebar.menu.link />cotton/ui/alert.html is used as <c-ui.alert />index.html in a folder to define the default component (e.g. cotton/card/index.html for <c-card />)Example structure for larger projects:
templates/cotton/
├── card/
│ ├── index.html
│ └── header.html
├── form/
│ ├── input.html
│ └── button.html
└── ui/
├── alert.html
└── card.html
<!-- Usage -->
<c-card>
<c-card.header>...</c-card.header>
</c-card>
<c-form.input placeholder="Name" />
<c-ui.alert>Success!</c-ui.alert>
Sometimes there is a need to include a component dynamically, for example, you are looping through some data and the type of component is defined within a variable.
<!--
form_fields = [
{'type': 'text'},
{'type': 'textarea'},
{'type': 'checkbox'}
]
-->
{% for field in form_fields %}
<c-component :is="field.type" />
{% endfor %}
You can also provide a template expression, should the component be inside a subdirectory or have a prefix:
{% for field in form_fields %}
<!-- subfolder -->
<c-component is="form-fields.{{ field.type }}" />
<!-- component prefix -->
<c-component is="field_{{ field.type }}" />
{% endfor %}
Cotton helps you build re-usable HTMX-powered components. Define your styles and markup once, then pass different HTMX attributes via attributes:
<!-- cotton/button.html -->
<button {{ attrs }} class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
{{ slot }}
</button>
<!-- in view -->
<c-button hx-post="/users/1/follow" hx-swap="outerHTML">
Follow
</c-button>
<c-button hx-delete="/posts/1" hx-confirm="Are you sure?">
Delete Post
</c-button>
<c-button hx-get="/notifications" hx-target="#notifications">
Load More
</c-button>
When building HTMX-powered interfaces, you often need to return partial HTML from view functions. Cotton provides render_component() to programmatically render components from views:
<!-- cotton/notification.html -->
<div class="alert alert-{{ type }}">{{ message }}</div>
# views.py
from django.http import HttpResponse
from django_cotton import render_component
def user_deleted(request, id):
user = User.objects.get(id=id)
user.delete()
return HttpResponse(
render_component(request, "notification",
message=f"{user.name} deleted",
type="success"
)
)
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:
{% block %} and {% extends %}This system strongly couples child and parent templates making it hard to create a truly re-usable component that can be used in places without it having a related base template.
{% 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.
{% with %}?Whilst {% with %} tags allow us to provide variables and strings it quickly busies up your code and has the same limitations about passing more complex types.
{% templatetags %}Cotton does essentially compile down to templatetags but there is some extra work it performs above it to help with scoping and auto-managing keys which will be difficult to manage manually in complex nested structures.
In addition, Cotton enables you to navigate around some of the limitations with Django's native tags and template language:
❌ Django native:
{% my_header icon="<svg>...</svg>" %}
✅ Cotton:
<c-my-header>
<c-slot name="icon">
<svg>...</svg>
</c-slot>
</c-my-header>
❌ Django native:
{% bio name="{{ first_name }} {{ last_name }}" extra="{% get_extra %}" %}
✅ Cotton:
<c-bio name="{{ first_name }} {{ last_name }}" extra="{% get_extra %} />
❌ Django native:
{% my_component default_options="['yes', 'no', 'maybe']" %}
{% my_component config="{'open': True}" %}
{% my_component enabled="True" %}
✅ Cotton:
<c-my-component :default_options="['yes', 'no', 'maybe']" />
<c-my-component :config="{'open': True}" />
<c-my-component :enabled="True" />
(provides a List and Dict to component)
❌ Django native:
{% my_component
arg=1 %}
✅ Cotton:
<c-my-component
class="blue"
x-data="{
something: 1
}" />
❌ Django native:
{% {{ templatetag_name }} arg=1 %}
✅ Cotton:
<c-component :is="component_name" />
<c-component is="{{ component_name }}" />
<c-component is="subfolder1.subfolder2.{{ component_name }}" />
Cotton supports two syntax styles for using components in your templates:
This syntax has better IDE support - code formatting, autocompletion, autoclosing and syntax highlighting.
For those who prefer Django's native template tag style, Cotton provides equivalent template tags for all features.
| Feature | HTML-like Syntax | Native Template Syntax |
|---|---|---|
| Component | <c-button>...</c-button> | {% cotton button %}...{% endcotton %} |
| Self-closing | <c-button /> | {% cotton button / %} |
| Variables | <c-vars title /> | {% cotton:vars title %} |
| Named Slot | <c-slot name="header">...</c-slot> | {% cotton:slot header %}...{% endcotton:slot %} |
COTTON_DIR (default: "cotton")
The directory where your components are stored.
COTTON_BASE_DIR (default: None)
If you use a project-level templates folder then you can set the path here. This is not needed if your project already has a BASE_DIR variable.
COTTON_SNAKE_CASED_NAMES (default: True)
Whether to search for component filenames in snake_case. If set to False, you can use kebab-cased / hyphenated filenames.
COTTON_ISOLATE_BY_DEFAULT (default: False)
When set to True, every component is rendered with Smart Isolation: it cannot see variables from the surrounding parent template (preventing accidental context leaks via {% with %}, {% for %}, the view context, etc.), but it does still receive global context-processor output such as request, user, messages, perms and any of your own custom processors.
Context-processor output is captured once per request and reused across every component on the page — processor functions are not re-invoked per component, so DB-backed processors stay cheap regardless of how deeply you nest components.
For total isolation on an individual component (block context processors too), add the only flag on the tag itself.
This setting replaces the experimental COTTON_ENABLE_CONTEXT_ISOLATION flag, which continues to work but emits a DeprecationWarning.
Cotton includes its own compilation cache that avoids re-processing component syntax when template files haven't changed (based on file modification time). For full performance, Cotton should also be used with Django's cached.Loader, which additionally caches fully parsed Template objects and avoids disk reads entirely. If you use automatic configuration (the default), the cached loader is already applied automatically.
| Feature | Cotton | django-components | Slippers | Template Partials |
|---|---|---|---|---|
| Intro | UI-focused, expressive syntax | Holistic solution with backend logic | Enhances DTL for reusable components | Inline named partials for HTMX |
| Definition of 'component' | An HTML template | A backend class with template | An HTML template | A named fragment within a template |
| Syntax Style | HTML-like | Django Template Tags | Django Template Tags with custom tags | Django Template Tags |
| One-step package install | ✅ | ❌ | ❌ | ✅ (built-in since Django 6.0) |
| Create component in one step? | ✅ (place in folder) | ✅ (Technically yes with single-file components) | ❌ (need to register in YAML file or with function) | ✅ (define with {% partialdef %}) |
| Renderable from views Render a component directly from a Python view | ✅ | ✅ | ❌ | ✅ (via template#partial syntax) |
| Slots Pass HTML content between tags | ✅ | ✅ | ✅ | ❌ |
| Named Slots Designate a slot in the component template | ✅ | ✅ | ✅ (using 'fragments') | ❌ |
| Dynamic Components Dynamically render components based on a variable or expression | ✅ | ✅ | ❌ | ❌ |
| Scoped Slots Reference component context in parent template | ❌ | ✅ | ❌ | ❌ |
| Dynamic Attributes Pass string literals of basic Python types | ✅ | ❌ | ❌ | ❌ |
| Boolean Attributes Pass valueless attributes as True | ✅ | ✅ | ❌ | ❌ |
| Implicit Attribute Passing Pass all defined attributes to an element | ✅ | ❌ | ✅ | ❌ |
| Django Template Expressions in Attribute Values Use template expressions in attribute values | ✅ | ❌ | ❌ | ❌ |
| Attribute Merging Replace existing attributes with component attributes | ✅ | ✅ | ❌ | ❌ |
| Multi-line Component Tags Write component tags over multiple lines | ✅ | ✅ | ❌ | ❌ |
Notes:
django-expr package. So the list focuses on a comparison of core feature of that library.For full docs and demos, checkout django-cotton.com
420 followers · starred Jul 2024
323 followers · starred Jan 2025
243 followers · starred Jun 2024
129 followers · starred Apr 2026
Enabling Modern UI Composition in Django
See the codeBringing component-based design to Django templates.
Why?
Install
Usage Basics
Your First component
Attributes
Named Slots
Pass Template Variables
Template expressions in attributes
Boolean attributes
Passing Python data types
Increase Re-usability with {{ attrs }}
Merging and Proxying Attributes with :attrs
In-component Variables with <c-vars>
Organizing in Subfolders
HTMX Example
Limitations in Django that Cotton overcomes
Template Syntax Options
Configuration
Caching
Tools
Version support
Changelog
Comparison with other packages
Cotton aims to overcome certain limitations that exist in the django template system that hold us back when we want to apply modern practices to compose UIs in a modular and reusable way.
pip install django-cotton
INSTALLED_APPS = [
'django_cotton'
]
If you have previously specified a custom loader, you should perform manual setup.
templates/cotton folder (or define a custom folder).<c-my-component />my_component.html (or configure for kebab-case)<!-- cotton/button.html -->
<a href="/" class="...">{{ slot }}</a>
<!-- in view -->
<c-button>Contact</c-button>
<!-- html output -->
<a href="/" class="...">Contact</a>
Everything provided between the opening and closing tag is provided to the component as {{ slot }}. It can contain any content, HTML or Django template expression.
<!-- cotton/button.html -->
<a href="{{ url }}" class="...">
{{ slot }}
</a>
<!-- in view -->
<c-button url="/contact">Contact</c-button>
<!-- html output -->
<a href="/contact" class="...">
Contact
</a>
Named slots are a powerful concept. They allow us to provide HTML to appear in one or more areas in the component. Here we allow the button to optionally display an svg icon:
<!-- cotton/button.html -->
<a href="{{ url }}" class="...">
{{ slot }}
{% if icon %}
<span class="some-class">
{{ icon }}
</span>
{% endif %}
</a>
<!-- in view -->
<c-button url="/contact">
Contact
<c-slot name="icon">
<svg>...</svg>
</c-slot>
</c-button>
Named slots can also contain any html or django template expression:
<!-- in view -->
<c-button url="/contact">
<c-slot name="icon">
{% if mode == 'edit' %}
<svg id="pencil">...</svg>
{% else %}
<svg id="disk">...</svg>
{% endif %}
</c-slot>
</c-button>
To pass a template variable you prepend the attribute name with a colon :. Consider a bio card component:
<!-- in view -->
<c-bio-card :user="user" />
That has a component definition like:
<!-- cotton/bio_card.html -->
<div class="...">
<img src="{{ user.avatar }}" alt="...">
{{ user.username }} {{ user.country_code }}
</div>
You can use template expression statements inside attributes.
<c-weather icon="fa-{{ icon }}"
unit="{{ unit|default:'c' }}"
condition="very {% get_intensity %}"
/>
Boolean attributes reduce boilerplate when we just want to indicate a certain attribute should be True or not.
<!-- in view -->
<c-button url="/contact" external>Contact</c-button>
By passing just the attribute name without a value, it will automatically be provided to the component as True
<!-- cotton/button.html -->
<a href="{{ url }}" {% if external %} target="_blank" {% endif %} class="...">
{{ slot }}
</a>
Cotton supports two syntaxes for passing dynamic values:
attr=value - for simple values without spaces (like native Django):attr="value" - works for any expression<!-- Quoteless: for simple literals and variables -->
<c-button enabled=True />
<c-button enabled=False />
<c-input value=my_variable />
<c-counter start=42 />
<!-- Colon prefix: required for complex expressions with spaces/quotes -->
<c-select :options="['yes', 'no', 'maybe']" />
<c-card :config="{'open': True}" />
Both approaches support:
This benefits a number of use-cases, for example if you have a select component that you want to provide the possible options from the parent:
<!-- cotton/select.html -->
<select {{ attrs }}>
{% for option in options %}
<option value="{{ option }}">{{ option }}</option>
{% endfor %}
</select>
<c-select name="q1" :options="['yes', 'no', 'maybe']" />
<!-- source code output -->
<select name="q1">
<option value="yes">yes</option>
<option value="no">no</option>
<option value="maybe">maybe</option>
</select>
{{ attrs }}{{ attrs }} is a special variable that contains all the attributes passed to the component (except those defined as c-vars) in a key="value" format. This is useful when you want to pass all attributes to a child element without having to explicitly define them in the component template. For example, you have inputs that can have any number of attributes defined:
<!-- cotton/input.html -->
<input type="text" class="..." {{ attrs }} />
<!-- example usage -->
<c-input placeholder="Enter your name" />
<c-input name="country" id="country" value="Japan" required />
<!-- html output -->
<input type="text" class="..." placeholder="Enter your name" />
<input type="text" class="..." name="country" id="country" value="Japan" required />
:attrsWhile {{ attrs }} is great for outputting all attributes passed to a component, Cotton provides more control with the special dynamic attribute :attrs.
Merge a dictionary of attributes
You can pass a dictionary of attributes (e.g., from your Django view's context) to a component using the :attrs syntax. These attributes are then merged with any other attributes passed directly and become available in the component's attrs variable (for use with {{ attrs }}). This is useful for applying a pre-defined set of attributes.
<!-- cotton/input.html -->
<input type="text" {{ attrs }} />
# In your view context
context = {
'widget_attrs': {
'placeholder': 'Enter your name',
'data-validate': 'true',
'size': '40'
}
}
<!-- In your template (e.g., form_view.html) -->
<c-input :attrs="widget_attrs" required />
<!-- HTML output -->
<input type="text" placeholder="Enter your name" data-validate="true" size="40" required />
Notice how required (passed directly to <c-input>) is merged with attributes from widget_attrs.
Proxy attributes to a nested component
The :attrs attribute also allows you to pass all attributes received by a wrapper component directly to a nested component. This is powerful for creating higher-order components or wrapping existing ones. When you use <c-child :attrs="attrs">, the child component receives the attrs dictionary of the parent.
<!-- cotton/outer_wrapper.html -->
<c-vars message /> <!-- 'message' is for outer_wrapper, not to be passed via attrs -->
<p>Outer message: {{ message }}</p>
<c-inner-component :attrs="attrs">
{{ slot }}
</c-inner-component>
<!-- cotton/inner_component.html -->
<div class="inner {{ class }}">
{{ slot }}
</div>
<!-- In view -->
<c-outer-wrapper message="Hello from outside"
class="special-class">
Inner content
</c-outer-wrapper>
<!-- HTML output -->
<p>Outer message: Hello from outside</p>
<div class="inner special-class">
Inner content
</div>
Attributes like class are passed from <c-outer_wrapper> to <c-inner_component> via its attrs variable, while message (declared in <c-vars>) is used by outer-wrapper itself and excluded from the attrs passed down.
<c-vars>Django templates adhere quite strictly to the MVC model and does not permit a lot of data manipulation in views. Fair enough, but what if we want to handle data for the purpose of UI state only? Having presentation related variables defined in the back is overkill and can quickly lead to higher maintenance cost and loses encapsulation of the component. Cotton allows you define in-component variables for the following reasons:
<c-vars> for default attributesIn this example we have a button component with a default "theme" but it can be overridden.
<!-- cotton/button.html -->
<c-vars theme="bg-purple-500" />
<a href="..." class="{{ theme }}">
{{ slot }}
</a>
<!-- in view -->
<c-button>I'm a purple button</c-button>
<!-- html output -->
<a href="..." class="bg-purple-500">
I'm a purple button
</a>
Now we have a default theme for our button, but it is overridable:
<!-- in view -->
<c-button theme="bg-green-500">But I'm green</c-button>
<!-- html output -->
<a href="..." class="bg-green-500">
But I'm green
</a>
<c-vars> to govern {{ attrs }}Using {{ attrs }} to pass all attributes from parent scope onto an element in the component, you'll sometimes want to provide additional properties to the component which are not intended to be an attributes. In this case you can declare them in <c-vars /> and it will prevent it from being in {{ attrs }}
Take this example where we want to provide any number of attributes to an input but also an icon setting which is not intened to be an attribute on <input>:
<!-- in view -->
<c-input type="password" id="password" icon="padlock" />
<!-- cotton/input.html -->
<c-vars icon />
<img src="icons/{{ icon }}.png" />
<input {{ attrs }} />
Input will have all attributes provided apart from the icon:
<input type="password" id="password" />
Use dot notation in component names to organize components in subfolders. This keeps large projects easier to navigate and maintain.
cotton/sidebar/menu/link.html is used as <c-sidebar.menu.link />cotton/ui/alert.html is used as <c-ui.alert />index.html in a folder to define the default component (e.g. cotton/card/index.html for <c-card />)Example structure for larger projects:
templates/cotton/
├── card/
│ ├── index.html
│ └── header.html
├── form/
│ ├── input.html
│ └── button.html
└── ui/
├── alert.html
└── card.html
<!-- Usage -->
<c-card>
<c-card.header>...</c-card.header>
</c-card>
<c-form.input placeholder="Name" />
<c-ui.alert>Success!</c-ui.alert>
Sometimes there is a need to include a component dynamically, for example, you are looping through some data and the type of component is defined within a variable.
<!--
form_fields = [
{'type': 'text'},
{'type': 'textarea'},
{'type': 'checkbox'}
]
-->
{% for field in form_fields %}
<c-component :is="field.type" />
{% endfor %}
You can also provide a template expression, should the component be inside a subdirectory or have a prefix:
{% for field in form_fields %}
<!-- subfolder -->
<c-component is="form-fields.{{ field.type }}" />
<!-- component prefix -->
<c-component is="field_{{ field.type }}" />
{% endfor %}
Cotton helps you build re-usable HTMX-powered components. Define your styles and markup once, then pass different HTMX attributes via attributes:
<!-- cotton/button.html -->
<button {{ attrs }} class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
{{ slot }}
</button>
<!-- in view -->
<c-button hx-post="/users/1/follow" hx-swap="outerHTML">
Follow
</c-button>
<c-button hx-delete="/posts/1" hx-confirm="Are you sure?">
Delete Post
</c-button>
<c-button hx-get="/notifications" hx-target="#notifications">
Load More
</c-button>
When building HTMX-powered interfaces, you often need to return partial HTML from view functions. Cotton provides render_component() to programmatically render components from views:
<!-- cotton/notification.html -->
<div class="alert alert-{{ type }}">{{ message }}</div>
# views.py
from django.http import HttpResponse
from django_cotton import render_component
def user_deleted(request, id):
user = User.objects.get(id=id)
user.delete()
return HttpResponse(
render_component(request, "notification",
message=f"{user.name} deleted",
type="success"
)
)
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:
{% block %} and {% extends %}This system strongly couples child and parent templates making it hard to create a truly re-usable component that can be used in places without it having a related base template.
{% 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.
{% with %}?Whilst {% with %} tags allow us to provide variables and strings it quickly busies up your code and has the same limitations about passing more complex types.
{% templatetags %}Cotton does essentially compile down to templatetags but there is some extra work it performs above it to help with scoping and auto-managing keys which will be difficult to manage manually in complex nested structures.
In addition, Cotton enables you to navigate around some of the limitations with Django's native tags and template language:
❌ Django native:
{% my_header icon="<svg>...</svg>" %}
✅ Cotton:
<c-my-header>
<c-slot name="icon">
<svg>...</svg>
</c-slot>
</c-my-header>
❌ Django native:
{% bio name="{{ first_name }} {{ last_name }}" extra="{% get_extra %}" %}
✅ Cotton:
<c-bio name="{{ first_name }} {{ last_name }}" extra="{% get_extra %} />
❌ Django native:
{% my_component default_options="['yes', 'no', 'maybe']" %}
{% my_component config="{'open': True}" %}
{% my_component enabled="True" %}
✅ Cotton:
<c-my-component :default_options="['yes', 'no', 'maybe']" />
<c-my-component :config="{'open': True}" />
<c-my-component :enabled="True" />
(provides a List and Dict to component)
❌ Django native:
{% my_component
arg=1 %}
✅ Cotton:
<c-my-component
class="blue"
x-data="{
something: 1
}" />
❌ Django native:
{% {{ templatetag_name }} arg=1 %}
✅ Cotton:
<c-component :is="component_name" />
<c-component is="{{ component_name }}" />
<c-component is="subfolder1.subfolder2.{{ component_name }}" />
Cotton supports two syntax styles for using components in your templates:
This syntax has better IDE support - code formatting, autocompletion, autoclosing and syntax highlighting.
For those who prefer Django's native template tag style, Cotton provides equivalent template tags for all features.
| Feature | HTML-like Syntax | Native Template Syntax |
|---|---|---|
| Component | <c-button>...</c-button> | {% cotton button %}...{% endcotton %} |
| Self-closing | <c-button /> | {% cotton button / %} |
| Variables | <c-vars title /> | {% cotton:vars title %} |
| Named Slot | <c-slot name="header">...</c-slot> | {% cotton:slot header %}...{% endcotton:slot %} |
COTTON_DIR (default: "cotton")
The directory where your components are stored.
COTTON_BASE_DIR (default: None)
If you use a project-level templates folder then you can set the path here. This is not needed if your project already has a BASE_DIR variable.
COTTON_SNAKE_CASED_NAMES (default: True)
Whether to search for component filenames in snake_case. If set to False, you can use kebab-cased / hyphenated filenames.
COTTON_ISOLATE_BY_DEFAULT (default: False)
When set to True, every component is rendered with Smart Isolation: it cannot see variables from the surrounding parent template (preventing accidental context leaks via {% with %}, {% for %}, the view context, etc.), but it does still receive global context-processor output such as request, user, messages, perms and any of your own custom processors.
Context-processor output is captured once per request and reused across every component on the page — processor functions are not re-invoked per component, so DB-backed processors stay cheap regardless of how deeply you nest components.
For total isolation on an individual component (block context processors too), add the only flag on the tag itself.
This setting replaces the experimental COTTON_ENABLE_CONTEXT_ISOLATION flag, which continues to work but emits a DeprecationWarning.
Cotton includes its own compilation cache that avoids re-processing component syntax when template files haven't changed (based on file modification time). For full performance, Cotton should also be used with Django's cached.Loader, which additionally caches fully parsed Template objects and avoids disk reads entirely. If you use automatic configuration (the default), the cached loader is already applied automatically.
| Feature | Cotton | django-components | Slippers | Template Partials |
|---|---|---|---|---|
| Intro | UI-focused, expressive syntax | Holistic solution with backend logic | Enhances DTL for reusable components | Inline named partials for HTMX |
| Definition of 'component' | An HTML template | A backend class with template | An HTML template | A named fragment within a template |
| Syntax Style | HTML-like | Django Template Tags | Django Template Tags with custom tags | Django Template Tags |
| One-step package install | ✅ | ❌ | ❌ | ✅ (built-in since Django 6.0) |
| Create component in one step? | ✅ (place in folder) | ✅ (Technically yes with single-file components) | ❌ (need to register in YAML file or with function) | ✅ (define with {% partialdef %}) |
| Renderable from views Render a component directly from a Python view | ✅ | ✅ | ❌ | ✅ (via template#partial syntax) |
| Slots Pass HTML content between tags | ✅ | ✅ | ✅ | ❌ |
| Named Slots Designate a slot in the component template | ✅ | ✅ | ✅ (using 'fragments') | ❌ |
| Dynamic Components Dynamically render components based on a variable or expression | ✅ | ✅ | ❌ | ❌ |
| Scoped Slots Reference component context in parent template | ❌ | ✅ | ❌ | ❌ |
| Dynamic Attributes Pass string literals of basic Python types | ✅ | ❌ | ❌ | ❌ |
| Boolean Attributes Pass valueless attributes as True | ✅ | ✅ | ❌ | ❌ |
| Implicit Attribute Passing Pass all defined attributes to an element | ✅ | ❌ | ✅ | ❌ |
| Django Template Expressions in Attribute Values Use template expressions in attribute values | ✅ | ❌ | ❌ | ❌ |
| Attribute Merging Replace existing attributes with component attributes | ✅ | ✅ | ❌ | ❌ |
| Multi-line Component Tags Write component tags over multiple lines | ✅ | ✅ | ❌ | ❌ |
Notes:
django-expr package. So the list focuses on a comparison of core feature of that library.For full docs and demos, checkout django-cotton.com
420 followers · starred Jul 2024
323 followers · starred Jan 2025
243 followers · starred Jun 2024
129 followers · starred Apr 2026