Renderless Vue.js components that expose VueUse composable functionality through declarative template-based interfaces
—
Components and directives for tracking element properties like size, position, visibility, and bounding rectangles.
Tracks element bounding rectangle information with reactive updates.
/**
* Component that tracks element bounding rectangle
* @example
* <UseElementBounding v-slot="{ x, y, width, height }">
* <div>Position: {{ x }}, {{ y }} Size: {{ width }}x{{ height }}</div>
* </UseElementBounding>
*/
interface UseElementBoundingProps extends RenderableComponent {
/** ResizeObserver box type @default 'content-box' */
box?: ResizeObserverBoxOptions;
}
/** Slot data exposed by UseElementBounding component */
interface UseElementBoundingReturn {
/** Distance from left edge of viewport */
x: Ref<number>;
/** Distance from top edge of viewport */
y: Ref<number>;
/** Distance from top edge of viewport (alias for y) */
top: Ref<number>;
/** Distance from right edge of viewport */
right: Ref<number>;
/** Distance from bottom edge of viewport */
bottom: Ref<number>;
/** Distance from left edge of viewport (alias for x) */
left: Ref<number>;
/** Element width */
width: Ref<number>;
/** Element height */
height: Ref<number>;
/** Update the bounding information manually */
update: () => void;
}
type ResizeObserverBoxOptions = 'border-box' | 'content-box' | 'device-pixel-content-box';Usage Examples:
<template>
<!-- Basic usage -->
<UseElementBounding v-slot="{ x, y, width, height }">
<div class="box">
Position: ({{ Math.round(x) }}, {{ Math.round(y) }})
<br>
Size: {{ Math.round(width) }} × {{ Math.round(height) }}
</div>
</UseElementBounding>
<!-- Access all properties -->
<UseElementBounding v-slot="{ top, right, bottom, left, width, height, update }">
<div class="element">
<p>Bounding Rectangle:</p>
<ul>
<li>Top: {{ Math.round(top) }}px</li>
<li>Right: {{ Math.round(right) }}px</li>
<li>Bottom: {{ Math.round(bottom) }}px</li>
<li>Left: {{ Math.round(left) }}px</li>
<li>Width: {{ Math.round(width) }}px</li>
<li>Height: {{ Math.round(height) }}px</li>
</ul>
<button @click="update">Manual Update</button>
</div>
</UseElementBounding>
<!-- Custom wrapper element -->
<UseElementBounding as="section" v-slot="{ width, height }">
<div>Section size: {{ width }} × {{ height }}</div>
</UseElementBounding>
</template>
<script setup>
import { UseElementBounding } from '@vueuse/components';
</script>Directive for element bounding tracking without component wrapper.
/**
* Directive for tracking element bounding rectangle
* @example
* <div v-element-bounding="handleBounding">Track bounding</div>
* <div v-element-bounding="[handleBounding, options]">With options</div>
*/
type ElementBoundingHandler = (rect: UseElementBoundingReturn) => void;
interface VElementBoundingValue {
/** Simple handler function */
handler: ElementBoundingHandler;
/** Handler with options tuple */
handlerWithOptions: [ElementBoundingHandler, UseResizeObserverOptions];
}
interface UseResizeObserverOptions {
/** ResizeObserver box type @default 'content-box' */
box?: ResizeObserverBoxOptions;
}Usage Examples:
<template>
<!-- Simple tracking -->
<div v-element-bounding="handleBounding" class="tracked-element">
Tracked element
</div>
<!-- With options -->
<div v-element-bounding="[handleBounding, { box: 'border-box' }]">
Border-box tracking
</div>
</template>
<script setup>
import { vElementBounding } from '@vueuse/components';
function handleBounding(rect) {
console.log('Element bounds:', {
x: rect.x.value,
y: rect.y.value,
width: rect.width.value,
height: rect.height.value
});
}
</script>Tracks element dimensions with reactive updates.
/**
* Component that tracks element size
* @example
* <UseElementSize v-slot="{ width, height }">
* <div>Size: {{ width }}x{{ height }}</div>
* </UseElementSize>
*/
interface UseElementSizeProps extends RenderableComponent {
/** Initial size values */
initialSize?: { width: number; height: number };
/** ResizeObserver box type @default 'content-box' */
box?: ResizeObserverBoxOptions;
}
/** Slot data exposed by UseElementSize component */
interface UseElementSizeReturn {
/** Element width */
width: Ref<number>;
/** Element height */
height: Ref<number>;
/** Stop watching for size changes */
stop: () => void;
}Usage Examples:
<template>
<!-- Basic usage -->
<UseElementSize v-slot="{ width, height }">
<div class="resizable">
Current size: {{ Math.round(width) }} × {{ Math.round(height) }}
</div>
</UseElementSize>
<!-- With initial size -->
<UseElementSize
v-slot="{ width, height, stop }"
:initial-size="{ width: 300, height: 200 }"
>
<textarea
:style="{ width: width + 'px', height: height + 'px' }"
@focus="() => {}"
@blur="stop"
>
Resizable textarea
</textarea>
</UseElementSize>
<!-- Custom box type -->
<UseElementSize box="border-box" v-slot="{ width, height }">
<div class="border-tracked">
Border-box size: {{ width }} × {{ height }}
</div>
</UseElementSize>
</template>
<script setup>
import { UseElementSize } from '@vueuse/components';
</script>Directive for element size tracking without component wrapper.
/**
* Directive for tracking element size
* @example
* <div v-element-size="handleSize">Track size</div>
* <div v-element-size="[handleSize, options]">With options</div>
*/
type ElementSizeHandler = (size: UseElementSizeReturn) => void;
interface VElementSizeValue {
/** Simple handler function */
handler: ElementSizeHandler;
/** Handler with options tuple */
handlerWithOptions: [ElementSizeHandler, UseElementSizeOptions];
}
interface UseElementSizeOptions {
/** Initial size values */
initialSize?: { width: number; height: number };
/** ResizeObserver box type @default 'content-box' */
box?: ResizeObserverBoxOptions;
}Usage Examples:
<template>
<!-- Simple tracking -->
<div v-element-size="handleSize" class="size-tracked">
Size tracked element
</div>
<!-- With options -->
<div v-element-size="[handleSize, { box: 'border-box', initialSize: { width: 100, height: 100 } }]">
Custom size tracking
</div>
</template>
<script setup>
import { vElementSize } from '@vueuse/components';
function handleSize(size) {
console.log('Element size changed:', {
width: size.width.value,
height: size.height.value
});
}
</script>Tracks element visibility within the viewport using Intersection Observer.
/**
* Component that tracks element visibility in viewport
* @example
* <UseElementVisibility v-slot="{ isVisible }">
* <div>Visible: {{ isVisible }}</div>
* </UseElementVisibility>
*/
interface UseElementVisibilityProps extends RenderableComponent {
/** Intersection observer options */
options?: IntersectionObserverInit;
/** Scroll target element @default window */
scrollTarget?: MaybeRefOrGetter<HTMLElement | null | undefined>;
}
/** Slot data exposed by UseElementVisibility component */
interface UseElementVisibilityReturn {
/** Whether the element is visible in viewport */
isVisible: Ref<boolean>;
/** Stop the visibility observer */
stop: () => void;
}
interface IntersectionObserverInit {
/** Root element for intersection @default null */
root?: Element | null;
/** Root margin @default '0px' */
rootMargin?: string;
/** Visibility threshold @default 0 */
threshold?: number | number[];
}Usage Examples:
<template>
<!-- Basic visibility tracking -->
<UseElementVisibility v-slot="{ isVisible }">
<div class="tracked-element" :class="{ visible: isVisible }">
{{ isVisible ? 'I am visible!' : 'I am hidden' }}
</div>
</UseElementVisibility>
<!-- With custom threshold -->
<UseElementVisibility
v-slot="{ isVisible, stop }"
:options="{ threshold: 0.5 }"
>
<div class="half-visible">
{{ isVisible ? '50% visible' : 'Less than 50% visible' }}
<button @click="stop">Stop tracking</button>
</div>
</UseElementVisibility>
<!-- With root margin -->
<UseElementVisibility
v-slot="{ isVisible }"
:options="{ rootMargin: '100px' }"
>
<div class="early-trigger">
Triggers 100px before entering viewport: {{ isVisible }}
</div>
</UseElementVisibility>
</template>
<script setup>
import { UseElementVisibility } from '@vueuse/components';
</script>
<style>
.tracked-element {
padding: 20px;
background: #f0f0f0;
transition: background 0.3s;
}
.tracked-element.visible {
background: #90EE90;
}
</style>Directive for element visibility tracking without component wrapper.
/**
* Directive for tracking element visibility in viewport
* @example
* <div v-element-visibility="handleVisibility">Track visibility</div>
* <div v-element-visibility="[handleVisibility, options]">With options</div>
*/
type ElementVisibilityHandler = (isVisible: boolean, entry: IntersectionObserverEntry) => void;
interface VElementVisibilityValue {
/** Simple handler function */
handler: ElementVisibilityHandler;
/** Handler with options tuple */
handlerWithOptions: [ElementVisibilityHandler, UseElementVisibilityOptions];
}
interface UseElementVisibilityOptions {
/** Intersection observer options */
options?: IntersectionObserverInit;
/** Scroll target element @default window */
scrollTarget?: MaybeRefOrGetter<HTMLElement | null | undefined>;
}Usage Examples:
<template>
<!-- Simple visibility tracking -->
<div v-element-visibility="handleVisibility" class="visibility-tracked">
Visibility tracked element
</div>
<!-- With threshold -->
<div v-element-visibility="[handleVisibility, { options: { threshold: 0.8 } }]">
80% visibility required
</div>
<!-- Multiple thresholds -->
<div v-element-visibility="[handleMultipleVisibility, {
options: { threshold: [0, 0.25, 0.5, 0.75, 1.0] }
}]">
Progressive visibility tracking
</div>
</template>
<script setup>
import { vElementVisibility } from '@vueuse/components';
function handleVisibility(isVisible, entry) {
console.log('Element visibility changed:', isVisible);
console.log('Intersection ratio:', entry.intersectionRatio);
}
function handleMultipleVisibility(isVisible, entry) {
const ratio = Math.round(entry.intersectionRatio * 100);
console.log(`Element is ${ratio}% visible`);
}
</script>Directive for hover state detection on elements.
/**
* Directive for detecting hover state on elements
* @example
* <div v-element-hover="handleHover">Hover me</div>
* <div v-element-hover="[handleHover, options]">With options</div>
*/
type ElementHoverHandler = (isHovering: boolean) => void;
interface VElementHoverValue {
/** Simple handler function */
handler: ElementHoverHandler;
/** Handler with options tuple */
handlerWithOptions: [ElementHoverHandler, UseElementHoverOptions];
}
interface UseElementHoverOptions {
/** Delay in ms before triggering hover @default 0 */
delayEnter?: number;
/** Delay in ms before removing hover @default 0 */
delayLeave?: number;
}Usage Examples:
<template>
<!-- Simple hover detection -->
<div v-element-hover="handleHover" class="hover-element">
{{ isHovering ? 'Hovering!' : 'Not hovering' }}
</div>
<!-- With delays -->
<div v-element-hover="[handleDelayedHover, { delayEnter: 200, delayLeave: 500 }]">
Delayed hover (200ms enter, 500ms leave)
</div>
</template>
<script setup>
import { ref } from 'vue';
import { vElementHover } from '@vueuse/components';
const isHovering = ref(false);
function handleHover(hovering) {
isHovering.value = hovering;
console.log('Hover state:', hovering);
}
function handleDelayedHover(hovering) {
console.log('Delayed hover state:', hovering);
}
</script>/** Common types used across element tracking */
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);
type MaybeElementRef = HTMLElement | null | undefined;
interface RenderableComponent {
/** The element that the component should be rendered as @default 'div' */
as?: object | string;
}
/** Position information */
interface Position {
x: number;
y: number;
}
/** Size information */
interface Size {
width: number;
height: number;
}Install with Tessl CLI
npx tessl i tessl/npm-vueuse--components