Kanban
kanbanuiComposable compound Kanban primitive for building board, pipeline, and agile workflows with tactile card drag-and-drop mechanics.
Installation
$pnpm dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/kanban.json$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/kanban.json$yarn dlx shadcn-svelte@latest add https://uipkge.dev/r/svelte/kanban.json$bunx shadcn-svelte@latest add https://uipkge.dev/r/svelte/kanban.jsonManifest:
https://uipkge.dev/r/svelte/kanban.jsonInstalls to:src/lib/components/ui/kanban/Variants
Loading interactive previews…
Loading interactive previews…
Examples in Use
Full BlockMulti-column agile board composing Kanban columns, drag-and-drop task cards, priority indicators, and member avatar stacks.
Props
| Name | Type / Values | Default | Required |
|---|---|---|---|
isOver | 'true''false' | — | optional |
children | Snippet | — | optional |
oncardmoveFired when a card moves within or across columns (pointer drop or keyboard move). | (event: KanbanMoveEvent) => void | — | optional |
ref | HTMLDivElement | null | — | optional |
Schema
Type aliases from this item's source — use them to shape the data you pass in.
KanbanMoveEventinterface KanbanMoveEvent {
cardId: string
fromColumnId: string
toColumnId: string
toIndex?: number
}KanbanContextinterface KanbanContext {
readonly draggingCardId: string | null
readonly draggingColumnId: string | null
readonly overColumnId: string | null
/** Set while a card is held by keyboard (Space), not by pointer drag. */
readonly grabbedCardId: string | null
setDraggingCard: (cardId: string | null, columnId: string | null) => void
setOverColumn: (columnId: string | null) => void
setGrabbedCard: (cardId: string | null) => void
emitMove: (event: KanbanMoveEvent) => void
/** Speak a message through the board's polite live region. */
announce: (message: string) => void
}KanbanColumnContextinterface KanbanColumnContext {
columnId: string
/** True while a dragged (pointer or keyboard-grabbed) card hovers this column. React twin: `isOver`. */
readonly isOver: boolean
/** Column label, used to announce keyboard moves. Falls back to the id. */
readonly label: string | undefined
}npm dependencies
Files installed (18)
src/lib/components/ui/kanban/Kanban.svelte2 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' import type { KanbanMoveEvent } from './context' export interface KanbanProps extends HTMLAttributes<HTMLDivElement> { children?: Snippet /** Fired when a card moves within or across columns (pointer drop or keyboard move). */ oncardmove?: (event: KanbanMoveEvent) => void ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' import { setKanbanContext } from './context' let { class: className, children, oncardmove, ref = $bindable(null), ...restProps }: KanbanProps = $props() let draggingCardId = $state<string | null>(null) let draggingColumnId = $state<string | null>(null) let overColumnId = $state<string | null>(null) let grabbedCardId = $state<string | null>(null) // Keyboard moves are silent to a screen reader — the card just appears // somewhere else. This region narrates pick up / move / drop / cancel. let announcement = $state('') setKanbanContext({ get draggingCardId() { return draggingCardId }, get draggingColumnId() { return draggingColumnId }, get overColumnId() { return overColumnId }, get grabbedCardId() { return grabbedCardId }, setDraggingCard(cardId, columnId) { draggingCardId = cardId draggingColumnId = columnId }, setOverColumn(columnId) { overColumnId = columnId }, setGrabbedCard(cardId) { grabbedCardId = cardId }, emitMove(event) { oncardmove?.(event) }, announce(message) { // Re-assigning the same string would not re-trigger the live region. announcement = announcement === message ? `${message} ` : message }, }) </script> <div bind:this={ref} data-uipkge="" data-slot="kanban" class={cn('w-full', className)} {...restProps}> {@render children?.()} <div data-slot="kanban-live-region" class="sr-only" role="status" aria-live="polite" aria-atomic="true"> {announcement} </div> </div>src/lib/components/ui/kanban/KanbanBoard.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanBoardProps extends HTMLAttributes<HTMLDivElement> { children?: Snippet ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanBoardProps = $props() </script> <div bind:this={ref} data-slot="kanban-board" class={cn('flex w-full items-start gap-4 overflow-x-auto pb-4', className)} {...restProps} > {@render children?.()} </div>src/lib/components/ui/kanban/KanbanColumn.svelte2.4 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanColumnProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> { id: string /** Accessible name for the column, also used in keyboard move * announcements ("moved to In progress"). Falls back to the id. */ label?: string children?: Snippet<[{ isOver: boolean }]> ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' import { getKanbanContext, setKanbanColumnContext } from './context' import { kanbanColumnVariants } from './kanban.variants' let { class: className, id, label, children, ref = $bindable(null), ...restProps }: KanbanColumnProps = $props() const kanban = getKanbanContext() const isOver = $derived(kanban?.overColumnId === id) setKanbanColumnContext({ // svelte-ignore state_referenced_locally: column ids are static for the board's lifetime. columnId: id, get isOver() { return isOver }, get label() { return label }, }) function handleDragOver(e: DragEvent) { e.preventDefault() if (e.dataTransfer) { e.dataTransfer.dropEffect = 'move' } if (kanban && kanban.overColumnId !== id) { kanban.setOverColumn(id) } } function handleDragLeave(e: DragEvent & { currentTarget: HTMLElement }) { const currentTarget = e.currentTarget const relatedTarget = e.relatedTarget as HTMLElement | null if (currentTarget?.contains(relatedTarget)) return if (kanban && kanban.overColumnId === id) { kanban.setOverColumn(null) } } function handleDrop(e: DragEvent) { e.preventDefault() if (kanban && kanban.draggingCardId && kanban.draggingColumnId) { kanban.emitMove({ cardId: kanban.draggingCardId, fromColumnId: kanban.draggingColumnId, toColumnId: id, }) } kanban?.setDraggingCard(null, null) kanban?.setOverColumn(null) } </script> <div bind:this={ref} data-uipkge="" data-slot="kanban-column" role="group" aria-label={label ?? id} data-column-id={id} data-over={isOver ? '' : undefined} class={cn(kanbanColumnVariants({ isOver }), className)} ondragover={handleDragOver} ondragleave={handleDragLeave} ondrop={handleDrop} {...restProps} > {@render children?.({ isOver })} </div>src/lib/components/ui/kanban/KanbanColumnHeader.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanColumnHeaderProps extends HTMLAttributes<HTMLDivElement> { children?: Snippet ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanColumnHeaderProps = $props() </script> <div bind:this={ref} data-slot="kanban-column-header" class={cn('flex items-center justify-between gap-2 px-1 py-0.5', className)} {...restProps} > {@render children?.()} </div>src/lib/components/ui/kanban/KanbanColumnDot.svelte0.5 kB
<script lang="ts" module> import type { HTMLAttributes } from 'svelte/elements' export interface KanbanColumnDotProps extends HTMLAttributes<HTMLSpanElement> { color?: string ref?: HTMLSpanElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, color = 'bg-primary', ref = $bindable(null), ...restProps }: KanbanColumnDotProps = $props() </script> <span bind:this={ref} data-slot="kanban-column-dot" class={cn('size-2 shrink-0 rounded-full', color, className)} {...restProps}></span>src/lib/components/ui/kanban/KanbanColumnTitle.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanColumnTitleProps extends HTMLAttributes<HTMLHeadingElement> { children?: Snippet ref?: HTMLHeadingElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanColumnTitleProps = $props() </script> <h3 bind:this={ref} data-slot="kanban-column-title" class={cn('text-foreground text-sm font-semibold tracking-tight', className)} {...restProps} > {@render children?.()} </h3>src/lib/components/ui/kanban/KanbanColumnCount.svelte0.7 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanColumnCountProps extends HTMLAttributes<HTMLSpanElement> { count?: number | string children?: Snippet ref?: HTMLSpanElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, count, children, ref = $bindable(null), ...restProps }: KanbanColumnCountProps = $props() </script> <span bind:this={ref} data-slot="kanban-column-count" class={cn('bg-muted text-muted-foreground rounded-md px-1.5 py-0.5 text-xs font-medium tabular-nums', className)} {...restProps} > {#if children} {@render children()} {:else} {count} {/if} </span>src/lib/components/ui/kanban/KanbanColumnAdd.svelte0.9 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLButtonAttributes } from 'svelte/elements' export interface KanbanColumnAddProps extends HTMLButtonAttributes { children?: Snippet ref?: HTMLButtonElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' import { Plus } from '@lucide/svelte' let { class: className, type = 'button', children, ref = $bindable(null), ...restProps }: KanbanColumnAddProps = $props() </script> <button bind:this={ref} {type} data-slot="kanban-column-add" class={cn( 'text-muted-foreground hover:bg-background hover:text-foreground focus-visible:ring-ring inline-flex size-6 items-center justify-center rounded-md transition-colors focus-visible:ring-1 focus-visible:outline-none', className, )} {...restProps} > {#if children} {@render children()} {:else} <Plus class="size-3.5" /> {/if} </button>src/lib/components/ui/kanban/KanbanColumnBody.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanColumnBodyProps extends HTMLAttributes<HTMLDivElement> { children?: Snippet ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanColumnBodyProps = $props() </script> <div bind:this={ref} data-slot="kanban-column-body" class={cn('flex flex-1 flex-col gap-2 overflow-y-auto py-1', className)} {...restProps} > {@render children?.()} </div>src/lib/components/ui/kanban/KanbanColumnEmpty.svelte0.8 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanColumnEmptyProps extends HTMLAttributes<HTMLDivElement> { children?: Snippet ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanColumnEmptyProps = $props() </script> <div bind:this={ref} data-slot="kanban-column-empty" class={cn( 'border-border/60 text-muted-foreground/60 flex flex-1 items-center justify-center rounded-lg border border-dashed py-8 text-xs', className, )} {...restProps} > {#if children} {@render children()} {:else} No cards {/if} </div>src/lib/components/ui/kanban/KanbanCard.svelte6.1 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> { id: string disabled?: boolean /** Disable the keyboard grab (Space / arrows) while leaving pointer * dragging intact. Default: enabled. */ keyboardDraggable?: boolean children?: Snippet<[{ isDragging: boolean; isGrabbed: boolean }]> ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' import { getKanbanColumnContext, getKanbanContext } from './context' import { kanbanCardVariants } from './kanban.variants' let { class: className, id, disabled = false, keyboardDraggable = true, children, ref = $bindable(null), ...restProps }: KanbanCardProps = $props() const kanban = getKanbanContext() const column = getKanbanColumnContext() const isGrabbed = $derived(kanban?.grabbedCardId === id) const isDragging = $derived(kanban?.draggingCardId === id || isGrabbed) const canKeyboardDrag = $derived(keyboardDraggable && !disabled && !!kanban && !!column) function columnName(el: HTMLElement | null) { return el?.getAttribute('aria-label') || el?.dataset.columnId || 'column' } /** Ordered, enabled columns of the board this card sits in. */ function boardColumns(): HTMLElement[] { const board = ref?.closest('[data-slot="kanban"]') ?? document return Array.from(board.querySelectorAll<HTMLElement>('[data-slot="kanban-column"]')) } function focusSelfAfterMove() { // The consumer owns the data, so the card is re-rendered (often as a new // node) in its new column. Re-find it by id and restore focus. requestAnimationFrame(() => { const moved = document.querySelector<HTMLElement>(`[data-slot="kanban-card"][data-card-id="${id}"]`) moved?.focus() }) } function grab() { if (!canKeyboardDrag) return kanban!.setGrabbedCard(id) kanban!.setDraggingCard(id, column!.columnId) kanban!.setOverColumn(column!.columnId) kanban!.announce(`Picked up card. Use the arrow keys to move it, space to drop, escape to cancel.`) } function release(cancelled: boolean) { if (!kanban) return kanban.setGrabbedCard(null) kanban.setDraggingCard(null, null) kanban.setOverColumn(null) kanban.announce(cancelled ? 'Move cancelled.' : 'Card dropped.') } function moveToColumn(delta: -1 | 1) { const columns = boardColumns() const currentIdx = columns.findIndex((el) => el.dataset.columnId === column!.columnId) if (currentIdx === -1) return const target = columns[currentIdx + delta] // Deliberately not wrapping: running off the end of a board should stop, // not teleport the card back to the first column. if (!target?.dataset.columnId) return kanban!.emitMove({ cardId: id, fromColumnId: column!.columnId, toColumnId: target.dataset.columnId, }) kanban!.setDraggingCard(id, target.dataset.columnId) kanban!.setOverColumn(target.dataset.columnId) kanban!.announce(`Moved to ${columnName(target)}.`) focusSelfAfterMove() } function moveWithinColumn(delta: -1 | 1) { const columnEl = ref?.closest<HTMLElement>('[data-slot="kanban-column"]') if (!columnEl) return const cards = Array.from(columnEl.querySelectorAll<HTMLElement>('[data-slot="kanban-card"]')) const currentIdx = cards.findIndex((el) => el.dataset.cardId === id) if (currentIdx === -1) return const targetIdx = currentIdx + delta if (targetIdx < 0 || targetIdx > cards.length - 1) return kanban!.emitMove({ cardId: id, fromColumnId: column!.columnId, toColumnId: column!.columnId, toIndex: targetIdx, }) kanban!.announce(`Position ${targetIdx + 1} of ${cards.length}.`) focusSelfAfterMove() } function handleKeydown(e: KeyboardEvent) { if (!canKeyboardDrag) return if (e.key === ' ' || e.key === 'Spacebar') { e.preventDefault() if (isGrabbed) release(false) else grab() return } if (!isGrabbed) return if (e.key === 'Escape') { e.preventDefault() release(true) return } if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { e.preventDefault() moveToColumn(e.key === 'ArrowLeft' ? -1 : 1) return } if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { e.preventDefault() moveWithinColumn(e.key === 'ArrowUp' ? -1 : 1) } } function handleBlur() { // A grabbed card that loses focus (click elsewhere, Tab) would otherwise // stay stuck in the held state with no way back to it. if (isGrabbed) release(true) } function handleDragStart(e: DragEvent) { if (disabled) { e.preventDefault() return } if (e.dataTransfer) { e.dataTransfer.effectAllowed = 'move' e.dataTransfer.setData('text/plain', id) } kanban?.setDraggingCard(id, column?.columnId ?? null) } function handleDragEnd() { kanban?.setDraggingCard(null, null) kanban?.setOverColumn(null) } </script> <!-- `role="button"` rather than a real <button> so consumers can nest interactive content (menus, links) inside a card — the HTML spec forbids that inside <button>. Matches <BoardCard>. --> <!-- svelte-ignore a11y_no_noninteractive_tabindex: role is button whenever the card is focusable --> <div bind:this={ref} data-uipkge="" data-slot="kanban-card" data-card-id={id} data-state={isGrabbed ? 'grabbed' : isDragging ? 'dragging' : 'idle'} data-disabled={disabled || undefined} role={canKeyboardDrag ? 'button' : undefined} tabindex={canKeyboardDrag ? 0 : undefined} aria-disabled={disabled || undefined} aria-roledescription={canKeyboardDrag ? 'draggable card' : undefined} aria-pressed={canKeyboardDrag ? isGrabbed : undefined} draggable={!disabled} class={cn(kanbanCardVariants({ isDragging }), disabled && 'pointer-events-none opacity-50', className)} ondragstart={handleDragStart} ondragend={handleDragEnd} onkeydown={handleKeydown} onblur={handleBlur} {...restProps} > {@render children?.({ isDragging, isGrabbed })} </div>src/lib/components/ui/kanban/KanbanCardHeader.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanCardHeaderProps extends HTMLAttributes<HTMLDivElement> { children?: Snippet ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanCardHeaderProps = $props() </script> <div bind:this={ref} data-slot="kanban-card-header" class={cn('flex flex-col gap-1', className)} {...restProps}> {@render children?.()} </div>src/lib/components/ui/kanban/KanbanCardTitle.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanCardTitleProps extends HTMLAttributes<HTMLParagraphElement> { children?: Snippet ref?: HTMLParagraphElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanCardTitleProps = $props() </script> <p bind:this={ref} data-slot="kanban-card-title" class={cn('text-foreground text-sm leading-snug font-medium', className)} {...restProps} > {@render children?.()} </p>src/lib/components/ui/kanban/KanbanCardDescription.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanCardDescriptionProps extends HTMLAttributes<HTMLParagraphElement> { children?: Snippet ref?: HTMLParagraphElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanCardDescriptionProps = $props() </script> <p bind:this={ref} data-slot="kanban-card-description" class={cn('text-muted-foreground line-clamp-2 text-xs', className)} {...restProps} > {@render children?.()} </p>src/lib/components/ui/kanban/KanbanCardFooter.svelte0.6 kB
<script lang="ts" module> import type { Snippet } from 'svelte' import type { HTMLAttributes } from 'svelte/elements' export interface KanbanCardFooterProps extends HTMLAttributes<HTMLDivElement> { children?: Snippet ref?: HTMLDivElement | null } </script> <script lang="ts"> import { cn } from '$lib/utils' let { class: className, children, ref = $bindable(null), ...restProps }: KanbanCardFooterProps = $props() </script> <div bind:this={ref} data-slot="kanban-card-footer" class={cn('text-muted-foreground mt-1 flex items-center justify-between gap-2 pt-1 text-xs', className)} {...restProps} > {@render children?.()} </div>src/lib/components/ui/kanban/context.ts1.7 kB
import { getContext, setContext } from 'svelte' export interface KanbanMoveEvent { cardId: string fromColumnId: string toColumnId: string toIndex?: number } export interface KanbanContext { readonly draggingCardId: string | null readonly draggingColumnId: string | null readonly overColumnId: string | null /** Set while a card is held by keyboard (Space), not by pointer drag. */ readonly grabbedCardId: string | null setDraggingCard: (cardId: string | null, columnId: string | null) => void setOverColumn: (columnId: string | null) => void setGrabbedCard: (cardId: string | null) => void emitMove: (event: KanbanMoveEvent) => void /** Speak a message through the board's polite live region. */ announce: (message: string) => void } const KanbanContextKey = Symbol('KanbanContext') export function setKanbanContext(ctx: KanbanContext): void { setContext(KanbanContextKey, ctx) } export function getKanbanContext(): KanbanContext | undefined { return getContext<KanbanContext | undefined>(KanbanContextKey) } export interface KanbanColumnContext { columnId: string /** True while a dragged (pointer or keyboard-grabbed) card hovers this column. React twin: `isOver`. */ readonly isOver: boolean /** Column label, used to announce keyboard moves. Falls back to the id. */ readonly label: string | undefined } const KanbanColumnContextKey = Symbol('KanbanColumnContext') export function setKanbanColumnContext(ctx: KanbanColumnContext): void { setContext(KanbanColumnContextKey, ctx) } export function getKanbanColumnContext(): KanbanColumnContext | undefined { return getContext<KanbanColumnContext | undefined>(KanbanColumnContextKey) }src/lib/components/ui/kanban/kanban.variants.ts1.4 kB
import type { VariantProps } from 'class-variance-authority' import { cva } from 'class-variance-authority' export const kanbanColumnVariants = cva( 'flex min-h-[300px] w-72 shrink-0 flex-col gap-2 rounded-xl border bg-muted/40 p-3 transition-colors duration-200 ease-out', { variants: { isOver: { true: 'border-primary/50 bg-primary/5 ring-2 ring-primary/20', false: 'border-border/70', }, }, defaultVariants: { isOver: false, }, }, ) export const kanbanCardVariants = cva( 'group relative flex cursor-grab flex-col gap-2 rounded-lg border bg-card p-3 text-card-foreground shadow-xs transition-[border-color,box-shadow,opacity] duration-150 ease-out active:cursor-grabbing hover:border-border hover:shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', { variants: { isDragging: { // Keyboard grab reuses this state, so the held card stays fully // opaque — unlike a pointer drag there is no drag image to look at. true: 'shadow-none ring-2 ring-primary/40 opacity-100 data-[state=dragging]:opacity-40', false: 'opacity-100', }, }, defaultVariants: { isDragging: false, }, }, ) export type KanbanColumnVariantsProps = VariantProps<typeof kanbanColumnVariants> export type KanbanCardVariantsProps = VariantProps<typeof kanbanCardVariants>src/lib/components/ui/kanban/index.ts1.5 kB
export { default as Kanban, type KanbanProps } from './Kanban.svelte' export { default as KanbanBoard, type KanbanBoardProps } from './KanbanBoard.svelte' export { default as KanbanColumn, type KanbanColumnProps } from './KanbanColumn.svelte' export { default as KanbanColumnHeader, type KanbanColumnHeaderProps } from './KanbanColumnHeader.svelte' export { default as KanbanColumnDot, type KanbanColumnDotProps } from './KanbanColumnDot.svelte' export { default as KanbanColumnTitle, type KanbanColumnTitleProps } from './KanbanColumnTitle.svelte' export { default as KanbanColumnCount, type KanbanColumnCountProps } from './KanbanColumnCount.svelte' export { default as KanbanColumnAdd, type KanbanColumnAddProps } from './KanbanColumnAdd.svelte' export { default as KanbanColumnBody, type KanbanColumnBodyProps } from './KanbanColumnBody.svelte' export { default as KanbanColumnEmpty, type KanbanColumnEmptyProps } from './KanbanColumnEmpty.svelte' export { default as KanbanCard, type KanbanCardProps } from './KanbanCard.svelte' export { default as KanbanCardHeader, type KanbanCardHeaderProps } from './KanbanCardHeader.svelte' export { default as KanbanCardTitle, type KanbanCardTitleProps } from './KanbanCardTitle.svelte' export { default as KanbanCardDescription, type KanbanCardDescriptionProps } from './KanbanCardDescription.svelte' export { default as KanbanCardFooter, type KanbanCardFooterProps } from './KanbanCardFooter.svelte' export * from './kanban.variants' export * from './context'
Raw manifest:https://uipkge.dev/r/svelte/kanban.json