@usevyre/react@usevyre/vue

Kanban

A drag-and-drop board. Cards move between columns or reorder within one. Like DataGrid, Kanban is fully controlled and data-driven — you own the columns array and apply the next one in onChange (v-model in Vue). Uses native HTML5 drag-and-drop with zero dependencies.


Basic

Pass a controlled array of columns, each with its own cards. When a card is dropped, Kanban calls onChange with the next array — store it in state to complete the move. Card ids must be unique across the whole board.

To Do2
Spec the API
Define value/onChange shape
Write docs page
In Progress1
Drag-and-drop logic
Native HTML5 DnD
Done2
Project kickoff
Design tokens

Drag a card to another column — or reorder within one.

import { useState } from "react";
import { Kanban } from "@usevyre/react";
import type { KanbanColumn } from "@usevyre/react";

const [columns, setColumns] = useState<KanbanColumn[]>([
  { id: "todo", title: "To Do", cards: [
    { id: "c1", title: "Spec the API", description: "Define value/onChange shape" },
    { id: "c2", title: "Write docs page" },
  ]},
  { id: "doing", title: "In Progress", cards: [
    { id: "c3", title: "Drag-and-drop logic", description: "Native HTML5 DnD" },
  ]},
  { id: "done", title: "Done", cards: [
    { id: "c4", title: "Project kickoff" },
    { id: "c5", title: "Design tokens" },
  ]},
]);

<Kanban value={columns} onChange={setColumns} />

Custom cards & click handler

Use renderCard (React render prop) or the #card scoped slot (Vue) for custom card content, and onCardClick /@card-click to open a detail view. Clicks are suppressed immediately after a drag so dropping never triggers a click. Compose the body from useVyre primitives — here Item with ItemMedia /ItemContent / ItemActions — so no extra CSS is needed.

In Review2
Auth refactor

Alex

k1
Billing webhook

Sam

k2
Ready to Ship1
Dark mode

Riya

k3

Click a card — or drag it to another column.

import {
  Kanban, Item, ItemMedia, ItemContent,
  ItemTitle, ItemDescription, ItemActions, Avatar, Badge,
} from "@usevyre/react";

<Kanban
  value={columns}
  onChange={setColumns}
  onCardClick={(card) => openDetail(card.id)}
  renderCard={(card) => (
    <Item variant="plain" size="sm" style={{ padding: 0 }}>
      <ItemMedia>
        <Avatar fallback={card.description?.[0] ?? "?"} size="sm" />
      </ItemMedia>
      <ItemContent>
        <ItemTitle>{card.title}</ItemTitle>
        <ItemDescription>{card.description}</ItemDescription>
      </ItemContent>
      <ItemActions><Badge variant="teal">{card.id}</Badge></ItemActions>
    </Item>
  )}
/>

Complex card content

A card only needs id and title — attach any extra fields (assignee, tags, progress, priority…) to the card object and read them back inside renderCard. Each card is automatically wrapped in aCard, so you only supply the body. This example composes ItemContent, TagGroup /Tag, Progress and Avatar — all useVyre components, no custom CSS — and stays fully draggable.

To Do1
Implement OAuth callback
High
backendsecurity
Alex Kim
20%
In Progress1
Redesign settings page
Medium
designfrontend
Riya Shah
60%

Cards composed from Item, TagGroup, Progress and Avatar — drag still works.

import {
  Kanban, ItemContent, ItemTitle, ItemMedia, ItemActions,
  Item, TagGroup, Tag, Badge, Progress, Avatar,
} from "@usevyre/react";

// A card only needs { id, title }. Attach any extra app fields
// and read them back in renderCard — no extra CSS needed, the
// content is composed from useVyre components.
interface TaskCard {
  id: string; title: string;
  assignee: string; initials: string;
  tags: string[]; progress: number;
  priority: "Low" | "Medium" | "High";
}

const priorityColor = {
  High: "danger", Medium: "warning", Low: "default",
} as const;

const [columns, setColumns] = useState<KanbanColumn[]>([
  { id: "todo", title: "To Do", cards: [{
    id: "t1", title: "Implement OAuth callback",
    assignee: "Alex Kim", initials: "AK",
    tags: ["backend", "security"], progress: 20, priority: "High",
  } satisfies TaskCard] },
]);

<Kanban
  value={columns}
  onChange={setColumns}
  onCardClick={(card) => openDetail(card.id)}
  renderCard={(card) => {
    const c = card as unknown as TaskCard;
    return (
      <ItemContent>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <ItemTitle style={{ flex: 1 }}>{c.title}</ItemTitle>
          <Badge variant={priorityColor[c.priority]}>{c.priority}</Badge>
        </div>
        <TagGroup gap="sm">
          {c.tags.map((t) => <Tag key={t}>{t}</Tag>)}
        </TagGroup>
        <Progress value={c.progress} size="sm" />
        <Item variant="plain" size="sm" style={{ padding: 0 }}>
          <ItemMedia><Avatar fallback={c.initials} size="sm" /></ItemMedia>
          <ItemContent><ItemTitle>{c.assignee}</ItemTitle></ItemContent>
          <ItemActions>{c.progress}%</ItemActions>
        </Item>
      </ItemContent>
    );
  }}
/>

Custom colors

Set color on a column to tint the whole column, or on an individual card to tint just that card. Values are semantic and token-based, so they adapt to light/dark themes automatically. Available:accent, teal, success,warning, danger (and the implicit default).

Backlog1
Triage incoming bugs
Active2
API rate limiting
Dashboard charts
Blocked1
Vendor SDK upgrade
Shipped1
Onboarding flow

Columns and individual cards carry a semantic color.

import { Kanban } from "@usevyre/react";
import type { KanbanColumn } from "@usevyre/react";

// color is a semantic tint: "accent" | "teal" | "success"
//                          | "warning" | "danger" | "default"
const [columns, setColumns] = useState<KanbanColumn[]>([
  { id: "active", title: "Active", color: "teal", cards: [
    { id: "g2", title: "API rate limiting", color: "warning" },
  ]},
  { id: "blocked", title: "Blocked", color: "danger", cards: [
    { id: "g4", title: "Vendor SDK upgrade", color: "danger" },
  ]},
  { id: "shipped", title: "Shipped", color: "success", cards: [
    { id: "g5", title: "Onboarding flow", color: "success" },
  ]},
]);

<Kanban value={columns} onChange={setColumns} />

Props

Props

PropTypeDefaultDescription
value / v-modelKanbanColumn[]Controlled board data. KanbanColumn = { id, title, cards, color? }. KanbanCard = { id, title, description?, color? }. Card ids must be unique across the whole board.
onChange / @update:modelValue(next: KanbanColumn[]) => voidCalled with the next columns array after a drag move. Apply it back to your state — Kanban holds no internal data.
renderCard(card, column) => ReactNodeCustom card body, rendered inside the wrapping Card. React: render prop. Vue: #card scoped slot. Can return any components.
onCardClick / @card-click(card, column) => voidFired on click / Enter / Space (suppressed right after a drag).
className / classstringAdditional CSS class on the board root.

Color values

Props

PropTypeDefaultDescription
color (column & card)"default" | "accent" | "teal" | "success" | "warning" | "danger""default"Semantic background tint. Set on a KanbanColumn to tint the whole column, or on a KanbanCard to tint that card. Token-based — adapts to theme.