Blocks
Board
Cards in lists, moved by dragging: across lists, up and down, with lists that reorder too. For tasks, pipelines and anything that moves through stages.
Examples
A translation workshop
Drag a card to another list, or a list by its grip. On a phone, press and hold a card first. Add cards at the bottom of a list; open one to rename it, label it or delete it. The board is kept in this browser.
To translate
In progress
Checking
Copied
Press Space to pick up, arrow keys to move, Space again to drop, Escape to cancel.
Press Space to pick up. Arrows move it, Space drops it, Escape puts it back. Enter opens the card.
<script lang="ts">
import { onMount } from 'svelte';
import Board, { type BoardColumn, type BoardLabel } from '#lib/blocks/board/Board.svelte';
const labels: BoardLabel[] = [
{ id: 'greek', name: 'Greek', color: 'var(--ui-accent)' },
{ id: 'persian', name: 'Persian', color: 'var(--ui-warning)' },
{ id: 'urgent', name: 'Urgent', color: 'var(--ui-danger)' },
{ id: 'review', name: 'Review', color: 'light-dark(#0369a1, #38bdf8)' }
];
const sample = (): BoardColumn[] => [
{
id: 'todo',
title: 'To translate',
cards: [
{ id: 'euclid', title: "Euclid's Elements, books 7–9", labels: ['greek'] },
{ id: 'ptolemy', title: 'Ptolemy’s Almagest', labels: ['greek', 'urgent'] },
{ id: 'kalila', title: 'Kalila and Dimna', labels: ['persian'] }
]
},
{
id: 'doing',
title: 'In progress',
cards: [
{ id: 'galen', title: 'Galen on the pulse', labels: ['greek'] },
{ id: 'sindhind', title: 'Star tables (Zij al-Sindhind)', labels: ['review'] }
]
},
{
id: 'check',
title: 'Checking',
cards: [{ id: 'aristotle', title: 'Aristotle, On the Soul', labels: ['greek', 'review'] }]
},
{ id: 'done', title: 'Copied', cards: [{ id: 'hippocrates', title: 'Hippocratic aphorisms' }] }
];
// Kept in this browser, so the board is as you left it after a reload.
const KEY = 'sinaui-board';
let columns = $state(sample());
let loaded = false;
onMount(() => {
try {
const saved = JSON.parse(localStorage.getItem(KEY) ?? 'null');
if (Array.isArray(saved)) columns = saved;
} catch {
// Nothing saved, or storage blocked: start from the demo board.
}
loaded = true;
});
$effect(() => {
const json = JSON.stringify(columns);
if (!loaded) return;
try {
localStorage.setItem(KEY, json);
} catch {
// Storage blocked: changes last until reload.
}
});
</script>
<Board bind:columns {labels} label="Translation board" />Installation
npx sinaui add blocks/boardCopy src/lib/blocks/board/, with Sortable, Button, Dialog, Icon, Input, scroll-edges.ts, announce.ts and tokens.css. No dependencies.
Props
| Prop | Type | Default |
|---|---|---|
columns | { id, title, cards: { id, title, labels? }[] }[] (bindable) | required |
labels | { id, name, color }[] | [] |
label | string | 'Board' |
onmove | (card, { column, index }) => Promise<void> | void: save a drop |
A dropped card moves at once. If onmove rejects, it goes back to where it was picked
up and a short message says so (mount <Toaster /> once in your layout to show it).
Keyboard
| Key | Does |
|---|---|
| Space | Pick up a card, then drop it |
| ↑ ↓ | Move it within its list |
| ← → | Move it to the next list |
| Escape | Put it back |
| Enter | Open the card |
Accessibility
- Every pick-up, move and drop is announced with the list and position.
- Lists are named with their card count; each card is a button.
- Dragging near the board's sides or a list's ends scrolls it.
- With reduced motion, cards move without sliding or tilting.