{% trans "Get started" %}
{% trans "From zero to your first component" %}
{% blocktranslate with url=docs_url %}Drop the components into a Django project in 90 seconds. Three steps, three copy-and-pasteable snippets. Once you're up and running, head to Documentation for the full configuration and annotation reference.{% endblocktranslate %}
{% trans "1. Install" %}
{% blocktranslate %}Install both apps with pip, register them in settings.py, then mount the gallery URLs behind a DEBUG guard. Plug and play — no other configuration needed for first run.{% endblocktranslate %}
{% codeblock %}{% verbatim %}# settings.py
INSTALLED_APPS = [
# ...
"django_cotton",
"django_cotton_gallery",
]
COTTON_SNAKE_CASED_NAMES = False # cotton's setting; both styles work, see Documentation{% endverbatim %}{% endcodeblock %}
{% blocktranslate %}Then mount the gallery URLs in your project's urls.py — gated by DEBUG so the routes never reach production:{% endblocktranslate %}
{% codeblock %}{% verbatim %}# urls.py
from django.conf import settings
from django.urls import include, path
urlpatterns = [...]
if settings.DEBUG:
urlpatterns += [path("gallery/", include("django_cotton_gallery.urls"))]{% endverbatim %}{% endcodeblock %}
{% blocktranslate %}Visit /gallery/ in your browser. The gallery is a development tool — leaving the include guarded by DEBUG keeps it off in production by construction.{% endblocktranslate %}
{% blocktranslate %}Django Cotton's regex compiler runs before Django's template engine and converts <c-foo /> tags into {% include %} calls. The COTTON_SNAKE_CASED_NAMES = False setting keeps tag names kebab-case so file paths like atoms/ui/button.html map to <c-atoms.ui.button /> directly.{% endblocktranslate %}
{% trans "2. Use a component" %}
{% blocktranslate %}Render any component by its kebab-case path. The folder structure becomes the dotted tag name — that's the whole convention.{% endblocktranslate %}
{% codeblock %}{% verbatim %}Save
Your changes were saved.
{% endverbatim %}{% endcodeblock %}
{% blocktranslate %}That's it — your components are live in the gallery. Whatever stack they need (Tailwind, HTMX, Alpine, your own bundle…) hooks in through DJANGO_COTTON_GALLERY_EXTRA_CSS / _EXTRA_JS in settings, or the _extra_head.html partial in your templates dir. Browse the sidebar to see your components live, with their props panels.{% endblocktranslate %}
{% trans "3. Make your own" %}
{% blocktranslate %}Drop a new file under templates/cotton/. The gallery picks it up automatically — no registration, no rebuild. Add an @prop annotation comment and the controls panel builds itself.{% endblocktranslate %}
{% codeblock %}{% verbatim %}{# templates/cotton/atoms/hello.html #}
{# @prop name:text | default:"World" | description:"Who to greet" #}
Hello, {{ name }}!
{% endverbatim %}{% endcodeblock %}
{% blocktranslate %}Save the file, restart the server (Cotton caches templates), and your component shows up in the gallery's sidebar with a live preview, an editable name control, and a generated props table.{% endblocktranslate %}
{% blocktranslate %}Folder layout is flexible. Components can live at any depth — cotton/button.html, cotton/button/index.html, cotton/atoms/button.html, or cotton/atoms/button/index.html all work. The first folder under cotton/ becomes the sidebar category; loose files at the root group under "Components". When both <dir>.html and <dir>/index.html exist, the sibling wins (cotton's resolution order).{% endblocktranslate %}
{% blocktranslate with url=docs_url %}Annotation grammar (types, filters, dynamic props, slots, triggers) is covered in Documentation → Annotations. The full list of consumer settings is in Documentation → Configuration.{% endblocktranslate %}
{% trans "4. Enable language switching (optional)" %}
{% blocktranslate %}The gallery ships with translations for English, Spanish, Basque, and French. The dropdown in the sidebar footer only appears when your project has Django's i18n URL and middleware wired — without them, Django can't persist the chosen language, so the gallery hides the control to avoid a dead button.{% endblocktranslate %}
{% blocktranslate %}Two changes in your project to enable it. First, mount the i18n URLs:{% endblocktranslate %}
{% codeblock %}{% verbatim %}# urls.py
from django.urls import include, path
urlpatterns = [
path("i18n/", include("django.conf.urls.i18n")),
# ... your real app URLs
]{% endverbatim %}{% endcodeblock %}
{% blocktranslate %}Second, add LocaleMiddleware to the middleware stack. Order matters: it must come after SessionMiddleware and before CommonMiddleware:{% endblocktranslate %}
{% codeblock %}{% verbatim %}# settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
# ... rest as you had it
]{% endverbatim %}{% endcodeblock %}
{% blocktranslate %}Refresh the page and the dropdown shows up in the sidebar with the four supported locales. To expose more or fewer locales, set LANGUAGES in your settings.py — the gallery picks up whatever is configured.{% endblocktranslate %}