Renderless Vue.js components that expose VueUse composable functionality through declarative template-based interfaces
—
Components for accessing device information, sensors, and hardware capabilities.
Provides battery status information from the Battery Status API.
/**
* Component that provides battery status information
* @example
* <UseBattery v-slot="{ charging, level, dischargingTime }">
* <div>Battery: {{ Math.round(level * 100) }}% {{ charging ? 'charging' : 'discharging' }}</div>
* </UseBattery>
*/
interface UseBatteryProps {
// No props - uses browser Battery API directly
}
/** Slot data exposed by UseBattery component */
interface BatteryState {
/** Whether the battery is charging */
charging: Ref<boolean>;
/** Time remaining until charged (seconds) */
chargingTime: Ref<number>;
/** Time remaining until discharged (seconds) */
dischargingTime: Ref<number>;
/** Battery charge level (0-1) */
level: Ref<number>;
/** Whether the Battery API is supported */
isSupported: Ref<boolean>;
}Usage Examples:
<template>
<!-- Basic battery info -->
<UseBattery v-slot="{ charging, level, isSupported }">
<div v-if="isSupported" class="battery-info">
<div class="battery-level" :style="{ width: level * 100 + '%' }"></div>
<span>{{ Math.round(level * 100) }}% {{ charging ? '🔌' : '🔋' }}</span>
</div>
<div v-else>Battery API not supported</div>
</UseBattery>
<!-- Detailed battery info -->
<UseBattery v-slot="{ charging, level, chargingTime, dischargingTime }">
<div class="detailed-battery">
<h3>Battery Status</h3>
<p>Level: {{ Math.round(level * 100) }}%</p>
<p>Status: {{ charging ? 'Charging' : 'Discharging' }}</p>
<p v-if="charging && chargingTime !== Infinity">
Time to full: {{ Math.round(chargingTime / 60) }} minutes
</p>
<p v-if="!charging && dischargingTime !== Infinity">
Time remaining: {{ Math.round(dischargingTime / 60) }} minutes
</p>
</div>
</UseBattery>
</template>
<script setup>
import { UseBattery } from '@vueuse/components';
</script>
<style>
.battery-info {
position: relative;
width: 200px;
height: 30px;
border: 2px solid #333;
border-radius: 4px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.battery-level {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: linear-gradient(90deg, #4caf50, #8bc34a);
transition: width 0.3s ease;
}
</style>Provides device motion sensor data including acceleration and rotation.
/**
* Component that provides device motion sensor data
* @example
* <UseDeviceMotion v-slot="{ acceleration, rotationRate }">
* <div>Acceleration: {{ acceleration.x.toFixed(2) }}, {{ acceleration.y.toFixed(2) }}</div>
* </UseDeviceMotion>
*/
interface UseDeviceMotionProps {
// No props - uses browser DeviceMotion API directly
}
/** Slot data exposed by UseDeviceMotion component */
interface DeviceMotionState {
/** Device acceleration data */
acceleration: Ref<DeviceMotionAcceleration | null>;
/** Device acceleration including gravity */
accelerationIncludingGravity: Ref<DeviceMotionAcceleration | null>;
/** Device rotation rate data */
rotationRate: Ref<DeviceMotionRotationRate | null>;
/** Motion interval in milliseconds */
interval: Ref<number>;
/** Whether the DeviceMotion API is supported */
isSupported: Ref<boolean>;
}
interface DeviceMotionAcceleration {
/** Acceleration along x-axis (m/s²) */
x: number | null;
/** Acceleration along y-axis (m/s²) */
y: number | null;
/** Acceleration along z-axis (m/s²) */
z: number | null;
}
interface DeviceMotionRotationRate {
/** Rotation around x-axis (degrees/second) */
alpha: number | null;
/** Rotation around y-axis (degrees/second) */
beta: number | null;
/** Rotation around z-axis (degrees/second) */
gamma: number | null;
}Usage Examples:
<template>
<!-- Basic motion tracking -->
<UseDeviceMotion v-slot="{ acceleration, rotationRate, isSupported }">
<div v-if="isSupported" class="motion-display">
<h3>Device Motion</h3>
<div v-if="acceleration">
<h4>Acceleration (m/s²):</h4>
<p>X: {{ acceleration.x?.toFixed(2) ?? 'N/A' }}</p>
<p>Y: {{ acceleration.y?.toFixed(2) ?? 'N/A' }}</p>
<p>Z: {{ acceleration.z?.toFixed(2) ?? 'N/A' }}</p>
</div>
<div v-if="rotationRate">
<h4>Rotation Rate (°/s):</h4>
<p>Alpha: {{ rotationRate.alpha?.toFixed(2) ?? 'N/A' }}</p>
<p>Beta: {{ rotationRate.beta?.toFixed(2) ?? 'N/A' }}</p>
<p>Gamma: {{ rotationRate.gamma?.toFixed(2) ?? 'N/A' }}</p>
</div>
</div>
<div v-else>Device motion not supported</div>
</UseDeviceMotion>
<!-- Motion with gravity -->
<UseDeviceMotion v-slot="{ accelerationIncludingGravity, interval }">
<div class="gravity-motion">
<h3>Motion with Gravity</h3>
<p>Update interval: {{ interval }}ms</p>
<div v-if="accelerationIncludingGravity">
<p>X: {{ accelerationIncludingGravity.x?.toFixed(3) }}</p>
<p>Y: {{ accelerationIncludingGravity.y?.toFixed(3) }}</p>
<p>Z: {{ accelerationIncludingGravity.z?.toFixed(3) }}</p>
</div>
</div>
</UseDeviceMotion>
</template>
<script setup>
import { UseDeviceMotion } from '@vueuse/components';
</script>Provides device orientation data including compass heading and device tilt.
/**
* Component that provides device orientation data
* @example
* <UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute }">
* <div>Heading: {{ alpha }}° Tilt: {{ beta }}°, {{ gamma }}°</div>
* </UseDeviceOrientation>
*/
interface UseDeviceOrientationProps {
// No props - uses browser DeviceOrientation API directly
}
/** Slot data exposed by UseDeviceOrientation component */
interface DeviceOrientationState {
/** Compass heading (0-360°) */
alpha: Ref<number | null>;
/** Front-to-back tilt (-180 to 180°) */
beta: Ref<number | null>;
/** Left-to-right tilt (-90 to 90°) */
gamma: Ref<number | null>;
/** Whether orientation is absolute (true north) */
absolute: Ref<boolean>;
/** Whether the DeviceOrientation API is supported */
isSupported: Ref<boolean>;
}Usage Examples:
<template>
<!-- Basic orientation -->
<UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute, isSupported }">
<div v-if="isSupported" class="orientation-display">
<h3>Device Orientation</h3>
<div class="compass" :style="{ transform: `rotate(${alpha || 0}deg)` }">
<div class="compass-needle"></div>
</div>
<p>Compass: {{ alpha?.toFixed(1) ?? 'N/A' }}° {{ absolute ? '(True North)' : '(Magnetic)' }}</p>
<p>Pitch: {{ beta?.toFixed(1) ?? 'N/A' }}°</p>
<p>Roll: {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>
</div>
<div v-else>Device orientation not supported</div>
</UseDeviceOrientation>
<!-- Tilt indicator -->
<UseDeviceOrientation v-slot="{ beta, gamma }">
<div class="tilt-indicator">
<div
class="tilt-ball"
:style="{
transform: `translateX(${(gamma || 0) * 2}px) translateY(${(beta || 0) * 2}px)`
}"
></div>
<p>Tilt: {{ beta?.toFixed(1) ?? 'N/A' }}°, {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>
</div>
</UseDeviceOrientation>
</template>
<script setup>
import { UseDeviceOrientation } from '@vueuse/components';
</script>
<style>
.compass {
width: 100px;
height: 100px;
border: 2px solid #333;
border-radius: 50%;
position: relative;
margin: 20px auto;
}
.compass-needle {
position: absolute;
top: 10%;
left: 50%;
width: 2px;
height: 40%;
background: red;
transform: translateX(-50%);
transform-origin: bottom center;
}
.tilt-indicator {
width: 200px;
height: 200px;
border: 2px solid #333;
border-radius: 10px;
position: relative;
margin: 20px auto;
overflow: hidden;
}
.tilt-ball {
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
background: #2196f3;
border-radius: 50%;
transform: translate(-50%, -50%);
transition: transform 0.1s ease;
}
</style>Tracks device pixel ratio changes.
/**
* Component that tracks device pixel ratio
* @example
* <UseDevicePixelRatio v-slot="{ pixelRatio }">
* <div>Pixel Ratio: {{ pixelRatio }}</div>
* </UseDevicePixelRatio>
*/
interface UseDevicePixelRatioProps {
// No props - uses browser devicePixelRatio API
}
/** Slot data exposed by UseDevicePixelRatio component */
interface UseDevicePixelRatioReturn {
/** Current device pixel ratio */
pixelRatio: Ref<number>;
}Usage Examples:
<template>
<!-- Basic pixel ratio -->
<UseDevicePixelRatio v-slot="{ pixelRatio }">
<div class="pixel-ratio-info">
<h3>Display Information</h3>
<p>Device Pixel Ratio: {{ pixelRatio }}</p>
<p>Display Type: {{ getDisplayType(pixelRatio) }}</p>
</div>
</UseDevicePixelRatio>
<!-- Responsive images based on pixel ratio -->
<UseDevicePixelRatio v-slot="{ pixelRatio }">
<img
:src="getImageSrc(pixelRatio)"
:alt="`Image for ${pixelRatio}x display`"
class="responsive-image"
/>
</UseDevicePixelRatio>
</template>
<script setup>
import { UseDevicePixelRatio } from '@vueuse/components';
function getDisplayType(ratio) {
if (ratio >= 3) return 'Ultra High DPI';
if (ratio >= 2) return 'High DPI (Retina)';
if (ratio >= 1.5) return 'Medium DPI';
return 'Standard DPI';
}
function getImageSrc(ratio) {
if (ratio >= 2) return '/images/logo@2x.png';
return '/images/logo.png';
}
</script>Lists available media devices (cameras, microphones, speakers).
/**
* Component that lists available media devices
* @example
* <UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs }">
* <div>Cameras: {{ videoInputs.length }}, Mics: {{ audioInputs.length }}</div>
* </UseDevicesList>
*/
interface UseDevicesListProps {
/** Request permissions for device access @default false */
requestPermissions?: boolean;
/** Device constraints for permission request */
constraints?: MediaStreamConstraints;
/** Device change callback */
onUpdated?: (devices: MediaDeviceInfo[]) => void;
}
/** Slot data exposed by UseDevicesList component */
interface UseDevicesListReturn {
/** All available devices */
devices: Ref<MediaDeviceInfo[]>;
/** Video input devices (cameras) */
videoInputs: Ref<MediaDeviceInfo[]>;
/** Audio input devices (microphones) */
audioInputs: Ref<MediaDeviceInfo[]>;
/** Audio output devices (speakers) */
audioOutputs: Ref<MediaDeviceInfo[]>;
/** Whether the MediaDevices API is supported */
isSupported: Ref<boolean>;
/** Permissions granted status */
permissionGranted: Ref<boolean>;
/** Refresh device list */
ensurePermissions: () => Promise<boolean>;
/** Update device list */
update: () => Promise<void>;
}
interface MediaDeviceInfo {
deviceId: string;
kind: 'videoinput' | 'audioinput' | 'audiooutput';
label: string;
groupId: string;
}
interface MediaStreamConstraints {
video?: boolean | MediaTrackConstraints;
audio?: boolean | MediaTrackConstraints;
}Usage Examples:
<template>
<!-- Basic device list -->
<UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs, isSupported }">
<div v-if="isSupported" class="devices-list">
<h3>Available Devices</h3>
<div class="device-category">
<h4>Cameras ({{ videoInputs.length }})</h4>
<ul>
<li v-for="device in videoInputs" :key="device.deviceId">
{{ device.label || `Camera ${device.deviceId.slice(0, 8)}...` }}
</li>
</ul>
</div>
<div class="device-category">
<h4>Microphones ({{ audioInputs.length }})</h4>
<ul>
<li v-for="device in audioInputs" :key="device.deviceId">
{{ device.label || `Microphone ${device.deviceId.slice(0, 8)}...` }}
</li>
</ul>
</div>
<div class="device-category">
<h4>Speakers ({{ audioOutputs.length }})</h4>
<ul>
<li v-for="device in audioOutputs" :key="device.deviceId">
{{ device.label || `Speaker ${device.deviceId.slice(0, 8)}...` }}
</li>
</ul>
</div>
</div>
<div v-else>Media devices not supported</div>
</UseDevicesList>
<!-- With permissions -->
<UseDevicesList
:request-permissions="true"
:constraints="{ video: true, audio: true }"
v-slot="{ devices, permissionGranted, ensurePermissions, update }"
>
<div class="permissions-devices">
<div v-if="!permissionGranted" class="permission-request">
<p>Camera and microphone access required</p>
<button @click="ensurePermissions">Grant Permissions</button>
</div>
<div v-else class="devices-granted">
<p>Found {{ devices.length }} devices</p>
<button @click="update">Refresh List</button>
<div v-for="device in devices" :key="device.deviceId" class="device-item">
<span class="device-type">{{ device.kind }}</span>
<span class="device-label">{{ device.label }}</span>
</div>
</div>
</div>
</UseDevicesList>
</template>
<script setup>
import { UseDevicesList } from '@vueuse/components';
</script>
<style>
.device-category {
margin: 15px 0;
}
.device-category h4 {
margin: 5px 0;
color: #666;
}
.device-category ul {
list-style: none;
padding: 0;
}
.device-category li {
padding: 5px 0;
border-bottom: 1px solid #eee;
}
.device-item {
display: flex;
justify-content: space-between;
padding: 8px;
border: 1px solid #ddd;
margin: 5px 0;
border-radius: 4px;
}
.device-type {
font-weight: bold;
text-transform: capitalize;
}
.permission-request {
text-align: center;
padding: 20px;
border: 2px dashed #ccc;
border-radius: 8px;
}
</style>/** Common types used across device and sensor components */
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);
interface RenderableComponent {
/** The element that the component should be rendered as @default 'div' */
as?: object | string;
}
/** Battery API types */
interface BatteryManager {
charging: boolean;
chargingTime: number;
dischargingTime: number;
level: number;
onchargingchange: ((this: BatteryManager, ev: Event) => any) | null;
onchargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;
ondischargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;
onlevelchange: ((this: BatteryManager, ev: Event) => any) | null;
}
/** Device motion and orientation types */
interface DeviceMotionEvent extends Event {
acceleration: DeviceMotionAcceleration | null;
accelerationIncludingGravity: DeviceMotionAcceleration | null;
rotationRate: DeviceMotionRotationRate | null;
interval: number;
}
interface DeviceOrientationEvent extends Event {
alpha: number | null;
beta: number | null;
gamma: number | null;
absolute: boolean;
}Install with Tessl CLI
npx tessl i tessl/npm-vueuse--components