Lane Board
Lanes of cards that can be dragged within a lane and from one lane to another.
Use it for things that move through a few stages: changes going into a release, tasks, orders. Each stage is a lane with its cards and a count, and a card is dragged to another place in its lane or into another lane.
The board does not keep the cards. You give it the lanes and it tells you what they should become when a card is set down, so the order on screen is always the order in your state.
The board has a fixed height. A lane with more cards than fit scrolls on its own, and on a narrow screen the lanes scroll sideways. For one list that only needs reordering, a sortable list is the simpler thing.
Installation
Copy the folder into components/ui, where you can edit it. It uses reka-ui's ScrollArea and the cn helper, both already in a shadcn-vue project, and the theme's info, success and warning colours.
components/ui/lane-board/
├── LaneBoard.vue
├── LaneBoardTitle.vue … LaneBoardAvatar.vue
├── board.ts
└── index.tsUsage
<script setup lang="ts">
import { ref } from "vue";
import { LaneBoard, LaneBoardCode, LaneBoardTitle, type LaneBoardLane } from "@/components/ui/lane-board";
const lanes = ref<LaneBoardLane<Change>[]>([
{ id: "building", title: "Building", cards: [{ id: "REL-224", title: "Faster search" }] },
{ id: "shipped", title: "Shipped", cards: [] },
]);
</script>
<template>
<LaneBoard v-model:lanes="lanes">
<template #card="{ card: change }">
<LaneBoardTitle>{{ change.title }}</LaneBoardTitle>
<LaneBoardCode>{{ change.id }}</LaneBoardCode>
</template>
</LaneBoard>
</template>Composition
<LaneBoard v-model:lanes="…">
<template #card="{ card, lane, carried }">
<LaneBoardRow>
<LaneBoardTitle />
<LaneBoardAvatar />
</LaneBoardRow>
<LaneBoardCode />
<LaneBoardRow>
<LaneBoardSteps />
<LaneBoardMeta />
</LaneBoardRow>
</template>
<template #lane-footer="{ lane }" />
</LaneBoard>- LaneBoardTitle: a card's name.
- LaneBoardCode: a card's reference, in a fixed-width face so a column of them lines up.
- LaneBoardRow: a line of a card with something at each end. It is the last line unless you move it up, as with a title and an avatar.
- LaneBoardSteps: how far along a card is: a row of dashes with value of max lit, in the theme colour named by color.
- LaneBoardTag: a word in a pill, for a card's state.
- LaneBoardMeta: a short figure at the end of the row: a time, a count.
- LaneBoardAvatar: who a card belongs to, as initials in a small circle.
A fuller board
A lane can have a color, drawn as a dot before its title, and a limit: the count is written against it and turns to the warning colour once the lane holds more. The #lane-footer slot draws under a lane's cards, here a button that adds one. The cards are built from the same parts, with a tag for priority and the owner's initials.
Refusing a move
update:lanes is an offer. Here a task with steps left is not let into Done: the lanes it was offered are not applied, so the card goes back to where it was.
API Reference
LaneBoard
LaneBoardSteps
LaneBoardTag
Every part also takes the attributes of the element it renders, and a class. LaneBoardColor is "primary" | "info" | "success" | "warning" | "destructive".
Notes
The data
lanes is a list of { id, title, cards }, and a card is any object with an id of its own across the whole board. Everything else on a card is yours, and the #card slot draws it. The parts above are there for the common pieces and none of them is required.
Pass default-lanes in place of lanes and the board holds the order itself.
Lanes
Besides id, title and cards, a lane takes color, one of the theme's roles, and limit. The limit is advice: the count shows it and warns when the lane is over, and the board still lets a card in. To hold a lane to its limit, refuse the move in update:lanes, as the second example refuses one.
The #lane-footer slot is given the lane and draws under its cards. A button there is pressed as usual and does not start a drag.
Hearing about a move
update:lanes is emitted with all the lanes as they should now be. move is emitted with the one card and where it went from and to, which is the shape a request to a server usually wants. Neither is emitted when a card is set down where it started.
Dragging
A press on a card becomes a drag once the pointer has moved a few pixels. On a touch screen a card is picked up by resting a finger on it for a moment, so a swipe still scrolls. A button, a link or a field inside a card works as usual and does not start a drag; put data-no-drag on anything else that should not.
The lane a carried card is over is drawn with a firmer border, and the place it would land is outlined. Near either end of the board, a carried card scrolls it. Escape puts the card back.
Keyboard
Every card is a Tab stop. Space or Enter picks one up; up and down move it within its lane, left and right into the lane beside; Space or Enter sets it down and Escape puts it back. Moving focus away also puts it back.
Screen readers
Picking up, each move and setting down are announced with the card's name, its lane and its place in it. card-name says what a card is called; without it, its id is used. A lane is named by its title.
Width
Lanes share the board's width and are never narrower than 12.5rem. When they do not fit, the board scrolls sideways and stops on a lane.
Height
The board is 28rem tall unless height says otherwise: a number of pixels or any CSS length. Every lane fills it. A lane with more cards than fit scrolls, with a thin bar that shows while the pointer is on the lane, and its cards fade out towards the edge they carry on past. Adding a card to a full lane therefore moves nothing else on the page.
A card carried near the top or bottom of a lane scrolls it, so a card can be set down anywhere in a long lane. What #lane-footer draws stays under the lane and does not scroll away.
height="auto" lets the board grow with its longest lane. It then keeps the greatest height it has had and does not shrink when cards leave that lane, so nothing moves under the pointer during a drag.
Motion
Cards slide to their new places as one is carried over them, and a card that is let go flies the last of the way into its place. A card moved to another lane with the keyboard appears there without sliding. With reduced motion on, they move at once.
Public exports
- Components: LaneBoard, LaneBoardTitle, LaneBoardCode, LaneBoardRow, LaneBoardSteps, LaneBoardTag, LaneBoardMeta, LaneBoardAvatar
- Helpers: moveCard, locateCard, keyboardMove, describeMove
- Types: LaneBoardLane, LaneBoardCard, LaneBoardColor, LaneBoardMove, LaneBoardPlace
Accessibility
Keyboard shortcuts and ARIA behavior.
- The board is a group named by label, each lane a section named by its title, and each card announces itself as "Card that can be moved".
- Each pick-up, move and set-down is spoken through a live region with the card's name, lane and place.