Agent Skills for VueUse — a collection of essential Vue Composition Utilities. Experimental Project: Aims to help AI agents use libraries more accurately with fewer tokens. Feedback is welcome.
> /plugin marketplace add vueuse/skills> /plugin install vueuse-functions@vueuse-skills
Repo: vueuse/skills
What's inside
Agent Skills for VueUse — a collection of essential Vue Composition Utilities.
[!IMPORTANT] Experimental Project: Aims to help AI agents use libraries more accurately with fewer tokens. Feedback is welcome.
AGENTS.mdnpx skills add vueuse/skills
An alternative for Claude Code users:
# Add marketplace
/plugin marketplace add vueuse/skills
# Install individual skills
/plugin install vueuse-functions@vueuse-skills
Install VueUse in your Vue or Nuxt project, then instruct the agent. It will automatically leverage VueUse to assist development.
Example prompt:
create a todo app with the following features:
- save todos to local storage
- show remains todo count on browser title
- add a copy button for each todo items
- infinite scrolling for this todo list
- dark / light mode
<script setup lang="ts">
import { computed, ref } from 'vue'
import {
useClipboard,
useColorMode,
useInfiniteScroll,
useLocalStorage,
useTitle,
} from '@vueuse/core'
type Todo = {
id: number
text: string
done: boolean
}
const seedTexts = [
'Review project goals',
'Plan the next sprint',
'Reply to client email',
]
const defaultTodos = Array.from({ length: 36 }, (_, index) => ({
id: index + 1,
text:
seedTexts[index % seedTexts.length] +
(index >= seedTexts.length ? ` #${index + 1}` : ''),
done: index % 7 === 0,
}))
const todos = useLocalStorage<Todo[]>('focus-flow-todos', defaultTodos)
const nextId = ref(
todos.value.reduce((max, todo) => Math.max(max, todo.id), 0) + 1,
)
const newTodo = ref('')
const totalCount = computed(() => todos.value.length)
const remainingCount = computed(() =>
todos.value.filter((todo) => !todo.done).length,
)
const completedCount = computed(
() => totalCount.value - remainingCount.value,
)
useTitle(computed(() => `Todos (${remainingCount.value})`))
const mode = useColorMode({
attribute: 'data-theme',
disableTransition: false,
})
const isDark = computed(() => mode.value === 'dark')
const toggleMode = () => {
mode.value = isDark.value ? 'light' : 'dark'
}
const { copy, copied, isSupported } = useClipboard()
const lastCopiedId = ref<number | null>(null)
const handleCopy = async (todo: Todo) => {
await copy(todo.text)
lastCopiedId.value = todo.id
}
const pageSize = 8
const visibleCount = ref(Math.min(pageSize, todos.value.length))
const visibleTodos = computed(() => todos.value.slice(0, visibleCount.value))
const listRef = ref<HTMLElement | null>(null)
const { isLoading } = useInfiniteScroll(
listRef,
() => {
if (visibleCount.value < todos.value.length) {
visibleCount.value = Math.min(
visibleCount.value + pageSize,
todos.value.length,
)
}
},
{
distance: 120,
canLoadMore: () => visibleCount.value < todos.value.length,
},
)
const syncVisibleCount = () => {
if (todos.value.length <= pageSize) {
visibleCount.value = todos.value.length
return
}
if (visibleCount.value === 0) {
visibleCount.value = pageSize
return
}
if (visibleCount.value > todos.value.length) {
visibleCount.value = todos.value.length
}
}
const addTodo = () => {
const value = newTodo.value.trim()
if (!value)
return
todos.value.unshift({
id: nextId.value++,
text: value,
done: false,
})
newTodo.value = ''
syncVisibleCount()
}
const removeTodo = (id: number) => {
todos.value = todos.value.filter((todo) => todo.id !== id)
syncVisibleCount()
}
</script>
<template>
<div class="page">
<div class="shell">
<header class="header">
<div>
<p class="eyebrow">Minimal todo tracker</p>
<h1>Focus Flow</h1>
<p class="subtitle">
Keep a lightweight list, copy tasks with a click, and scroll as the
list grows.
</p>
</div>
<button class="btn ghost mode-toggle" type="button" @click="toggleMode">
<span class="mode-dot" :class="{ dark: isDark }" />
<span>{{ isDark ? 'Dark' : 'Light' }} mode</span>
</button>
</header>
<form class="composer" @submit.prevent="addTodo">
<div class="input-wrap">
<input
v-model="newTodo"
type="text"
maxlength="120"
placeholder="Add a new task"
aria-label="Add a new task"
/>
<button class="btn primary" type="submit" :disabled="!newTodo.trim()">
Add task
</button>
</div>
<div class="stats">
<div class="stat">
<span>Total</span>
<strong>{{ totalCount }}</strong>
</div>
<div class="stat">
<span>Remaining</span>
<strong>{{ remainingCount }}</strong>
</div>
<div v-if="completedCount" class="stat">
<span>Done</span>
<strong>{{ completedCount }}</strong>
</div>
</div>
</form>
<section class="list-card">
<div class="list-head">
<h2>Todo list</h2>
<span class="list-hint">
{{ visibleTodos.length }} / {{ totalCount }} shown
</span>
</div>
<div ref="listRef" class="todo-list" aria-live="polite">
<article
v-for="(todo, index) in visibleTodos"
:key="todo.id"
class="todo-item"
:class="{ done: todo.done }"
:style="{ animationDelay: `${index * 0.03}s` }"
>
<label class="todo-check">
<input v-model="todo.done" type="checkbox" />
<span class="checkmark" />
</label>
<p class="todo-text">{{ todo.text }}</p>
<div class="todo-actions">
<button
class="btn ghost"
type="button"
:disabled="!isSupported"
:title="isSupported ? 'Copy task text' : 'Clipboard not supported'"
@click="handleCopy(todo)"
>
{{ copied && lastCopiedId === todo.id ? 'Copied' : 'Copy' }}
</button>
<button class="btn danger" type="button" @click="removeTodo(todo.id)">
Remove
</button>
</div>
</article>
<p v-if="!visibleTodos.length" class="empty">
No tasks yet. Add your first todo above.
</p>
<div v-if="visibleTodos.length" class="list-footer">
<span class="footer-status">
<span v-if="isLoading">Loading more...</span>
<span v-else-if="visibleTodos.length < totalCount">
Scroll to load more
</span>
<span v-else>All caught up</span>
</span>
</div>
</div>
</section>
</div>
</div>
</template>
<style>
@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap');
:root {
color-scheme: light;
--bg: #f6f7fb;
--bg-spot: rgba(59, 130, 246, 0.18);
--bg-spot-2: rgba(34, 197, 94, 0.18);
--card: rgba(255, 255, 255, 0.92);
--surface: rgba(255, 255, 255, 0.84);
--border: rgba(148, 163, 184, 0.35);
--text: #0f172a;
--muted: #64748b;
--accent: #2563eb;
--accent-strong: #1d4ed8;
--accent-soft: rgba(37, 99, 235, 0.18);
--danger: #ef4444;
--danger-soft: rgba(239, 68, 68, 0.15);
--shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
--radius: 22px;
}
:root[data-theme='dark'] {
color-scheme: dark;
--bg: #0b1222;
--bg-spot: rgba(56, 189, 248, 0.18);
--bg-spot-2: rgba(16, 185, 129, 0.16);
--card: rgba(15, 23, 42, 0.86);
--surface: rgba(15, 23, 42, 0.7);
--border: rgba(148, 163, 184, 0.25);
--text: #f8fafc;
--muted: #94a3b8;
--accent: #38bdf8;
--accent-strong: #0ea5e9;
--accent-soft: rgba(56, 189, 248, 0.2);
--danger: #f87171;
--danger-soft: rgba(248, 113, 113, 0.2);
--shadow: 0 26px 70px rgba(2, 6, 23, 0.55);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: 'Sora', system-ui, -apple-system, sans-serif;
color: var(--text);
background:
radial-gradient(900px circle at top left, var(--bg-spot), transparent 55%),
radial-gradient(700px circle at bottom right, var(--bg-spot-2), transparent 50%),
var(--bg);
transition: background 0.4s ease, color 0.4s ease;
}
#app {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: clamp(20px, 4vw, 48px);
}
.page {
width: min(980px, 100%);
}
.shell {
display: grid;
gap: clamp(20px, 3vw, 28px);
padding: clamp(20px, 4vw, 36px);
border-radius: var(--radius);
background: var(--card);
border: 1px solid var(--border);
box-shadow: var(--shadow);
backdrop-filter: blur(18px);
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
}
.eyebrow {
text-transform: uppercase;
letter-spacing: 0.2em;
font-size: 0.72rem;
color: var(--muted);
margin: 0 0 10px;
}
h1 {
margin: 0;
font-size: clamp(2rem, 3vw, 2.6rem);
}
.subtitle {
margin: 10px 0 0;
color: var(--muted);
max-width: 520px;
}
.composer {
display: grid;
gap: 16px;
}
.input-wrap {
display: grid;
grid-template-columns: 1fr auto;
gap: 12px;
}
input[type='text'] {
padding: 12px 14px;
border-radius: 14px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
font-size: 1rem;
transition: border 0.2s ease, box-shadow 0.2s ease;
}
input[type='text']:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.stats {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.stat {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 999px;
background: var(--surface);
border: 1px solid var(--border);
font-size: 0.9rem;
color: var(--muted);
}
.stat strong {
font-family: 'Space Mono', ui-monospace, SFMono-Regular, monospace;
color: var(--text);
font-size: 0.95rem;
}
.list-card {
display: grid;
gap: 16px;
}
.list-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
}
.list-head h2 {
margin: 0;
font-size: 1.2rem;
}
.list-hint {
font-size: 0.85rem;
color: var(--muted);
font-family: 'Space Mono', ui-monospace, SFMono-Regular, monospace;
}
.todo-list {
max-height: clamp(320px, 55vh, 520px);
overflow-y: auto;
display: grid;
gap: 12px;
padding: 6px;
margin: -6px;
}
.todo-item {
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: center;
padding: 14px 16px;
border-radius: 16px;
background: var(--surface);
border: 1px solid var(--border);
animation: fadeUp 0.4s ease both;
}
.todo-item.done {
opacity: 0.7;
}
.todo-text {
margin: 0;
font-size: 0.98rem;
}
.todo-item.done .todo-text {
text-decoration: line-through;
color: var(--muted);
}
.todo-check {
display: inline-flex;
align-items: center;
}
.todo-check input {
width: 18px;
height: 18px;
accent-color: var(--accent);
}
.checkmark {
display: none;
}
.todo-actions {
display: inline-flex;
gap: 8px;
flex-wrap: wrap;
}
.btn {
border: 1px solid var(--border);
background: transparent;
color: var(--text);
padding: 8px 14px;
border-radius: 999px;
font-size: 0.88rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.btn:disabled {
cursor: not-allowed;
opacity: 0.6;
}
.btn.primary {
background: var(--accent);
border-color: var(--accent);
color: #fff;
font-weight: 600;
}
.btn.primary:hover:not(:disabled) {
background: var(--accent-strong);
border-color: var(--accent-strong);
}
.btn.ghost:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.btn.danger {
border-color: transparent;
color: var(--danger);
background: var(--danger-soft);
}
.mode-toggle {
white-space: nowrap;
}
.mode-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #facc15;
box-shadow: 0 0 0 3px rgba(250, 204, 21, 0.2);
}
.mode-dot.dark {
background: #38bdf8;
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.2);
}
.empty {
text-align: center;
padding: 32px 12px;
color: var(--muted);
border-radius: 16px;
border: 1px dashed var(--border);
}
.list-footer {
text-align: center;
font-size: 0.85rem;
color: var(--muted);
padding: 8px 0 12px;
}
@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 820px) {
.header {
flex-direction: column;
align-items: flex-start;
}
.input-wrap {
grid-template-columns: 1fr;
}
.todo-item {
grid-template-columns: auto 1fr;
}
.todo-actions {
grid-column: 1 / -1;
justify-content: flex-end;
}
}
</style>
MIT
.claude-plugin/
marketplace.json
.github/
assets/
banner.svg
workflows/
vueuse-update.yml
.gitignore
.gitmodules
.vscode/
settings.json
build.ts
eslint.config.js
LICENSE
package.json
pnpm-lock.yaml
pnpm-workspace.yaml
README.md
skills/
vueuse-functions/
references/
computedAsync.md
computedEager.md
computedInject.md
computedWithControl.md
createDisposableDirective.md
createEventHook.md
createGenericProjection.md
createGlobalState.md
createInjectionState.md
createProjection.md
createRef.md
createReusableTemplate.md
createSharedComposable.md
createTemplatePromise.md
createUnrefFn.md
extendRef.md
from.md
get.md
injectLocal.md
isDefined.md
logicAnd.md
logicNot.md
logicOr.md
makeDestructurable.md
onClickOutside.md
onElementRemoval.md
onKeyStroke.md
onLongPress.md
onStartTyping.md
provideLocal.md
reactify.md
reactifyObject.md
reactiveComputed.md
reactiveOmit.md
reactivePick.md
refAutoReset.md
refDebounced.md
refDefault.md
refManualReset.md
refThrottled.md
refWithControl.md
set.md
syncRef.md
syncRefs.md
templateRef.md
toObserver.md
toReactive.md
toRef.md
toRefs.md
tryOnBeforeMount.md
tryOnBeforeUnmount.md
tryOnMounted.md
tryOnScopeDispose.md
tryOnUnmounted.md
unrefElement.md
until.md
useAbs.md
useActiveElement.md
useAnimate.md
useArrayDifference.md
useArrayEvery.md
useArrayFilter.md
useArrayFind.md
useArrayFindIndex.md
useArrayFindLast.md
useArrayIncludes.md
useArrayJoin.md
useArrayMap.md
useArrayReduce.md
useArraySome.md
useArrayUnique.md
useAsyncQueue.md
useAsyncState.md
useAsyncValidator.md
useAuth.md
useAverage.md
useAxios.md
useBase64.md
useBattery.md
useBluetooth.md
useBreakpoints.md
useBroadcastChannel.md
useBrowserLocation.md
useCached.md
useCeil.md
useChangeCase.md
useClamp.md
useClipboard.md
useClipboardItems.md
useCloned.md
useColorMode.md
useConfirmDialog.md
useCookies.md
useCountdown.md
useCounter.md
useCssSupports.md
useCssVar.md
useCurrentElement.md
useCycleList.md
useDark.md
useDateFormat.md
useDebouncedRefHistory.md
useDebounceFn.md
useDeviceMotion.md
useDeviceOrientation.md
useDevicePixelRatio.md
useDevicesList.md
useDisplayMedia.md
useDocumentVisibility.md
useDraggable.md
useDrauu.md
useDropZone.md
useElementBounding.md
useElementByPoint.md
useElementHover.md
useElementOverflow.md
useElementSize.md
useElementVisibility.md
useEventBus.md
useEventListener.md
useEventSource.md
useExtractedObservable.md
useEyeDropper.md
useFavicon.md
useFetch.md
useFileDialog.md
useFileSystemAccess.md
useFirestore.md
useFloor.md
useFocus.md
useFocusTrap.md
useFocusWithin.md
useFps.md
useFullscreen.md
useFuse.md
useGamepad.md
useGeolocation.md
useIDBKeyval.md
useIdle.md
useImage.md
useInfiniteScroll.md
useIntersectionObserver.md
useInterval.md
useIntervalFn.md
useIpcRenderer.md
useIpcRendererInvoke.md
useIpcRendererOn.md
useJwt.md
useKeyModifier.md
useLastChanged.md
useLocalStorage.md
useMagicKeys.md
useManualRefHistory.md
useMath.md
useMax.md
useMediaControls.md
useMediaQuery.md
useMemoize.md
useMemory.md
useMin.md
useMounted.md
useMouse.md
useMouseInElement.md
useMousePressed.md
useMutationObserver.md
useNavigatorLanguage.md
useNetwork.md
useNow.md
useNProgress.md
useObjectUrl.md
useObservable.md
useOffsetPagination.md
useOnline.md
usePageLeave.md
useParallax.md
useParentElement.md
usePerformanceObserver.md
usePermission.md
usePointer.md
usePointerLock.md
usePointerSwipe.md
usePrecision.md
usePreferredColorScheme.md
usePreferredContrast.md
usePreferredDark.md
usePreferredLanguages.md
usePreferredReducedMotion.md
usePreferredReducedTransparency.md
usePrevious.md
useProjection.md
useQRCode.md
useRafFn.md
useRefHistory.md
useResizeObserver.md
useRound.md
useRouteHash.md
useRouteParams.md
useRouteQuery.md
useRTDB.md
useScreenOrientation.md
useScreenSafeArea.md
useScriptTag.md
useScroll.md
useScrollLock.md
useSessionStorage.md
useShare.md
useSortable.md
useSorted.md
useSpeechRecognition.md
useSpeechSynthesis.md
useSSRWidth.md
useStepper.md
useStorage.md
useStorageAsync.md
useStyleTag.md
useSubject.md
useSubscription.md
useSum.md
useSupported.md
useSwipe.md
useTemplateRefsList.md
useTextareaAutosize.md
useTextDirection.md
useTextSelection.md
useThrottledRefHistory.md
useThrottleFn.md
useTimeAgo.md
useTimeAgoIntl.md
useTimeout.md
useTimeoutFn.md
useTimeoutPoll.md
useTimestamp.md
useTitle.md
useToggle.md
useToNumber.md
useToString.md
useTransition.md
useTrunc.md
useUrlSearchParams.md
useUserMedia.md
useVibrate.md
useVirtualList.md
useVModel.md
useVModels.md
useWakeLock.md
useWebNotification.md
useWebSocket.md
useWebWorker.md
useWebWorkerFn.md
useWindowFocus.md
useWindowScroll.md
useWindowSize.md
useZoomFactor.md
useZoomLevel.md
watchArray.md
watchAtMost.md
watchDebounced.md
watchDeep.md
watchExtractedObservable.md
watchIgnorable.md
watchImmediate.md
watchOnce.md
watchPausable.md
watchThrottled.md
watchTriggerable.md
watchWithFilter.md
whenever.md
SKILL.md
templates/
vueuse-functions-skills.md
tsconfig.jsonFAQ
vueuse-skills is a Claude Code plugin with 1 hand-picked skill for development work, indexed on Flowy. Install it with the command on its page. It includes vueuse-functions. Its skills do not fire on their own yet. Request auto-invocation to have Flowy route them as you prompt. Free and open source.