Skip to content

usePinnedPages ​

The reader's own shortlist of pages. Pinned from the page header, listed in the sidebar, kept across reloads.

Open in demo

Import ​

ts
import { usePinnedPages } from '@shared/composables/usePinnedPages'

Signature ​

ts
function usePinnedPages(): {
  pinned: Readonly<Ref<PinnedPage[]>>
  pin: (page: PinnedPage) => void
  unpin: (path: string) => void
  isPinned: (path: string) => boolean
  reorder: (newList: PinnedPage[]) => void
}

interface PinnedPage {
  name: string
  path: string
  title: string
  icon?: string
}

Returns ​

MemberTypeDescription
pinnedReadonly<Ref<PinnedPage[]>>The list, in the reader's own order.
pin(page) => voidAppends. Ignored if that path is already pinned.
unpin(path) => voidRemoves by path.
isPinned(path) => booleanFor a toggle's pressed state.
reorder(newList) => voidReplaces the whole list, for drag-and-drop.

Identity is the path, not the name or the route name. Deduping, unpinning and isPinned all match on it.

Example ​

vue
<script setup lang="ts">
import { usePinnedPages } from '@shared/composables/usePinnedPages'
import { computed } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
const { pin, unpin, isPinned } = usePinnedPages()

const pinnedHere = computed(() => isPinned(route.path))

function toggle() {
  if (pinnedHere.value) {
    unpin(route.path)
  }
  else {
    pin({
      name: String(route.name ?? route.path),
      path: route.path,
      title: String(route.meta.title ?? route.path),
    })
  }
}
</script>

<template>
  <button type="button" :aria-pressed="pinnedHere" @click="toggle()">
    {{ pinnedHere ? 'Unpin this page' : 'Pin this page' }}
  </button>
</template>

Notes ​

  • Persisted under sa-pinned, as a module-scope singleton, so the sidebar list and the page header's pin are the same state rather than two copies that drift.
  • Every write replaces the array rather than mutating it, which is what makes the persisted storage notice the change.
  • pin appends, so a new pin goes to the end. Call reorder to move it.
  • icon is part of the type and is stored, but nothing renders it yet.
  • pinned is readonly. Use reorder for a wholesale change.