| name | django-cotton |
| description | Expert guide for working with Django Cotton, a component-based UI composition library. Use when creating, modifying, or testing Cotton components — covers component syntax, slots (default and named), attributes, c-vars, dynamic attributes with Python types, attribute proxying, template organization, naming conventions, and integration patterns. Essential for building reusable UI components in Django templates. |
| license | Complete terms in LICENSE.txt |
Django Cotton
Guide for working with Django Cotton, a component-based UI composition library for Django templates.
When to Use This Skill
- Creating new Cotton components
- Modifying existing Cotton components
- Working with slots (default and named)
- Passing attributes and data to components
- Testing Cotton components
- Organizing component directory structure
- Integrating Cotton with Django forms, HTMX, or Alpine.js
Core Concepts
Component Syntax
Cotton uses an intuitive HTML-like syntax:
<c-button>Click me</c-button>
<c-input name="email" />
<c-card.header>Title</c-card.header>
This syntax provides excellent IDE support including autocompletion, syntax highlighting, and auto-closing tags.
🚨 CRITICAL: NEVER add {% load cotton %} to templates
Cotton is auto-loaded via Django's builtins configuration. Adding {% load cotton %} is:
- Completely unnecessary
- Redundant
- Shows misunderstanding of the package
Just use <c-component> tags directly. No loading required.
Component Naming Convention
🚨 CRITICAL FILE NAMING:
Before creating component files, ALWAYS check the project's COTTON_SNAKE_CASED_NAMES setting:
- If
COTTON_SNAKE_CASED_NAMES = True (default): Use snake_case filenames → my_component.html
- If
COTTON_SNAKE_CASED_NAMES = False: Use kebab-case filenames → my-component.html
- If setting not found: Default to snake_case filenames
🚨 CRITICAL TEMPLATE USAGE:
ALWAYS use kebab-case in templates, regardless of filename format:
- ✅ Correct:
<c-small-box />, <c-info-box />, <c-timeline-item />
- ❌ Wrong:
<c-small_box />, <c-smallBox />, <c-SmallBox />
Example:
Filename: small_box.html (snake_case)
Template: <c-small-box /> (kebab-case)
Kebab-case in templates looks clean and professional. Snake_case in templates looks terrible.
Directory Structure
Components live in templates/cotton/:
templates/
├── cotton/ # Default component directory (configurable via COTTON_DIR)
│ ├── button.html # <c-button />
│ ├── card.html # <c-card />
│ └── card/
│ ├── index.html # <c-card /> (default)
│ ├── header.html # <c-card.header />
│ └── body.html # <c-card.body />
Subfolders: Use dot notation: <c-sidebar.menu.link /> for sidebar/menu/link.html
Index files: index.html in a folder serves as the default component for that folder.
Slots
Default Slot
The {{ slot }} variable captures content between component tags:
<a href="{{ url }}" class="btn">{{ slot }}</a>
<c-button url="/home">Click me</c-button>
<a href="/home" class="btn">Click me</a>
Named Slots
Named slots allow targeted content injection:
<div class="card">
<div class="card-header">{{ header }}</div>
<div class="card-body">{{ slot }}</div>
{% if footer %}
<div class="card-footer">{{ footer }}</div>
{% endif %}
</div>
<c-card>
<c-slot name="header">
<h3>Card Title</h3>
</c-slot>
Main content here
<c-slot name="footer">
<button>Close</button>
</c-slot>
</c-card>
Slots can contain template expressions:
<c-slot name="icon">
{% if mode == 'edit' %}
<svg id="pencil">...</svg>
{% else %}
<svg id="disk">...</svg>
{% endif %}
</c-slot>
Attributes
Basic Attributes
Pass attributes directly to components:
<c-button url="/contact" theme="primary">Contact</c-button>
<a href="{{ url }}" class="btn-{{ theme }}">{{ slot }}</a>
The {{ attrs }} Variable
Use {{ attrs }} to spread all passed attributes as HTML attributes:
<input type="text" class="form-control" {{ attrs }} />
<c-input placeholder="Enter name" name="user_name" required />
<input type="text" class="form-control" placeholder="Enter name" name="user_name" required />
Tag
Use <c-vars> to:
- Define default attribute values
- Declare component-internal variables (excluded from
{{ attrs }})
<c-vars theme="primary" size="md" />
<a href="{{ url }}" class="btn btn-{{ theme }} btn-{{ size }}">
{{ slot }}
</a>
<c-button url="/home">Default button</c-button>
<c-button url="/save" theme="success">Save button</c-button>
Preventing attributes from rendering as HTML:
<c-vars icon />
{% if icon %}
<img src="icons/{{ icon }}.png" />
{% endif %}
<input {{ attrs }} />
<c-input type="password" id="pass" icon="padlock" />
<img src="icons/padlock.png" />
<input type="password" id="pass" />
Dynamic Attributes
Passing Python Types
Quoteless syntax (simple values):
<c-button enabled=True />
<c-input value=my_variable />
<c-counter start=42 />
Colon prefix (complex expressions with spaces/quotes):
<c-select :options="['yes', 'no', 'maybe']" />
<c-card :config="{'open': True, 'theme': 'dark'}" />
<c-bio-card :user="user" />
Template expressions in attributes:
<c-weather icon="fa-{{ icon }}"
unit="{{ unit|default:'c' }}"
condition="very {% get_intensity %}" />
Attribute Proxying
Pass attributes from parent to child components using :attrs:
<c-vars message />
<p>Message: {{ message }}</p>
<c-inner-component :attrs="attrs">{{ slot }}</c-inner-component>
<div class="inner {{ class }}">{{ slot }}</div>
<c-outer-wrapper message="Hello" class="special">Content</c-outer-wrapper>
<p>Message: Hello</p>
<div class="inner special">Content</div>
Pattern: Use this to build higher-order components (e.g., form fields with labels and error handling).
Dynamic Component Rendering
Render components dynamically based on variable names:
<c-component :is="component_name" />
<c-component is="{{ component_name }}" />
<c-component is="form-fields.{{ field_type }}" />
{% for field in form_fields %}
<c-component :is="field.type" :attrs="field.attrs" />
{% endfor %}
Configuration
Django Settings
INSTALLED_APPS = [
'django_cotton',
'django_cotton.apps.SimpleAppConfig',
]
COTTON_DIR = 'cotton'
COTTON_BASE_DIR = None
COTTON_SNAKE_CASED_NAMES = True
Manual Template Configuration
For custom loaders (e.g., with django-template-partials):
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'OPTIONS': {
'loaders': [
('django.template.loaders.cached.Loader', [
'django_cotton.cotton_loader.Loader',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
]),
],
'builtins': [
'django_cotton.templatetags.cotton',
],
},
},
]
Discovering Available Components
🚨 CRITICAL: Always discover available components before assuming they exist
Users may have Cotton components from:
- Project-level
templates/cotton/ directory
- Third-party packages (e.g.,
django-cotton-bs5, django-mvp)
- App-level component directories
Using the Discovery Script
This skill provides a script to discover all available components. Use it to get a complete list:
poetry run python manage.py shell < .github/skills/django-cotton/scripts/discover_components.py
The script will:
- Check the
COTTON_DIR setting (defaults to 'cotton')
- Use Django's template engine to find all template directories
- Discover all
.html files in the cotton directory
- Convert filenames to component names (kebab-case)
- Group components by namespace for easy viewing
Output example:
============================================================
Found 42 Cotton components:
============================================================
(root):
<c-card />
<c-button />
adminlte:
<c-adminlte.small-box />
<c-adminlte.info-box />
============================================================
When to Use Component Discovery
- When user asks "what components are available?"
- Before suggesting a component, verify it exists
- When working with third-party packages (django-cotton-bs5, django-mvp, etc.)
- When debugging "component not found" errors
- When exploring a new project
Integration Patterns
With HTMX
Cotton components work seamlessly with HTMX for partial HTML responses:
<c-vars item />
<div class="todo-item" hx-delete="/todos/{{ item.id }}/" hx-target="this" hx-swap="outerHTML">
<input type="checkbox" {% if item.done %}checked{% endif %} />
<span>{{ item.text }}</span>
</div>
With Alpine.js
Components can contain Alpine.js directives:
<div x-data="{ open: false }">
<button @click="open = !open">{{ label }}</button>
<div x-show="open" @click.away="open = false">
{{ slot }}
</div>
</div>
With Django Forms
Wrap form widgets in Cotton components:
<c-vars field label help_text />
<div class="form-group">
{% if label %}
<label for="{{ field.id_for_label }}">{{ label }}</label>
{% endif %}
{{ field }}
{% if field.errors %}
<div class="errors">{{ field.errors }}</div>
{% endif %}
{% if help_text %}
<small class="help">{{ help_text }}</small>
{% endif %}
</div>
<c-form-field :field="form.email" label="Email Address" />
Best Practices
Component Organization
- Keep components focused — One responsibility per component
- Use subfolders for related components (
card/index.html, card/header.html)
- Leverage index.html for default folder components
- Name descriptively —
todo_item.html, not item.html
Attribute Design
- Use
<c-vars> for defaults — Provide sensible defaults, allow overrides
- Use
{{ attrs }} for flexibility — Let users pass arbitrary HTML attributes
- Keep internal state in
<c-vars> — Don't leak implementation details to {{ attrs }}
- Document expected attributes — Comment at top of component file
Slot Design
- Always provide
{{ slot }} for main content
- Use named slots for structured content (headers, footers, actions)
- Make optional slots truly optional — Use
{% if slot_name %} checks
- Default to semantic HTML — Slots should enhance structure, not replace it
Testing
Use pytest fixtures from django-cotton-bs5 (or create similar fixtures):
def test_button_renders(cotton_render):
"""Test basic button component rendering"""
html = cotton_render('button', {'url': '/home', 'slot': 'Click me'})
assert 'href="/home"' in html
assert 'Click me' in html
def test_card_with_slots(cotton_render_soup):
"""Test card with named slots"""
soup = cotton_render_soup('card', {
'slot': 'Main content',
'header': '<h2>Title</h2>',
'footer': '<button>Close</button>'
})
assert soup.select_one('.card-header h2').text == 'Title'
assert soup.select_one('.card-body').text.strip() == 'Main content'
assert soup.select_one('.card-footer button').text == 'Close'
def test_component_attrs(cotton_render_soup):
"""Test attribute spreading"""
soup = cotton_render_soup('input', {
'attrs': {'name': 'email', 'placeholder': 'Enter email', 'required': True}
})
input_tag = soup.find('input')
assert input_tag['name'] == 'email'
assert input_tag['placeholder'] == 'Enter email'
assert input_tag.has_attr('required')
Common Patterns
Wrapper components:
<c-vars title variant="default" />
<div class="panel panel-{{ variant }}">
{% if title %}
<div class="panel-header">{{ title }}</div>
{% endif %}
<div class="panel-body">{{ slot }}</div>
</div>
Icon components:
<c-vars name size="md" />
<i class="icon icon-{{ name }} icon-{{ size }}"></i>
List components:
<c-vars items />
<ul>
{% for item in items %}
<li>{{ item }}</li>
{% endfor %}
</ul>
Common Pitfalls
- Using camelCase/PascalCase — Always use snake_case or kebab-case
- Using
|default filter for defaults — Use <c-vars> with default values instead
- Not using
<c-vars> for internal attributes — Results in unwanted HTML attributes
- Overcomplicating components — Keep them simple and composable
- Not checking optional slots — Always wrap optional slots in
{% if slot_name %}
Troubleshooting
Component not found:
- Check filename matches usage:
<c-my-button /> → my_button.html
- Verify component is in
templates/cotton/ (or your COTTON_DIR)
- Check
COTTON_SNAKE_CASED_NAMES setting matches your naming convention
Attributes not rendering:
- Ensure you're using
{{ attrs }} in the component
- Check if attribute is declared in
<c-vars> (which excludes it from {{ attrs }})
Slot content not appearing:
- Verify
{{ slot }} or {{ slot_name }} is in the component template
- For named slots, check spelling matches:
<c-slot name="header"> → {{ header }}