CTkKanban
CTkKanban is a small Kanban widget for CustomTkinter. It focuses on predictable card editing and movement instead of trying to be a database framework or a complete project-management application.
Install
python -m pip install CTkKanBan
Import the package with its canonical lowercase name:
import customtkinter as ctk
from ctk_kanban import CTkKanbanBoard
app = ctk.CTk()
app.geometry("1000x650")
board = CTkKanbanBoard(
app,
columns=[
{"id": "todo", "title": "To do"},
{"id": "doing", "title": "Doing"},
{"id": "done", "title": "Done"},
],
cards=[
{
"id": 1,
"column": "todo",
"title": "Try the simplified board",
"description": "Click for details and use the handle to drag.",
"priority": "High",
"tags": ["demo"],
}
],
on_change=lambda event: print(event["type"], event["data"]),
)
board.pack(fill="both", expand=True)
app.mainloop()
Interaction model
- Click a card to select it and open the editor drawer.
- Save explicitly with Save changes or Enter; Escape cancels.
- Drag cards only from their upper-right drag handle.
- Use the visible menu for move and delete actions.
- Columns use menu actions for left/right movement instead of column dragging.
There is no inline editing, click-away autosave, whole-card dragging, floating drag preview, or window-wide drag binding. A local Tk grab makes sure a handle drag always receives its release event.
Styling
Board surfaces, controls, text, hover states, and scrollbars follow the active
CustomTkinter color theme. Call ctk.set_default_color_theme(...) before
creating the board to use another built-in or custom theme. Priority and tag
metadata remain visible as compact colored pills. The optional theme mapping
can override individual board tokens when needed.
Data
Columns contain id and title. Cards contain id, column, title, and the
optional description, priority, and tags fields. IDs must be unique and
must be nonblank strings or integers. Priorities are empty, Low, Medium,
High, or Critical. Tags are trimmed, nonblank strings without commas.
get_data() returns a detached snapshot for application-owned storage. Use
string or integer IDs when the snapshot will be encoded as JSON.
set_data(snapshot) replaces the displayed board without emitting an event.
The optional on_change callback receives one event after each successful
add, edit, move, or delete that changes board data; event["data"] contains
the latest complete snapshot. Search also changes only the view and does not
emit an event.
Persistence, retries, paging, polling, and conflict handling intentionally live in the host application rather than the widget.
Database rows
Database results can be converted without adding a database-driver dependency
to CTkKanban. Mapping rows from psycopg dict_row, sqlite3.Row, and
SQLAlchemy are accepted by snapshot_from_rows():
from ctk_kanban import snapshot_from_rows
snapshot = snapshot_from_rows(column_rows, card_rows)
board.set_data(snapshot)
Plain DB-API tuple results can be converted using cursor metadata. Fetch each result before reusing its cursor:
from ctk_kanban import rows_from_cursor, snapshot_from_rows
cursor.execute("SELECT id, title FROM kanban_columns ORDER BY position")
columns = rows_from_cursor(cursor)
cursor.execute(
"""
SELECT id, column_id AS column, title, description, priority, tags
FROM kanban_cards
ORDER BY column_id, position
"""
)
cards = rows_from_cursor(cursor)
board.set_data(snapshot_from_rows(columns, cards))
Use SQL aliases such as column_id AS column to produce CTkKanban's exact
record keys. Result column names must be unique. rows_from_cursor() consumes
all remaining rows returned by the cursor.
snapshot_from_cursors(columns_cursor, cards_cursor) is a shorter equivalent
when two separately executed cursors are available. Every snapshot helper
normalizes and validates the complete result before returning it.
Asynchronous loading
load_async() performs fetching and validation on a daemon worker, then calls
set_data() and user callbacks safely on Tk's thread:
import psycopg
from psycopg.rows import dict_row
from ctk_kanban import snapshot_from_rows
def fetch_board():
with psycopg.connect(DATABASE_URL, row_factory=dict_row) as connection:
columns = connection.execute(COLUMN_QUERY).fetchall()
cards = connection.execute(CARD_QUERY).fetchall()
return snapshot_from_rows(columns, cards)
board.load_async(
fetch_board,
on_success=lambda snapshot: print("Loaded", len(snapshot["cards"]), "cards"),
on_error=lambda error: print("Load failed:", error),
)
board.is_loading reports pending work and board.load_error retains the most
recent asynchronous error. Existing data is preserved on failure unless
clear_on_error=True is requested. Starting a newer load makes an older result
stale, so it cannot overwrite newer data.
Pass on_card_open when the host application owns card editing. Its callback
receives the card snapshot and replaces the built-in drawer when a card opens.
Main API
get_data() / set_data(data)
get_card(id) / get_cards(column_id=None) / get_columns()
add_card() / update_card() / move_card() / delete_card()
add_column() / update_column() / move_column() / delete_column()
open_add_card_editor() / open_edit_card_editor(id)
search(query)
set_loading(bool) / load_async(fetch_snapshot, ...)
rows_from_cursor(cursor)
snapshot_from_rows(columns, cards) / snapshot_from_cursors(columns_cursor, cards_cursor)
The Tk-free BoardModel is also public for applications that want to validate
or manipulate board data without creating a window. Column.from_definition()
and Card.from_definition() expose the same normalization for typed application
code, while BoardSnapshot, ColumnRecord, and CardRecord provide public
typing shapes.
Migrating from 1.x
Version 2 is intentionally breaking. Remove dynamic field definitions, inline
editing options, persistence adapters, advanced filter/sort options, and the
large set of enable_*/show_* constructor flags. Replace mutation-specific
callbacks with on_change, and import from ctk_kanban rather than
CTkKanBan. Remove custom record keys before loading data; v2 rejects fields
outside its small schema instead of silently discarding them.
Development
python -m pip install -e ".[dev]"
python -m pytest -q
python -m ruff check .
python -m mypy ctk_kanban
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ctkkanban-2.0.1.tar.gz.
File metadata
- Download URL: ctkkanban-2.0.1.tar.gz
- Upload date:
- Size: 40.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b89bdce99b5b1e8e301c91992cd2d777c1766e0f7a19906e876d8bd27ceb563b
|
|
| MD5 |
b1f09945a7ed27c24e4bc07483b5448a
|
|
| BLAKE2b-256 |
92a2064f175fc10e0bfcf70d3358e5663498636f547a8f0319b719ea04f6ffc3
|
Provenance
The following attestation bundles were made for ctkkanban-2.0.1.tar.gz:
Publisher:
publish.yml on Harry-g25/CTkKanBan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctkkanban-2.0.1.tar.gz -
Subject digest:
b89bdce99b5b1e8e301c91992cd2d777c1766e0f7a19906e876d8bd27ceb563b - Sigstore transparency entry: 2498664251
- Sigstore integration time:
-
Permalink:
Harry-g25/CTkKanBan@71b2299c2e42372d9023d6d16b044de453c1ad9f -
Branch / Tag:
refs/tags/2.0.1 - Owner: https://github.com/Harry-g25
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@71b2299c2e42372d9023d6d16b044de453c1ad9f -
Trigger Event:
release
-
Statement type:
File details
Details for the file ctkkanban-2.0.1-py3-none-any.whl.
File metadata
- Download URL: ctkkanban-2.0.1-py3-none-any.whl
- Upload date:
- Size: 33.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67379643e84ff67e90ef2a244e74ccaeda3e4dee203388fc48be416a7f01cf89
|
|
| MD5 |
cc43a1559300a5d02e66f580279ef4fe
|
|
| BLAKE2b-256 |
7105bed86d307c96a6a34e7feca019116cb1beddbaff2e61893082ce454e3178
|
Provenance
The following attestation bundles were made for ctkkanban-2.0.1-py3-none-any.whl:
Publisher:
publish.yml on Harry-g25/CTkKanBan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ctkkanban-2.0.1-py3-none-any.whl -
Subject digest:
67379643e84ff67e90ef2a244e74ccaeda3e4dee203388fc48be416a7f01cf89 - Sigstore transparency entry: 2498664267
- Sigstore integration time:
-
Permalink:
Harry-g25/CTkKanBan@71b2299c2e42372d9023d6d16b044de453c1ad9f -
Branch / Tag:
refs/tags/2.0.1 - Owner: https://github.com/Harry-g25
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@71b2299c2e42372d9023d6d16b044de453c1ad9f -
Trigger Event:
release
-
Statement type: