diff --git a/pygittools/tasks.py b/pygittools/tasks.py
index ebbef5f..dc39a83 100644
--- a/pygittools/tasks.py
+++ b/pygittools/tasks.py
@@ -108,6 +108,7 @@ class Task:
 		priority: Priority | None = None,
 		assignee: str | None = None,
 		due_date: datetime | None = None,
+		tags: str = "",
 	):
 		if name is None:
 			name = f"{TASK_REF_PREFIX}{title.lower().replace(' ', '_')}"
@@ -124,6 +125,7 @@ class Task:
 		self.priority = priority
 		self.assignee = assignee
 		self.due_date = due_date
+		self.tags = tags
 		self.created_at = datetime.now()
 		self.comments: list[Oid | str] = []  # Comment OIDs
 		self.update_message()
@@ -180,6 +182,7 @@ def get_task(repo: Repository, ref: str) -> Task | None:
 		priority=priority,
 		assignee=j.get("assignee"),
 		due_date=_parse_dt(j.get("due_date")),
+		tags=j.get("tags", "") or "",
 	)
 	t.comments = j.get("comments", [])
 	t.created_at = _parse_dt(j.get("created_at")) or t.created_at
@@ -205,6 +208,7 @@ def get_task_by_oid(repo: Repository, oid: Oid) -> Task | None:
 		priority=priority,
 		assignee=j.get("assignee"),
 		due_date=_parse_dt(j.get("due_date")),
+		tags=j.get("tags", "") or "",
 	)
 	t.comments = j.get("comments", [])
 	t.created_at = _parse_dt(j.get("created_at")) or t.created_at
diff --git a/pygitweb/static/board.js b/pygitweb/static/board.js
index 8f83ae2..42db49e 100644
--- a/pygitweb/static/board.js
+++ b/pygitweb/static/board.js
@@ -225,11 +225,13 @@
       var statusEl = details.querySelector('.board-card-status');
       var priorityEl = details.querySelector('.board-card-priority');
       var assigneeEl = details.querySelector('.board-card-assignee');
+      var tagsEl = details.querySelector('.board-card-tags');
       if (dueDate) dueDate.textContent = task.due_date || 'None';
       if (updatedAt) updatedAt.textContent = task.updated_at || 'None';
       if (statusEl) statusEl.textContent = status;
       if (priorityEl) priorityEl.textContent = task.priority || 'LOW';
       if (assigneeEl) assigneeEl.textContent = task.assignee || 'None';
+      if (tagsEl) tagsEl.textContent = task.tags || '';
       updateCardCommentCount(card, task.comments_count || 0);
     }
     return clone;
@@ -617,6 +619,7 @@
     var dueEl = popoutCard.querySelector('.board-card-due-date');
     var statusEl = popoutCard.querySelector('.board-card-status');
     var priorityEl = popoutCard.querySelector('.board-card-priority');
+    var tagsEl = popoutCard.querySelector('.board-card-tags');
 
     function pushTaskUpdate() {
       var taskRef = popoutCard.dataset.taskRef;
@@ -629,16 +632,19 @@
       var sEl = popoutCard.querySelector('.board-card-status');
       var pEl = popoutCard.querySelector('.board-card-priority');
       var aEl = popoutCard.querySelector('.board-card-assignee');
+      var tagsEl_ = popoutCard.querySelector('.board-card-tags');
       var dueVal = (dueEl_ && dueEl_.textContent) ? dueEl_.textContent.trim() : '';
       var statusDisplay = (sEl && sEl.textContent) ? sEl.textContent.trim() : 'TODO';
       var priorityDisplay = (pEl && pEl.textContent) ? pEl.textContent.trim() : 'LOW';
+      var tagsVal = (tagsEl_ && tagsEl_.textContent) ? tagsEl_.textContent.trim() : '';
       var body = {
         title: (tEl && tEl.textContent) ? tEl.textContent.trim() || 'Untitled' : 'Untitled',
         description: getMarkdownFromElement(dEl),
         status: statusDisplay ? statusDisplay.replace(/\s+/g, '_').toUpperCase() : 'TODO',
         priority: priorityDisplay ? priorityDisplay.replace(/\s+/g, '_').toUpperCase() : 'LOW',
         assignee: (aEl && aEl.textContent && aEl.textContent.trim() !== 'None') ? aEl.textContent.trim() : null,
-        due_date: (dueVal && dueVal !== 'None') ? dueVal : null
+        due_date: (dueVal && dueVal !== 'None') ? dueVal : null,
+        tags: tagsVal || ''
       };
       fetch('/tasks/update?' + new URLSearchParams({ project: ctx.project, board: ctx.board, task: taskRef }).toString(), {
         method: 'POST',
@@ -783,6 +789,12 @@
         sel.addEventListener('change', commitPriority);
       });
     }
+
+    if (tagsEl) {
+      tagsEl.addEventListener('blur', function () {
+        pushTaskUpdate();
+      });
+    }
   }
 
   function showCardPopout(card) {
diff --git a/pygitweb/static/main.css b/pygitweb/static/main.css
index aef0f90..f995e32 100644
--- a/pygitweb/static/main.css
+++ b/pygitweb/static/main.css
@@ -1126,6 +1126,12 @@ pre {
 		text-transform: uppercase;
 	}
 
+	.board-card-tags[contenteditable="true"]:empty::before {
+		content: "(click to add tags)";
+		color: var(--pgw-text-muted);
+		opacity: 0.8;
+	}
+
 	.board-card-comment-body {
 		min-width: 0;
 		flex: 1;
diff --git a/pygitweb/tasks.py b/pygitweb/tasks.py
index 20342ea..9ce10cd 100644
--- a/pygitweb/tasks.py
+++ b/pygitweb/tasks.py
@@ -220,6 +220,7 @@ def _task_to_json(t: Task) -> dict[str, Any]:
 		"priority": t.priority.value if t.priority else None,
 		"assignee": t.assignee,
 		"due_date": t.due_date.isoformat() if t.due_date else None,
+		"tags": getattr(t, "tags", "") or "",
 		"created_at": t.created_at.isoformat() if t.created_at else None,
 		"updated_at": t.updated_at.isoformat() if t.updated_at else None,
 		"comments_count": len(getattr(t, "comments", [])),
@@ -305,6 +306,7 @@ def task_create(
 	priority: str = Query("LOW", description="Task priority"),
 	assignee: str = Query("", description="Assignee"),
 	due_date: str = Query("", description="Due date ISO"),
+	tags: str = Query("", description="Task tags (KEY=VALUE list)"),
 ) -> JSONResponse:
 	"""Create a new task on a board."""
 	repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
@@ -335,6 +337,7 @@ def task_create(
 		priority=priority_enum,
 		assignee=assignee or None,
 		due_date=due,
+		tags=tags or "",
 	)
 	oid = task.write(repo)
 	b.tasks = getattr(b, "tasks", []) or []
@@ -355,7 +358,7 @@ async def task_update(
 	board: str = Query(..., description="Board name"),
 	task_ref: str = Query(..., alias="task", description="Task ref (e.g. refs/tags/tasks/task_123)"),
 ) -> JSONResponse:
-	"""Update a task (body: optional title, description, status, priority, assignee, due_date)."""
+	"""Update a task (body: optional title, description, status, priority, assignee, due_date, tags)."""
 	try:
 		body = await request.json() if request.headers.get("content-type", "").startswith("application/json") else {}
 	except Exception:
@@ -385,6 +388,8 @@ async def task_update(
 			)
 		except ValueError:
 			pass
+	if "tags" in body:
+		t.tags = str(body.get("tags") or "")
 	old_oid = str(repo.references[ref].resolve().target)
 	t.update_message()
 	new_oid = t.write(repo)
diff --git a/pygitweb/templates/board_card.html b/pygitweb/templates/board_card.html
index c705fe4..29d5201 100644
--- a/pygitweb/templates/board_card.html
+++ b/pygitweb/templates/board_card.html
@@ -24,13 +24,46 @@
       {% endif %}
     </div>
     <div class="board-card-details d-none mt-2 pt-2 border-top small">
-      <div class="mb-2"><strong class="text-muted">Ref</strong><div class="board-card-task-ref font-monospace text-muted mt-1 text-break">{% if task and task.ref %}{{ task.ref }}{% else %}{% endif %}</div></div>
-      <div class="mb-2"><strong class="text-muted">Description</strong><div class="board-card-description-full board-card-editable board-card-markdown mt-1">{% if task and task.description %}{{ task.description }}{% else %}{% endif %}</div></div>
-      <div class="mb-2"><strong class="text-muted">Due date</strong><div class="board-card-due-date board-card-editable text-muted mt-1">{% if task and task.due_date %}{{ task.due_date }}{% else %}None{% endif %}</div></div>
-      <div class="mb-2"><strong class="text-muted">Last modified</strong><div class="board-card-updated-at text-muted mt-1">{% if task and task.updated_at %}{{ task.updated_at }}{% else %}None{% endif %}</div></div>
-      <div class="mb-2"><strong class="text-muted">Status</strong><div class="board-card-status board-card-editable text-muted mt-1">{% if task %}{{ task.status or 'TODO' }}{% else %}TODO{% endif %}</div></div>
-      <div class="mb-2"><strong class="text-muted">Priority</strong><div class="board-card-priority board-card-editable text-muted mt-1">{% if task and task.priority %}{{ task.priority }}{% else %}LOW{% endif %}</div></div>
-      <div class="mb-2"><strong class="text-muted">Assignee</strong><div class="board-card-assignee text-muted mt-1">{% if task and task.assignee %}{{ task.assignee }}{% else %}None{% endif %}</div></div>
+      <div class="mb-2">
+        <strong class="text-muted">Ref</strong>
+        <div class="board-card-task-ref font-monospace text-muted mt-1 text-break">{% if task and task.ref %}{{ task.ref }}{% else %}{% endif %}</div>
+      </div>
+      <div class="mb-2">
+        <strong class="text-muted">Description</strong>
+        <div class="board-card-description-full board-card-editable board-card-markdown mt-1">{% if task and task.description %}{{ task.description }}{% else %}{% endif %}</div>
+      </div>
+      <div class="mb-2">
+        <strong class="text-muted">Due date</strong>
+        <div class="board-card-due-date board-card-editable text-muted mt-1">{% if task and task.due_date %}{{ task.due_date }}{% else %}None{% endif %}</div>
+      </div>
+      <div class="mb-2">
+        <strong class="text-muted">Last modified</strong>
+        <div class="board-card-updated-at text-muted mt-1">{% if task and task.updated_at %}{{ task.updated_at }}{% else %}None{% endif %}</div>
+      </div>
+      <div class="mb-2">
+        <strong class="text-muted">Status</strong>
+        <div class="board-card-status board-card-editable text-muted mt-1">{% if task %}{{ task.status or 'TODO' }}{% else %}TODO{% endif %}</div>
+      </div>
+      <div class="mb-2">
+        <strong class="text-muted">Priority</strong>
+        <div class="board-card-priority board-card-editable text-muted mt-1">{% if task and task.priority %}{{ task.priority }}{% else %}LOW{% endif %}</div>
+      </div>
+      <div class="mb-2">
+        <strong class="text-muted">Assignee</strong>
+        <div class="board-card-assignee text-muted mt-1">{% if task and task.assignee %}{{ task.assignee }}{% else %}None{% endif %}</div>
+      </div>
+      <div class="mb-2">
+        <details class="board-card-tags-details" open>
+          <summary class="text-muted">Tags</summary>
+          <div
+            class="board-card-tags font-monospace text-muted mt-1 small"
+            contenteditable="true"
+            spellcheck="false"
+          >
+            {% if task and task.tags %}{{ task.tags }}{% endif %}
+          </div>
+        </details>
+      </div>
       <div class="mb-0 board-card-comments-section">
         <strong class="text-muted">Comments</strong>
         <div class="board-card-comments-list mt-2"></div>
