@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 a Card, 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

Prop Type Default Description
value / v-model KanbanColumn[] 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[]) => void Called with the next columns array after a drag move. Apply it back to your state — Kanban holds no internal data.
renderCard (card, column) => ReactNode Custom card body, rendered inside the wrapping Card. React: render prop. Vue: #card scoped slot. Can return any components.
onCardClick / @card-click (card, column) => void Fired on click / Enter / Space (suppressed right after a drag).
className / class string Additional CSS class on the board root.

Color values

Props

Prop Type Default Description
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.