Quickstart#
This guide will get you up and running with django-tag-me quickly.
Installation#
Install django-tag-me:
pip install django-tag-me
Add to
INSTALLED_APPSin yoursettings.py:
INSTALLED_APPS = [
# ...
'tag_me',
# ...
]
Run migrations:
python manage.py migrate tag_me
Frontend Requirements#
django-tag-me requires:
Alpine.js 3.x - For interactive components
Tailwind CSS - For styling (uses
tm-prefix to avoid conflicts)
Theming & Layout#
The tag-me select widget uses CSS custom properties to control its closed-state height and background. This ensures it aligns with whatever input styling your design system uses.
/* Defaults:
* Height: 36px (matches Tailwind UI: py-1.5 + text-sm/leading-6)
* Background: surface-container-high (MD3 outlined text field fill)
*/
/* Option A: Set the tag-me variables directly */
:root {
--tm-input-height: 38px;
--tm-input-bg: var(--color-surface-container);
}
/* Option B: Bridge from your design system tokens (recommended) */
:root {
--input-height: 36px;
--input-bg: var(--color-surface-container-high);
--tm-input-height: var(--input-height);
--tm-input-bg: var(--input-bg);
}
Height uses a min-height, so the widget still grows naturally on mobile or
when selected tags wrap to multiple lines.
Note
Both defaults are built into the widget as var() fallbacks — if you
don’t set the variables, it just works. When you do set them on :root,
the widget inherits your values with no specificity issues.
Tip
If you’re using Tailwind with the standard py-1.5 + sm:text-sm/leading-6
input styling, the 36px default should match out of the box.
Basic Usage#
In your
models.py:
from django.db import models
from tag_me.models import TagMeCharField
class YourModel(models.Model):
tags = TagMeCharField(blank=True)
In your
forms.py:
from django import forms
from tag_me.forms import TagMeModelFormMixin
from .models import YourModel
class YourForm(TagMeModelFormMixin, forms.ModelForm):
class Meta:
model = YourModel
fields = ['tags']
In your base template (
base.html):
<!DOCTYPE html>
<html>
<head>
{% block extra_css %}{% endblock %}
{{ form.media.css }}
</head>
<body>
{% block content %}{% endblock %}
{% block extra_js %}{% endblock %}
{# tag-me JS must load before Alpine.js starts #}
{{ form.media.js }}
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>
</body>
</html>
Important
The tag-me JavaScript ({{ form.media.js }}) must load before
Alpine.js. It registers the Alpine component that the widget depends on.
If Alpine starts before the component is registered, the widget will not
function.
In your page template:
{% extends 'base.html' %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Save</button>
</form>
{% endblock %}
That’s it! You now have a working tag selection widget.
Tip
If your project doesn’t use Tailwind CSS, you can add the development CDN
to your base template’s <head> for testing:
<script src="https://cdn.tailwindcss.com"></script>
This is not recommended for production. See /how-to/installation for production CSS setup.
Form Autosave Integration#
django-tag-me’s widget implements a two-way event contract that makes it compatible with form-level autosave, dirty-tracking, and draft restoration systems.
Save direction: When a user adds or removes tags, the widget dispatches a
standard change event (with bubbles: true) on its hidden input. Any
form-level listener — autosave, validation, or dirty-tracking — will
automatically detect tag changes without additional configuration.
Restore direction: When an external system (autosave, form pre-fill, test
harness) programmatically sets the hidden input’s value, it can notify the
widget by dispatching a form-field:external-update custom event on that
input. The widget will re-read the value and update its visual state to match.
// Example: restoring a saved tag value externally
const input = document.querySelector('input[name="tags"]');
input.value = 'python,django,alpine,';
input.dispatchEvent(
new CustomEvent('form-field:external-update', { bubbles: true })
);
No configuration is needed for the save direction — it works out of the box. The restore direction requires the external system to dispatch the custom event after setting the value.
Note
The hidden input’s value uses a trailing comma as a format sentinel
(e.g. python,django,). This ensures multi-word tags like
machine learning are parsed correctly. The trailing comma is handled
transparently by the widget and the Django field.
Next Steps#
/how-to/installation - Detailed installation and setup options
/how-to/customization - Customize the widget appearance and behavior
/reference/fields - Learn about TagMeCharField options
/reference/widgets - Learn about widget configuration