---
title: Utilities Guide - Helper Functions for Vue 3
features:
  order: 4
  level: 2
meta:
  - name: description
    content: Discover Vuetify0's utility functions for common development tasks. Type guards, transformers, and helpers that enhance code reusability in Vue 3 applications.
  - name: keywords
    content: vuetify0, utilities, helpers, type guards, transformers, toArray, toReactive, Vue 3
related:
  - /composables/data/create-filter
  - /composables/data/create-pagination
  - /composables/data/create-virtual
  - /composables/semantic/create-overflow
---

# Utilities

Standalone helpers for common UI patterns. These composables don't depend on context or plugins—use them anywhere.

<DocsPageFeatures :frontmatter />

## Overview

| Utility | Purpose |
| - | - |
| [createFilter](/composables/data/create-filter) | Filter arrays with search queries |
| [createPagination](/composables/data/create-pagination) | Page navigation state |
| [createVirtual](/composables/data/create-virtual) | Virtual scrolling for large lists |
| [createOverflow](/composables/semantic/create-overflow) | Compute visible item capacity |

> [!TIP]
> These utilities are standalone—they don't require plugins or context. Use them anywhere, including outside Vue components.

## createFilter

Filter arrays based on search queries:

```ts
import { createFilter } from '@vuetify/v0'

const items = ref(['Apple', 'Banana', 'Cherry'])
const query = ref('')

const filter = createFilter()
const { items: filtered } = filter.apply(query, items)

query.value = 'an'
filtered.value  // ['Banana']
```

### With Object Keys

```ts
const users = ref([
  { name: 'Alice', email: 'alice@example.com' },
  { name: 'Bob', email: 'bob@example.com' },
])

const filter = createFilter({
  keys: ['name', 'email'],
})

const query = ref('alice')
const { items: filtered } = filter.apply(query, users)
filtered.value  // [{ name: 'Alice', ... }]
```

### Filter Modes

```ts
const filter = createFilter({
  mode: 'intersection',  // 'some' | 'every' | 'union' | 'intersection'
  keys: ['name', 'tags'],
})
```

## createPagination

Pagination state management:

```ts
import { createPagination } from '@vuetify/v0'

const pagination = createPagination({
  size: 100,
  itemsPerPage: 10,
})

pagination.page.value     // 1
pagination.pages          // 10
pagination.isFirst.value  // true
pagination.isLast.value   // false

pagination.next()         // Go to page 2
pagination.prev()         // Go to page 1
pagination.select(5)      // Go to page 5
pagination.first()        // Go to page 1
pagination.last()         // Go to page 10
```

### With Reactive Size

```ts
const items = ref([...])

const pagination = createPagination({
  size: () => items.value.length,
  itemsPerPage: 20,
})
```

### Page Items

```ts
pagination.items.value  // [{ type: 'page', value: 1 }, { type: 'page', value: 2 }, ...]
```

## createVirtual

Virtual scrolling for large datasets:

```ts
import { createVirtual } from '@vuetify/v0'

const items = ref(Array.from({ length: 10000 }, (_, i) => `Item ${i}`))

// createVirtual takes items as first arg, options as second
const virtual = createVirtual(items, {
  itemHeight: 40,
})
```

```vue playground VirtualList.vue
<script setup lang="ts">
  const { element, items: virtualItems, offset, size } = virtual
</script>

<template>
  <div ref="element" style="height: 400px; overflow: auto;">
    <div :style="{ height: `${size}px`, paddingTop: `${offset}px` }">
      <div
        v-for="item in virtualItems"
        :key="item.index"
        style="height: 40px"
      >
        {{ item.raw }}
      </div>
    </div>
  </div>
</template>
```

### Variable Height

```ts
const virtual = createVirtual(items, {
  height: 400,  // Container height (or use element ref)
})
```

## createOverflow

Compute how many items fit in a container:

```ts
import { createOverflow } from '@vuetify/v0'
import { useTemplateRef } from 'vue'

const containerRef = useTemplateRef<HTMLElement>('container')

const overflow = createOverflow({
  container: containerRef,
  itemWidth: 100,
  gap: 8,
})

overflow.capacity.value       // Number of items that fit
overflow.isOverflowing.value  // Boolean: items exceed capacity
```

### Use Case: Responsive Chips

```vue playground ResponsiveChips.vue
<template>
  <div ref="container" class="flex gap-2">
    <span v-for="tag in visibleTags" :key="tag" class="chip">
      {{ tag }}
    </span>
    <span v-if="overflow.isOverflowing.value" class="chip">
      +{{ tags.length - overflow.capacity.value }}
    </span>
  </div>
</template>

<script setup>
  const tags = ['Vue', 'React', 'Angular', 'Svelte', 'Solid']
  const visibleTags = computed(() => tags.slice(0, overflow.capacity.value))
</script>
```

## Transformers

Value transformation utilities:

### toArray

Normalize any value to an array:

```ts
import { toArray } from '@vuetify/v0'

toArray('single')      // ['single']
toArray(['array'])     // ['array']
toArray(null)          // []
toArray(undefined)     // []
```

### toReactive

Convert ref objects to reactive proxies:

```ts
import { toReactive } from '@vuetify/v0'

const configRef = ref({ debug: false })

// Unwraps the ref and returns a reactive object
const config = toReactive(configRef)

config.debug  // Reactive access
```

## Color

Color manipulation utilities for hex and RGB formats.

### hexToRgb / rgbToHex

Convert between hex strings and `RGB` objects:

```ts
import { hexToRgb, rgbToHex } from '@vuetify/v0'

hexToRgb('#1976d2')  // { r: 25, g: 118, b: 210 }
hexToRgb('#fff')     // { r: 255, g: 255, b: 255 }

rgbToHex({ r: 25, g: 118, b: 210 })  // '#1976d2'
```

The `RGB` interface is available for typing:

```ts
import type { RGB } from '@vuetify/v0'

const color: RGB = { r: 255, g: 0, b: 0 }
```

### APCA Contrast

[APCA](https://github.com/Myndex/SAPC-APCA) (Advanced Perceptual Contrast Algorithm) for accessible color pairings. Returns a signed contrast value — higher magnitude means more contrast:

```ts
import { apca, foreground, hexToRgb } from '@vuetify/v0'

// Raw APCA contrast between two colors
const text = hexToRgb('#1a1a1a')
const bg   = hexToRgb('#ffffff')
apca(text, bg)  // ~-106 (high contrast, dark on light)

// Pick black or white text for a background
foreground('#1976d2')  // '#ffffff' (white reads better on this blue)
foreground('#ffd600')  // '#000000' (black reads better on yellow)
```

> [!TIP]
> `foreground` is ideal for computing on-color text in design tokens. It uses APCA to select the higher-contrast option.

## Errors

Structured error class and type guard for errors thrown by v0 internals.

### V0Error / isV0Error

```ts
import { isV0Error, V0Error } from '@vuetify/v0'

try {
  useContext('v0:missing')
} catch (err) {
  if (isV0Error(err, 'V0_CONTEXT_MISSING')) {
    console.log(err.code, err.key) // 'V0_CONTEXT_MISSING', 'v0:missing'
  }
}
```

`V0Error` carries a stable `code` discriminant (see [V0ErrorCode](/guide/features/types#v0errorcode)) instead of a message-only `Error`, so callers can branch on failure kind without parsing strings. `isV0Error(err, code?)` narrows `unknown` to `V0Error`, and — when `code` is passed — further narrows to the matching error-detail shape, so per-code fields like `key` or `plugin` become required instead of optional.

## Best Practices

### Combine Utilities

```ts
// Filter + Paginate
const query = ref('')
const filter = createFilter()
const { items: filtered } = filter.apply(query, items)

const pagination = createPagination({
  size: () => filtered.value.length,
  itemsPerPage: 10,
})

const displayedItems = computed(() => {
  const start = pagination.pageStart.value
  const end = pagination.pageStop.value
  return filtered.value.slice(start, end)
})
```

### Virtual + Filter

```ts
const query = ref('')
const filter = createFilter()
const { items: filtered } = filter.apply(query, items)

const virtual = createVirtual(filtered, {
  itemHeight: 40,
})
```

### Filter + Pagination + Virtual

For large datasets that need all three utilities working together:

```ts collapse
import { computed, shallowRef } from 'vue'
import { createFilter, createPagination, createVirtual, Pagination } from '@vuetify/v0'

// Source data
const items = shallowRef(Array.from({ length: 10000 }, (_, i) => ({
  id: i,
  name: `Item ${i}`,
})))

// 1. Filter first
const query = shallowRef('')
const filter = createFilter({ keys: ['name'] })
const { items: filtered } = filter.apply(query, items)

// 2. Paginate the filtered results — `page` is shared with Pagination.Root below
const page = shallowRef(1)
const pagination = createPagination({
  page,
  size: () => filtered.value.length,
  itemsPerPage: 100,
})

// 3. Get current page slice
const pageItems = computed(() => {
  const start = pagination.pageStart.value
  const end = pagination.pageStop.value
  return filtered.value.slice(start, end)
})

// 4. Virtual scroll the current page
const virtual = createVirtual(pageItems, { itemHeight: 40 })
const { element, items: virtualItems, offset, size } = virtual
```

```vue
<template>
  <input v-model="query" placeholder="Search..." />

  <div ref="element" style="height: 400px; overflow: auto;">
    <div :style="{ height: `${size}px`, paddingTop: `${offset}px` }">
      <div v-for="item in virtualItems" :key="item.raw.id" style="height: 40px">
        {{ item.raw.name }}
      </div>
    </div>
  </div>

  <Pagination.Root v-model="page" :size="filtered.length" :items-per-page="100" v-slot="{ items: pageNumbers }" class="flex items-center gap-1">
    <Pagination.Prev>‹</Pagination.Prev>

    <template v-for="entry in pageNumbers" :key="entry.value">
      <Pagination.Ellipsis v-if="entry.type === 'ellipsis'" />
      <Pagination.Item v-else :value="entry.value as number">
        {{ entry.value }}
      </Pagination.Item>
    </template>

    <Pagination.Next>›</Pagination.Next>
  </Pagination.Root>
</template>
```

```mermaid "Data Pipeline"
flowchart LR
    A[All Items] --> B[Filtered]
    B --> C[Paginated]
    C --> D[Virtualized]
```

Each layer is reactive—changing the search query refilters, which updates pagination, which updates the virtual list.

## Type Guards

Type-narrowing functions that replace raw `typeof` / `===` checks. All are tree-shakeable.

```ts
import { isString, isNullOrUndefined, isObject } from '@vuetify/v0'

if (isString(value)) {
  // value is string
}

if (!isNullOrUndefined(x)) {
  // x is defined
}
```

| Guard | Narrows to |
| - | - |
| `isFunction(x)` | `Function` |
| `isString(x)` | `string` |
| `isNumber(x)` | `number` (includes NaN) |
| `isBoolean(x)` | `boolean` |
| `isObject(x)` | `Record<string, any>` (excludes null and arrays)[^isobject-any] |
| `isArray(x)` | the array member of `x`[^isarray-preserves] |
| `isElement(x)` | `Element` |
| `isNull(x)` | `null` |
| `isUndefined(x)` | `undefined` |
| `isNullOrUndefined(x)` | `null \| undefined` |
| `isPrimitive(x)` | `string \| number \| boolean` |
| `isSymbol(x)` | `symbol` |
| `isNaN(x)` | `number` (only the NaN value)[^isnan-vs-global] |
| `isThenable(x)` | `{ then: Function }`[^isthenable-duck-typed] |

[^isobject-any]: The value type is `any` rather than `unknown` so that narrowing preserves what you already knew. TypeScript never grants an interface an implicit index signature, so a predicate of `Record<string, unknown>` would collapse an interface-typed value's known property types and would fail to remove object members in the `else` branch. `any` here widens only the *index* type — the union member itself survives narrowing intact.
[^isarray-preserves]: Narrowing preserves the element type, so a `string[] | string` narrows to `string[]`, not `unknown[]`. Tuples keep their arity and `readonly` arrays stay `readonly`. When nothing more specific is known (e.g. `x: unknown`), it falls back to `unknown[]`.
[^isnan-vs-global]: `isNaN` here uses `Number.isNaN()` internally — it does not coerce strings like the global `isNaN()`. `isNaN("foo")` returns `false`, not `true`.
[^isthenable-duck-typed]: Duck-typed — matches native `Promise` instances and any other object exposing a callable `.then`, not just `instanceof Promise`. Use it to guard async-vs-sync return values; use `instanceof Promise` directly when only native promises should pass.

> [!TIP]
> Prefer these over raw comparisons.

## Helpers

General-purpose utilities for numbers, arrays, and objects.

### clamp

Clamp a number between a minimum and maximum:

```ts
import { clamp } from '@vuetify/v0'

clamp(15, 0, 10)  // 10
clamp(-5, 0, 10)  // 0
clamp(5, 0, 10)   // 5
clamp(0.5)         // 0.5 (defaults: min=0, max=1)
```

### range

Create an array of sequential numbers:

```ts
import { range } from '@vuetify/v0'

range(5)      // [0, 1, 2, 3, 4]
range(3, 1)   // [1, 2, 3]
range(5, 10)  // [10, 11, 12, 13, 14]
range(0)      // []
```

### findMatchRanges

Locate a query inside a string and get back `[start, end]` pairs, optionally folding accents before matching. This is what backs the `ignoreAccents` option of [toHighlight](/composables/transformers/to-highlight):

```ts
import { findMatchRanges } from '@vuetify/v0'

findMatchRanges('Zürich', 'zurich', { ignoreCase: true, ignoreAccents: true })  // [[0, 6]]
findMatchRanges('Zürich', 'zurich', { ignoreCase: true })                       // []
```

`ignoreAccents` is directional. `'target'` folds only the text, so a plain query reaches accented entries; `'query'` folds only the search term, so a pasted `café` reaches plain `cafe`; `true` folds both sides.

```ts
findMatchRanges('Łódź', 'Lo', { ignoreAccents: 'target' })   // [[0, 2]]
findMatchRanges('cafe', 'café', { ignoreAccents: 'query' })  // [[0, 4]]
```

Folding strips Combining Diacritical Marks (U+0300–036F) after NFD, then common letters NFD leaves alone (`ł`, `ø`, `đ`, `ß`, `æ`, `œ`, …). When the text is folded or case-converted, ranges are mapped back onto the source — `ß` → `ss` still spans one source character, and `İ` still spans one. Pass `matchAll: true` for every occurrence instead of the first.

### getActiveElement

Resolve the deepest focused node. Open shadow roots are walked so focus/keyboard logic sees the real control, not the host; light DOM matches `document.activeElement`. Closed roots are not traversable. Returns `null` under SSR.

```ts
import { getActiveElement } from '@vuetify/v0'

const active = getActiveElement()
```

### pxToNumber

Parse a CSS pixel length into a number. Built for reading `getComputedStyle` output, where a length that does not apply resolves to `''` or `'auto'` rather than to a number:

```ts
import { pxToNumber } from '@vuetify/v0'

const style = getComputedStyle(el)

pxToNumber(style.marginLeft)   // 16
pxToNumber(style.marginRight)  // 0   (resolved '0px')
pxToNumber(style.width)        // 0   ('auto' does not parse)
pxToNumber(undefined)          // 0
```

The second argument is the value returned when the length does not parse — it defaults to `0`:

```ts
// A parsed 0 and an unparseable length are different answers
pxToNumber('0px', rect.width)  // 0
pxToNumber('auto', rect.width) // rect.width
```

That distinction is the reason to reach for this over `Number.parseFloat(value) || 0`, which collapses both cases onto `0` and so is only correct when the fallback is itself `0`.

Only the leading number is read, matching `Number.parseFloat`, so any unit suffix is ignored. Values `getComputedStyle` never returns — percentages, `calc()` expressions, multi-value shorthands — are out of scope.

### mergeDeep

Deep-merge objects without mutating inputs. Arrays are replaced, not concatenated:

```ts
import { mergeDeep } from '@vuetify/v0'

mergeDeep({ a: { b: 1 } }, { a: { c: 2 } })
// { a: { b: 1, c: 2 } }

mergeDeep({}, { a: 1 }, { b: 2 })
// { a: 1, b: 2 }

mergeDeep({ arr: [1, 2] }, { arr: [3] })
// { arr: [3] }
```

### useId

SSR-safe unique ID generation. Uses Vue's `useId()` inside components, falls back to a counter outside:

```ts
import { useId } from '@vuetify/v0'

// In component setup
const id = useId()  // 'v:0' (Vue's format)

// Outside component
const id = useId()  // 'v0-0', 'v0-1', ...
```
