KanbanBoard
A lightweight kanban board component with no external drag-and-drop library dependencies. Bold-bordered columns and sharp-shadow cards embody the tension of Neo-Brutalism.
Demo
待办
设计首页 Banner
确定配色与排版方案
编写组件文档
进行中
开发 Kanban 组件
支持拖拽排序
已完成
Accordion 组件
Timeline 组件
列拖拽排序与自定义添加按钮
拖拽列标题可重排顺序;待办列使用自定义「新建任务」按钮。
Backlog
调研需求
Progress
搭建脚手架
Review
代码评审
Installation
pnpm dlx brutx-vue@latest add kanbanUsage
<script setup>
import { KanbanBoard } from 'brutx-ui-vue'
import type { KanbanColumn } from 'brutx-ui-vue'
import { ref } from 'vue'
const columns = ref<KanbanColumn[]>([
{
id: 'todo',
title: 'Todo',
color: '#FFE66D',
cards: [
{ id: 'c1', title: 'Requirement analysis', tags: ['Dev'] },
],
},
{
id: 'done',
title: 'Done',
cards: [],
},
])
</script>
<template>
<KanbanBoard
v-model="columns"
@card-move="(id, from, to) => console.log(id, from, to)"
/>
</template>Column Reordering
Drag column headers to reorder columns. The ordering is synced via v-model and triggers the column-move event.
<script setup>
import { ref } from 'vue'
import { KanbanBoard } from 'brutx-ui-vue'
const columns = ref([
{ id: 'todo', title: 'Todo', cards: [] },
{ id: 'done', title: 'Done', cards: [] },
])
function handleColumnMove(columnId, fromIndex, toIndex) {
console.log('Column', columnId, 'moved from', fromIndex, 'to', toIndex)
}
</script>
<template>
<KanbanBoard v-model="columns" @column-move="handleColumnMove" />
</template>Custom Add Card Entry
Each column renders an outline style "Add card" button at the bottom by default, which triggers the add-card event on click. Use the #add-{columnId} slot to replace it with a custom entry.
<script setup>
import { ref } from 'vue'
import { KanbanBoard, Button } from 'brutx-ui-vue'
const columns = ref([
{ id: 'todo', title: 'Todo', cards: [] },
])
function handleAddCard(columnId) {
columns.value[0].cards.push({ id: Date.now().toString(), title: 'New card' })
}
</script>
<template>
<KanbanBoard v-model="columns" @add-card="handleAddCard">
<template #add-todo="{ columnId }">
<Button variant="primary" size="sm" class="w-full" @click="handleAddCard(columnId)">
New Task
</Button>
</template>
</KanbanBoard>
</template>Data Types
interface KanbanCard {
id: string
title: string
description?: string
tags?: string[]
color?: string
}
interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}Programmatic Control
<script setup>
import { ref } from 'vue'
import { KanbanBoard } from 'brutx-ui-vue'
const kanbanRef = ref(null)
const columns = ref([
{ id: 'todo', title: 'Todo', cards: [{ id: 'card-1', title: 'Task 1' }] },
{ id: 'done', title: 'Done', cards: [] },
])
function moveCardRight() {
kanbanRef.value?.moveCard('card-1', 'todo', 1)
}
</script>
<template>
<KanbanBoard ref="kanbanRef" v-model="columns" />
<button @click="moveCardRight">Move Card Right</button>
</template>API Reference
| Name | Type | Default | Description |
|---|---|---|---|
modelValueRequired | KanbanColumn[] | — | The board columns and cards. Bind with v-model to receive updated data after moves, and keep column and card IDs unique. |
DetailsDeclared typeKanbanColumn[] Referenced types KanbanColumn Type definition — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}Default value details
Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | |||
class | string | undefined | — | Custom CSS classes merged onto the board container that arranges columns horizontally. |
DetailsDeclared typestring | undefined Default value details
Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | |||
| Name | Signature or type | Description |
|---|---|---|
@update:modelValue | [columns: KanbanColumn[]] | Emits a new column array after a successful card move or column reorder, before the corresponding move event. |
DetailsDeclared type[columns: KanbanColumn[]] Referenced types KanbanColumn Type definition — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
@card-move | [cardId: string, fromColumn: string, toColumn: string] | Emits the card ID, source column ID, and target column ID after a successful mouse, keyboard, or instance-method move. Both column IDs are equal for reordering within one column. |
DetailsDeclared type[cardId: string, fromColumn: string, toColumn: string] Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
@card-click | [card: KanbanCard, columnId: string] | Emits the card and column ID on a click or Enter while focused. It is suppressed during card dragging; Space grabs or releases the card. |
DetailsDeclared type[card: KanbanCard, columnId: string] Referenced types KanbanCard Type definition — KanbanCardexport interface KanbanCard {
id: string
title: string
description?: string
tags?: string[]
color?: string
}Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
@column-move | [columnId: string, fromIndex: number, toIndex: number] | Emits the column ID and old/new zero-based indices after a successful header drag reorder or moveColumn call. |
DetailsDeclared type[columnId: string, fromIndex: number, toIndex: number] Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
@add-card | [columnId: string] | Emits the column ID when the built-in add button is clicked or instance addCard is called. The consumer creates the card and updates the data. |
DetailsDeclared type[columnId: string] Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
| Name | Signature or type | Description |
|---|---|---|
#add-{columnId} | { columnId: string } | Replaces the add-card entry at the bottom of the named column and receives its ID. By default, an outline, sm Button emits add-card when clicked. |
DetailsDeclared type{ columnId: string }Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
| Name | Signature or type | Description |
|---|---|---|
moveCard | (cardId: string, columnId: string, direction: number, mode?: "in-column" | "adjacent") => void | Moves a card by direction and handles focus and announcements. adjacent moves between columns; in-column reorders within the current column. A direction of 1 means right or down; other values mean left or up. The mode defaults to adjacent. |
DetailsDeclared type(cardId: string, columnId: string, direction: number, mode?: "in-column" | "adjacent") => void Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
moveColumn | (fromId: string, toId: string) => void | Moves the fromId column to the current index of toId, shifting intervening columns. A successful move emits data-update and column-move events. |
DetailsDeclared type(fromId: string, toId: string) => void Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
addCard | (columnId: string) => void | Emits an add-card request for the given column. The method itself does not create a card. |
DetailsDeclared type(columnId: string) => void Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
getColumn | (columnId: string) => KanbanColumn | undefined | Returns the column object from the current modelValue by ID, or undefined when absent. It preserves the original object reference. |
DetailsDeclared type(columnId: string) => KanbanColumn | undefined Referenced types KanbanColumn Type definition — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
getAllColumns | () => KanbanColumn[] | Returns the current modelValue column-array reference. To edit it, construct new data and update the bound value. |
DetailsDeclared type() => KanbanColumn[] Referenced types KanbanColumn Type definition — KanbanColumnexport interface KanbanColumn {
id: string
title: string
color?: string
cards: KanbanCard[]
}Sourcepackages/ui/src/components/kanban/KanbanBoard.vue | ||
Accessibility
- Cards support keyboard navigation and can be focused via the
Tabkey - Pressing
Enterafter focusing triggerscard-click;Spacegrabs or releases the card - Empty columns display a localized prompt text to guide users to drag and drop cards
- Keyboard drag: Focus a card and press
Spaceto grab, use arrow keys to move, pressSpaceagain to drop,Escapeto cancel↑/↓: Move card up/down within the current column←/→: Move card to an adjacent column
- Grabbed cards have
aria-grabbed="true"attribute added - Move results are automatically announced via
aria-live="polite"region
Keyboard Drag Example
<script setup>
import { ref } from 'vue'
import { KanbanBoard } from 'brutx-ui-vue'
const columns = ref([
{ id: 'todo', title: 'Todo', cards: [
{ id: 'c1', title: 'Task 1' },
{ id: 'c2', title: 'Task 2' },
]},
{ id: 'done', title: 'Done', cards: [] },
])
</script>
<template>
<!-- Users can:
1. Tab to focus a card
2. Press Space to grab the card
3. Use arrow keys to move the card
4. Press Space to drop the card
5. Press Escape to cancel
-->
<KanbanBoard v-model="columns" />
</template>FAQ
Q: Why isn't the data updated after dragging a card?
A: KanbanBoard uses v-model for two-way data binding. Make sure the columns passed in are reactive data (using ref or reactive). The component automatically updates the data via the update:modelValue event after a card move or column reorder. If you use a plain variable, the data will not sync.
Q: Does dragging work on mobile devices?
A: The component is built on the native HTML5 Drag and Drop API, which may have limited support on mobile browsers. If you need to use it on mobile, test compatibility on the target device or consider providing an alternative interaction method (e.g., button-based actions) for mobile.
Q: How can I limit the number of cards in a column?
A: The component does not provide a built-in card count limit. You can implement this by listening to the card-move event, checking the card count of the target column in the event handler, and moving the card back to the original column by modifying the data if the limit is exceeded.