Skip to content

@vibe-labs/design-vue-lists

Vue 3 list components for the Vibe Design System. Compound component architecture with selection, drag-and-drop reorder, keyboard navigation, and full ARIA support.

Installation

ts
import { VibeList, VibeListItem, VibeListGroup, VibeListDivider, VibeListEmpty } from "@vibe-labs/design-vue-lists";

Requires the CSS layer from @vibe-labs/design-components-lists.

Components

VibeList

Root container that provides shared context to all list children via inject.

Usage

vue
<!-- Basic -->
<VibeList>
  <VibeListItem label="Notifications" description="Manage your notification preferences" />
  <VibeListItem label="Security" description="Password, 2FA, and sessions" />
  <VibeListItem label="Billing" description="Plans, invoices, and payment methods" />
</VibeList>

<!-- Interactive with selection -->
<VibeList interactive hoverable selection-mode="single" v-model:selected="selectedItem">
  <VibeListItem value="a" label="Option A" />
  <VibeListItem value="b" label="Option B" />
  <VibeListItem value="c" label="Option C" />
</VibeList>

<!-- Multi-select -->
<VibeList interactive hoverable selection-mode="multi" v-model:selected="selectedItems">
  <VibeListItem value="one" label="Item 1" />
  <VibeListItem value="two" label="Item 2" />
  <VibeListItem value="three" label="Item 3" />
</VibeList>

<!-- Reorderable -->
<VibeList reorderable @reorder="onReorder">
  <VibeListItem v-for="item in items" :key="item.id" :value="item.id" :label="item.name" />
</VibeList>

<!-- Variants -->
<VibeList variant="elevated">...</VibeList>
<VibeList variant="outlined">...</VibeList>
<VibeList variant="ghost" flush>...</VibeList>

<!-- Density -->
<VibeList size="sm">...</VibeList>
<VibeList size="lg" divided>...</VibeList>

Props

PropTypeDefaultDescription
variantListVariant"default"Visual style
sizeListSize"md"Item density
dividedbooleanfalseAuto borders between items
stripedbooleanfalseAlternating backgrounds
hoverablebooleanfalseHover highlight on items
interactivebooleanfalsePointer cursor + active press
flushbooleanfalseStrip all container chrome
reorderablebooleanfalseEnable drag-and-drop reorder
selectionModeListSelectionMode"none"Selection behaviour
selectedstring | string[]Selected value(s) (v-model:selected)
ariaLabelstringAccessible label
tagstring"div"Container HTML element

Events

EventPayloadDescription
update:selectedstring | string[]Selection changed
item-clickstring, MouseEventItem clicked (with value)
reorderListReorderEventItem dropped onto new target

VibeListItem

Individual list row with structured slots.

vue
<!-- Simple -->
<VibeListItem label="Settings" />

<!-- With description -->
<VibeListItem label="Notifications" description="Email, push, and in-app alerts" />

<!-- Full slots -->
<VibeListItem value="doc-1">
  <template #icon><FileIcon /></template>
  Report Q3.pdf
  <template #description>2.4 MB · Modified today</template>
  <template #trail>
    <VibeBadge label="New" variant="accent-subtle" />
  </template>
</VibeListItem>

<!-- Custom drag handle -->
<VibeListItem value="item-1">
  <template #handle><GripIcon /></template>
  Draggable item
</VibeListItem>

<!-- Disabled -->
<VibeListItem label="Locked" disabled />

Props

PropTypeDefaultDescription
valuestringIdentifier (required for selection / reorder)
labelstringLabel text (alternative to default slot)
descriptionstringDescription text (alternative to slot)
selectedbooleanOverride selected state
disabledbooleanfalseDisabled state

Slots

SlotDescription
defaultLabel content (inside .list-item-label)
iconLeading icon
descriptionSecondary text below label
trailTrailing content (badges, actions, metadata)
handleCustom drag handle (default: 6-dot grip SVG)

Events

EventPayloadDescription
clickMouseEventItem clicked

VibeListGroup

Section wrapper with label heading.

vue
<VibeList>
  <VibeListGroup label="General">
    <VibeListItem label="Profile" />
    <VibeListItem label="Language" />
  </VibeListGroup>
  <VibeListGroup label="Danger Zone">
    <VibeListItem label="Delete account" />
  </VibeListGroup>
</VibeList>

Props

PropTypeDefaultDescription
labelstringGroup heading (alternative to label slot)

Slots

SlotDescription
defaultGroup items
labelCustom heading

VibeListDivider

Visual separator between items.

vue
<VibeList>
  <VibeListItem label="Above" />
  <VibeListDivider />
  <VibeListItem label="Below" />
</VibeList>

VibeListEmpty

Empty state displayed when the list has no items.

vue
<VibeList>
  <VibeListEmpty v-if="items.length === 0" message="No results found" />
  <VibeListItem v-for="item in items" :key="item.id" :label="item.name" />
</VibeList>

<!-- Custom content -->
<VibeListEmpty>
  <EmptyIllustration />
  <p>Nothing here yet</p>
</VibeListEmpty>

Props

PropTypeDefaultDescription
messagestringText (alternative to default slot)

Selection Modes

ModeBehaviour
"none" (default)No selection. Items can still be interactive/clickable.
"single"One item at a time. Re-selecting the same item deselects.
"multi"Toggle items independently. Multiple can be selected.

When selection is enabled, the container uses role="listbox" and items use role="option" with aria-selected.

Drag and Drop Reorder

When reorderable is set:

  • A drag handle appears on each item that has a value prop
  • Items become draggable via HTML5 Drag and Drop
  • During drag: source item gets data-dragging, target gets data-drag-over
  • On drop: reorder event emits { from, to } with the source and target values
  • Your code handles the actual array reorder — the list just reports the intent
vue
<script setup>
function onReorder({ from, to }) {
  const fromIdx = items.value.findIndex((i) => i.id === from);
  const toIdx = items.value.findIndex((i) => i.id === to);
  const [moved] = items.value.splice(fromIdx, 1);
  items.value.splice(toIdx, 0, moved);
}
</script>

<VibeList reorderable @reorder="onReorder">
  <VibeListItem v-for="item in items" :key="item.id" :value="item.id" :label="item.name" />
</VibeList>

Keyboard Navigation

KeyAction
ArrowDownFocus next item (wraps)
ArrowUpFocus previous item (wraps)
HomeFocus first item
EndFocus last item
SpaceToggle selection (when selection enabled)
EnterActivate / toggle selection

Keyboard navigation is active when interactive is set or selectionMode is not "none".

ARIA

  • Default list: role="list" on container
  • Selectable list: role="listbox" on container, role="option" on items, aria-selected tracks state
  • Multi-select: aria-multiselectable="true" on container
  • Disabled items: aria-disabled="true"
  • Reorderable items: draggable="true" on items with value
  • Groups: role="group" on group wrapper
  • Dividers: role="separator"
  • Empty state: role="status"

Dependencies

PackagePurpose
@vibe-labs/design-components-listsCSS tokens + generated styles

Build

bash
npm run build

Built with Vite + vite-plugin-dts. Outputs ES module with TypeScript declarations.