Renderless Vue.js components that expose VueUse composable functionality through declarative template-based interfaces
—
Components for interacting with various browser APIs like clipboard, geolocation, fullscreen, and media features.
Provides clipboard read and write functionality with reactive state.
/**
* Component that provides clipboard read/write functionality
* @example
* <UseClipboard v-slot="{ text, copy, isSupported }">
* <div>
* <p>Clipboard: {{ text }}</p>
* <button @click="copy('Hello!')">Copy Text</button>
* </div>
* </UseClipboard>
*/
interface UseClipboardProps {
/** Text source to read from @default undefined */
source?: MaybeRefOrGetter<string>;
/** Whether to read clipboard content immediately @default false */
read?: boolean;
/** Legacy mode using document.execCommand @default false */
legacy?: boolean;
/** Copy options */
copiedDuring?: number;
}
/** Slot data exposed by UseClipboard component */
interface UseClipboardReturn {
/** Whether clipboard API is supported */
isSupported: Ref<boolean>;
/** Current clipboard text content */
text: Ref<string>;
/** Whether clipboard was recently copied */
copied: Ref<boolean>;
/** Copy text to clipboard */
copy: (text?: string) => Promise<void>;
}Usage Examples:
<template>
<!-- Basic clipboard usage -->
<UseClipboard v-slot="{ text, copy, copied, isSupported }">
<div v-if="isSupported" class="clipboard-demo">
<h3>Clipboard Demo</h3>
<p>Current clipboard: {{ text || 'Empty' }}</p>
<div class="clipboard-actions">
<button @click="copy('Hello World!')" :disabled="copied">
{{ copied ? 'Copied!' : 'Copy Hello World' }}
</button>
<button @click="copy(customText)">
Copy Custom Text
</button>
</div>
<input v-model="customText" placeholder="Enter text to copy" />
</div>
<div v-else>Clipboard API not supported</div>
</UseClipboard>
<!-- Auto-read clipboard -->
<UseClipboard :read="true" v-slot="{ text, copy }">
<div class="auto-clipboard">
<p>Auto-synced clipboard: {{ text }}</p>
<button @click="copy(`Timestamp: ${Date.now()}`)">
Copy Timestamp
</button>
</div>
</UseClipboard>
<!-- Copy from source -->
<UseClipboard :source="sourceText" v-slot="{ copy, copied }">
<button @click="copy()" :class="{ copied }">
{{ copied ? 'Copied!' : 'Copy Source Text' }}
</button>
</UseClipboard>
</template>
<script setup>
import { ref } from 'vue';
import { UseClipboard } from '@vueuse/components';
const customText = ref('');
const sourceText = ref('This is source text to copy');
</script>
<style>
.clipboard-actions {
margin: 10px 0;
}
.clipboard-actions button {
margin-right: 10px;
padding: 8px 16px;
}
.copied {
background-color: #4caf50;
color: white;
}
</style>Provides geolocation data from the browser's Geolocation API.
/**
* Component that provides geolocation data
* @example
* <UseGeolocation v-slot="{ coords, error, resume, pause }">
* <div>Location: {{ coords.latitude }}, {{ coords.longitude }}</div>
* </UseGeolocation>
*/
interface UseGeolocationProps {
/** High accuracy mode @default true */
enableHighAccuracy?: boolean;
/** Maximum age of cached position (ms) @default 30000 */
maximumAge?: number;
/** Timeout for position request (ms) @default 27000 */
timeout?: number;
/** Start watching immediately @default true */
immediate?: boolean;
}
/** Slot data exposed by UseGeolocation component */
interface UseGeolocationReturn {
/** Whether geolocation is supported */
isSupported: Ref<boolean>;
/** Current position coordinates */
coords: Ref<GeolocationCoordinates>;
/** Location timestamp */
locatedAt: Ref<number | null>;
/** Geolocation error */
error: Ref<GeolocationPositionError | null>;
/** Start/resume location tracking */
resume: () => void;
/** Pause location tracking */
pause: () => void;
}
interface GeolocationCoordinates {
/** Latitude in decimal degrees */
latitude: number;
/** Longitude in decimal degrees */
longitude: number;
/** Altitude in meters above sea level */
altitude: number | null;
/** Accuracy of lat/lng in meters */
accuracy: number;
/** Accuracy of altitude in meters */
altitudeAccuracy: number | null;
/** Direction of travel in degrees */
heading: number | null;
/** Speed in meters per second */
speed: number | null;
}
interface GeolocationPositionError {
code: number;
message: string;
}Usage Examples:
<template>
<!-- Basic geolocation -->
<UseGeolocation v-slot="{ coords, error, isSupported, locatedAt }">
<div v-if="isSupported" class="geolocation-info">
<h3>Your Location</h3>
<div v-if="error" class="error">
Error: {{ error.message }}
</div>
<div v-else-if="coords.latitude">
<p>📍 {{ coords.latitude.toFixed(6) }}, {{ coords.longitude.toFixed(6) }}</p>
<p>📏 Accuracy: ±{{ Math.round(coords.accuracy) }}m</p>
<p v-if="coords.altitude">🏔️ Altitude: {{ Math.round(coords.altitude) }}m</p>
<p v-if="coords.speed">🏃 Speed: {{ (coords.speed * 3.6).toFixed(1) }} km/h</p>
<p v-if="coords.heading">🧭 Heading: {{ Math.round(coords.heading) }}°</p>
<p>⏰ Updated: {{ new Date(locatedAt).toLocaleTimeString() }}</p>
</div>
<div v-else class="loading">
📡 Getting your location...
</div>
</div>
<div v-else>Geolocation not supported</div>
</UseGeolocation>
<!-- Controlled geolocation -->
<UseGeolocation
:immediate="false"
:enable-high-accuracy="false"
v-slot="{ coords, resume, pause, error }"
>
<div class="controlled-geo">
<div class="geo-controls">
<button @click="resume">Start Tracking</button>
<button @click="pause">Stop Tracking</button>
</div>
<div v-if="coords.latitude" class="coordinates">
Low accuracy location: {{ coords.latitude.toFixed(4) }}, {{ coords.longitude.toFixed(4) }}
</div>
<div v-if="error" class="error">{{ error.message }}</div>
</div>
</UseGeolocation>
</template>
<script setup>
import { UseGeolocation } from '@vueuse/components';
</script>
<style>
.error {
color: #f44336;
padding: 10px;
background: #ffebee;
border-radius: 4px;
}
.loading {
color: #2196f3;
font-style: italic;
}
.geo-controls button {
margin-right: 10px;
padding: 8px 16px;
}
</style>Manages fullscreen mode for elements with the Fullscreen API.
/**
* Component that manages fullscreen mode
* @example
* <UseFullscreen v-slot="{ isFullscreen, enter, exit, toggle }">
* <div>
* <button @click="toggle">{{ isFullscreen ? 'Exit' : 'Enter' }} Fullscreen</button>
* </div>
* </UseFullscreen>
*/
interface UseFullscreenProps extends RenderableComponent {
/** Auto-exit fullscreen when component unmounts @default false */
autoExit?: boolean;
}
/** Slot data exposed by UseFullscreen component */
interface UseFullscreenReturn {
/** Whether fullscreen API is supported */
isSupported: Ref<boolean>;
/** Whether element is in fullscreen */
isFullscreen: Ref<boolean>;
/** Enter fullscreen mode */
enter: () => Promise<void>;
/** Exit fullscreen mode */
exit: () => Promise<void>;
/** Toggle fullscreen mode */
toggle: () => Promise<void>;
}Usage Examples:
<template>
<!-- Basic fullscreen -->
<UseFullscreen v-slot="{ isFullscreen, toggle, isSupported }">
<div v-if="isSupported" class="fullscreen-demo">
<div class="content" :class="{ 'fullscreen-content': isFullscreen }">
<h3>{{ isFullscreen ? '🎯 Fullscreen Mode!' : '📱 Normal Mode' }}</h3>
<p>This content can go fullscreen</p>
<button @click="toggle" class="fullscreen-btn">
{{ isFullscreen ? 'Exit Fullscreen' : 'Go Fullscreen' }}
</button>
</div>
</div>
<div v-else>Fullscreen API not supported</div>
</UseFullscreen>
<!-- Video fullscreen -->
<UseFullscreen v-slot="{ isFullscreen, enter, exit }">
<div class="video-container">
<video
ref="videoRef"
controls
width="400"
:style="{ width: isFullscreen ? '100vw' : '400px' }"
>
<source src="/sample-video.mp4" type="video/mp4">
</video>
<div class="video-controls">
<button @click="enter">📺 Fullscreen Video</button>
<button @click="exit" :disabled="!isFullscreen">↩️ Exit</button>
</div>
</div>
</UseFullscreen>
<!-- Auto-exit fullscreen -->
<UseFullscreen :auto-exit="true" v-slot="{ isFullscreen, toggle }">
<div class="auto-exit-demo">
<p>Auto-exits fullscreen when component unmounts</p>
<button @click="toggle">Toggle Fullscreen</button>
<p>Status: {{ isFullscreen ? 'Fullscreen' : 'Windowed' }}</p>
</div>
</UseFullscreen>
</template>
<script setup>
import { ref } from 'vue';
import { UseFullscreen } from '@vueuse/components';
const videoRef = ref();
</script>
<style>
.fullscreen-demo {
border: 2px solid #ccc;
border-radius: 8px;
overflow: hidden;
}
.content {
padding: 20px;
background: #f9f9f9;
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.fullscreen-content {
background: #1976d2;
color: white;
min-height: 100vh;
}
.fullscreen-btn {
padding: 12px 24px;
font-size: 16px;
border: none;
border-radius: 6px;
background: #2196f3;
color: white;
cursor: pointer;
}
.video-controls {
padding: 10px;
display: flex;
gap: 10px;
}
</style>Provides color picking functionality using the EyeDropper API.
/**
* Component that provides color picker functionality
* @example
* <UseEyeDropper v-slot="{ isSupported, sRGBHex, open }">
* <div>
* <button @click="open">Pick Color</button>
* <div>Color: {{ sRGBHex }}</div>
* </div>
* </UseEyeDropper>
*/
interface UseEyeDropperProps {
// No props - uses browser EyeDropper API directly
}
/** Slot data exposed by UseEyeDropper component */
interface UseEyeDropperReturn {
/** Whether EyeDropper API is supported */
isSupported: Ref<boolean>;
/** Selected color in sRGB hex format */
sRGBHex: Ref<string>;
/** Open color picker */
open: (options?: EyeDropperOpenOptions) => Promise<void>;
}
interface EyeDropperOpenOptions {
signal?: AbortSignal;
}Usage Examples:
<template>
<!-- Basic color picker -->
<UseEyeDropper v-slot="{ isSupported, sRGBHex, open }">
<div v-if="isSupported" class="color-picker">
<h3>Color Picker</h3>
<div class="color-display">
<div
class="color-swatch"
:style="{ backgroundColor: sRGBHex || '#transparent' }"
></div>
<span class="color-value">{{ sRGBHex || 'No color selected' }}</span>
</div>
<button @click="open" class="pick-button">
🎨 Pick Color from Screen
</button>
</div>
<div v-else>EyeDropper API not supported</div>
</UseEyeDropper>
<!-- Color palette builder -->
<UseEyeDropper v-slot="{ isSupported, sRGBHex, open }">
<div v-if="isSupported" class="palette-builder">
<h3>Build Color Palette</h3>
<div class="palette">
<div
v-for="(color, index) in palette"
:key="index"
class="palette-color"
:style="{ backgroundColor: color }"
:title="color"
></div>
</div>
<div class="palette-actions">
<button @click="addColor" :disabled="!sRGBHex">
Add Current Color
</button>
<button @click="open">Pick New Color</button>
<button @click="clearPalette">Clear Palette</button>
</div>
<p>Current: {{ sRGBHex || 'Pick a color' }}</p>
</div>
</UseEyeDropper>
</template>
<script setup>
import { ref, watch } from 'vue';
import { UseEyeDropper } from '@vueuse/components';
const palette = ref([]);
const addColor = () => {
// This would need access to sRGBHex from the slot
// In practice, you'd use a composable or different pattern
};
const clearPalette = () => {
palette.value = [];
};
</script>
<style>
.color-display {
display: flex;
align-items: center;
gap: 10px;
margin: 15px 0;
}
.color-swatch {
width: 50px;
height: 50px;
border: 2px solid #ccc;
border-radius: 6px;
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
}
.pick-button {
padding: 10px 20px;
background: #ff5722;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 16px;
}
.palette {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin: 15px 0;
min-height: 60px;
border: 2px dashed #ccc;
border-radius: 6px;
padding: 10px;
}
.palette-color {
width: 40px;
height: 40px;
border-radius: 4px;
border: 1px solid #ddd;
}
.palette-actions {
display: flex;
gap: 10px;
}
.palette-actions button {
padding: 8px 16px;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
}
</style>Tracks browser location and URL information.
/**
* Component that tracks browser location/URL information
* @example
* <UseBrowserLocation v-slot="{ url, origin, pathname, search }">
* <div>Current path: {{ pathname }}{{ search }}</div>
* </UseBrowserLocation>
*/
interface UseBrowserLocationProps {
// No props - uses browser location API directly
}
/** Slot data exposed by UseBrowserLocation component */
interface BrowserLocationState {
/** Reactive trigger ref for updates */
trigger: Ref<string>;
/** Complete URL */
url: ComputedRef<string>;
/** URL origin */
origin: ComputedRef<string>;
/** URL protocol */
protocol: ComputedRef<string>;
/** URL host */
host: ComputedRef<string>;
/** URL hostname */
hostname: ComputedRef<string>;
/** URL port */
port: ComputedRef<string>;
/** URL pathname */
pathname: ComputedRef<string>;
/** URL search params */
search: ComputedRef<string>;
/** URL hash */
hash: ComputedRef<string>;
}Usage Examples:
<template>
<!-- Basic location info -->
<UseBrowserLocation v-slot="{ url, pathname, search, hash, origin }">
<div class="location-info">
<h3>Browser Location</h3>
<div class="url-parts">
<p><strong>Full URL:</strong> {{ url }}</p>
<p><strong>Origin:</strong> {{ origin }}</p>
<p><strong>Path:</strong> {{ pathname }}</p>
<p><strong>Search:</strong> {{ search || '(none)' }}</p>
<p><strong>Hash:</strong> {{ hash || '(none)' }}</p>
</div>
</div>
</UseBrowserLocation>
<!-- URL analyzer -->
<UseBrowserLocation v-slot="{ protocol, host, port, pathname, search }">
<div class="url-analyzer">
<h3>URL Analysis</h3>
<table class="url-table">
<tr><td>Protocol:</td><td>{{ protocol }}</td></tr>
<tr><td>Host:</td><td>{{ host }}</td></tr>
<tr><td>Port:</td><td>{{ port || '(default)' }}</td></tr>
<tr><td>Path:</td><td>{{ pathname }}</td></tr>
<tr><td>Query:</td><td>{{ search || '(none)' }}</td></tr>
</table>
</div>
</UseBrowserLocation>
</template>
<script setup>
import { UseBrowserLocation } from '@vueuse/components';
</script>
<style>
.location-info, .url-analyzer {
border: 1px solid #ddd;
border-radius: 6px;
padding: 15px;
margin: 15px 0;
}
.url-table {
width: 100%;
border-collapse: collapse;
}
.url-table td {
padding: 8px;
border-bottom: 1px solid #eee;
}
.url-table td:first-child {
font-weight: bold;
width: 100px;
}
</style>/** Common types used across browser API components */
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);
interface RenderableComponent {
/** The element that the component should be rendered as @default 'div' */
as?: object | string;
}
/** Clipboard API types */
interface ClipboardItem {
readonly types: string[];
getType(type: string): Promise<Blob>;
}
/** Geolocation API types */
interface GeolocationPosition {
coords: GeolocationCoordinates;
timestamp: number;
}
interface GeolocationPositionError {
readonly code: number;
readonly message: string;
readonly PERMISSION_DENIED: number;
readonly POSITION_UNAVAILABLE: number;
readonly TIMEOUT: number;
}
/** Fullscreen API types */
interface FullscreenOptions {
navigationUI?: 'auto' | 'show' | 'hide';
}
/** EyeDropper API types */
interface EyeDropper {
open(options?: EyeDropperOpenOptions): Promise<ColorSelectionResult>;
}
interface ColorSelectionResult {
sRGBHex: string;
}Install with Tessl CLI
npx tessl i tessl/npm-vueuse--components