useBattery
A hook to access real-time battery information
A React hook that provides real-time access to the device's battery status using the Battery Status API. Includes formatted time displays, status indicators, and low/critical battery detection.
Source Code
View the full hook implementation in the Hook Source Code section below.
Browser Support
The Battery Status API is only supported in Chromium-based browsers (Chrome, Edge, Opera). It is not available in Firefox or Safari due to privacy concerns.
Features
- Real-time Updates - Automatically updates when battery status changes
- SSR Safe - Returns loading state during server-side rendering
- Formatted Times - Human-readable strings like "1h 30m" for charge/discharge times
- Status Detection -
charging,discharging,full, orunknown - Low Battery Alerts -
isLow(< 20%) andisCritical(< 10%) flags - Percentage Helper -
levelPercentfor easy display (0-100)
Learn More
Basic Usage
Display battery level with a visual indicator:
Loading...
"use client";
import { useBattery } from "@repo/hooks/device/use-battery";
import { Zap } from "lucide-react";
/* BASIC USAGE - Battery Status */
export const Example1 = () => {
const battery = useBattery();
if (battery.isLoading) {
return (
<span className="text-muted-foreground text-sm">Loading...</span>
);
}
if (!battery.isSupported) {
return (
<div className="rounded-md border border-yellow-500/50 bg-yellow-500/10 p-4">
<p className="text-sm text-yellow-600 dark:text-yellow-400">
Battery API is not supported in this browser
</p>
</div>
);
}
return (
<div className="flex flex-col gap-3">
<div className="flex items-center gap-3">
<div
className={`h-25 flex w-10 flex-col items-center justify-end rounded-xl border-2 p-0.5 pr-0.5 ${
battery.isCharging
? "border-green-500"
: battery.isCritical
? "border-red-500"
: battery.isLow
? "border-yellow-500"
: "border-muted-foreground"
}`}
>
<div
className={`w-full rounded-lg transition-all ${
battery.isCharging
? "bg-green-500"
: battery.isCritical
? "bg-red-500"
: battery.isLow
? "bg-yellow-500"
: "bg-primary"
}`}
style={{ height: `${battery.levelPercent}%` }}
/>
</div>
<span className="text-2xl font-bold">
{battery.levelPercent}%
</span>
{battery.isCharging && (
<span className="text-sm text-green-500">
<Zap className="h-5 w-5" />
</span>
)}
</div>
<p className="text-muted-foreground text-sm capitalize">
Status: {battery.status}
</p>
</div>
);
};
Detailed Information
Show all available battery properties:
Loading...
"use client";
import { useBattery } from "@repo/hooks/device/use-battery";
/* DETAILED INFO - All Battery Properties */
export const Example2 = () => {
const battery = useBattery();
if (battery.isLoading) {
return (
<span className="text-muted-foreground text-sm">Loading...</span>
);
}
if (!battery.isSupported) {
return (
<div className="rounded-md border border-yellow-500/50 bg-yellow-500/10 p-4">
<p className="text-sm text-yellow-600 dark:text-yellow-400">
Battery API is not supported in this browser
</p>
</div>
);
}
const rows = [
{ label: "Level", value: `${battery.levelPercent}%` },
{ label: "Status", value: battery.status },
{ label: "Charging", value: battery.isCharging ? "Yes" : "No" },
{
label: "Time to Full",
value: battery.chargingTimeFormatted ?? "N/A",
},
{
label: "Time Remaining",
value: battery.dischargingTimeFormatted ?? "N/A",
},
{ label: "Low Battery", value: battery.isLow ? "Yes (Warning)" : "No" },
{
label: "Critical",
value: battery.isCritical ? "Yes (Critical)" : "No",
},
];
return (
<div className="w-full max-w-sm overflow-hidden rounded-lg border">
<div className="bg-muted/50 border-b px-4 py-2">
<span className="text-sm font-medium">Battery Details</span>
</div>
<div className="divide-y">
{rows.map((row) => (
<div
key={row.label}
className="flex justify-between px-4 py-2"
>
<span className="text-muted-foreground text-sm">
{row.label}
</span>
<span className="text-sm font-medium">{row.value}</span>
</div>
))}
</div>
</div>
);
};
API Reference
function useBattery(): BatteryState;BatteryState
| Property | Type | Description |
|---|---|---|
isSupported | boolean | Whether Battery API is available |
isLoading | boolean | true while fetching battery info |
level | number | null | Battery level from 0 to 1 |
levelPercent | number | null | Battery level as percentage (0-100) |
isCharging | boolean | null | Whether device is charging |
chargingTime | number | null | Seconds until full (Infinity if not charging) |
dischargingTime | number | null | Seconds until empty (Infinity if charging) |
chargingTimeFormatted | string | null | Human-readable time to full (e.g., "1h 30m") |
dischargingTimeFormatted | string | null | Human-readable time remaining |
status | "charging" | "discharging" | "full" | "unknown" | Current battery status |
isLow | boolean | true if level < 20% |
isCritical | boolean | true if level < 10% |
Common Patterns
Low Battery Warning
const battery = useBattery();
if (battery.isCritical) {
return <Alert variant="destructive">Battery critically low!</Alert>;
}
if (battery.isLow) {
return <Alert variant="warning">Battery low, consider charging</Alert>;
}Conditional Features
const battery = useBattery();
// Disable heavy features on low battery
const enableAnimations = !battery.isLow;
const enableAutoSync = battery.isCharging || (battery.levelPercent ?? 0) > 50;Unsupported Browser Fallback
const battery = useBattery();
if (!battery.isSupported) {
return null; // Hide battery indicator on unsupported browsers
}Hook Source Code
import { useState, useEffect, useCallback } from "react";
/**
* Battery Manager interface (experimental Web API)
*/
interface BatteryManager extends EventTarget {
charging: boolean;
chargingTime: number;
dischargingTime: number;
level: number;
}
/**
* Battery event names
*/
const BATTERY_EVENTS = {
LEVEL_CHANGE: "levelchange",
CHARGING_CHANGE: "chargingchange",
CHARGING_TIME_CHANGE: "chargingtimechange",
DISCHARGING_TIME_CHANGE: "dischargingtimechange",
} as const;
/**
* Battery state returned by the hook
*/
export interface BatteryState {
/** Whether the Battery API is supported in this browser */
isSupported: boolean;
/** Whether the battery data is still loading */
isLoading: boolean;
/** Battery level from 0 to 1 (e.g., 0.75 = 75%) */
level: number | null;
/** Battery level as a percentage (0-100) */
levelPercent: number | null;
/** Whether the device is currently charging */
isCharging: boolean | null;
/** Time in seconds until fully charged (Infinity if not charging) */
chargingTime: number | null;
/** Time in seconds until fully discharged (Infinity if charging) */
dischargingTime: number | null;
/** Formatted time until full charge (e.g., "1h 30m") */
chargingTimeFormatted: string | null;
/** Formatted time until discharge (e.g., "2h 45m") */
dischargingTimeFormatted: string | null;
/** Battery status: 'charging', 'discharging', 'full', or 'unknown' */
status: "charging" | "discharging" | "full" | "unknown";
/** Whether battery is low (below 20%) */
isLow: boolean;
/** Whether battery is critical (below 10%) */
isCritical: boolean;
}
/**
* Formats seconds into a human-readable string (e.g., "1h 30m")
*/
function formatTime(seconds: number | null): string | null {
if (seconds === null || seconds === Infinity || isNaN(seconds)) {
return null;
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
}
return `${minutes}m`;
}
/**
* Determines battery status based on charging state and level
*/
function getBatteryStatus(
charging: boolean | null,
level: number | null,
): BatteryState["status"] {
if (charging === null || level === null) return "unknown";
if (charging && level >= 1) return "full";
if (charging) return "charging";
return "discharging";
}
const initialState: BatteryState = {
isSupported: true,
isLoading: true,
level: null,
levelPercent: null,
isCharging: null,
chargingTime: null,
dischargingTime: null,
chargingTimeFormatted: null,
dischargingTimeFormatted: null,
status: "unknown",
isLow: false,
isCritical: false,
};
/**
* Adds all battery event listeners
*/
function addBatteryListeners(
battery: BatteryManager,
handler: EventListener,
): void {
Object.values(BATTERY_EVENTS).forEach((event) => {
battery.addEventListener(event, handler);
});
}
/**
* Removes all battery event listeners
*/
function removeBatteryListeners(
battery: BatteryManager,
handler: EventListener,
): void {
Object.values(BATTERY_EVENTS).forEach((event) => {
battery.removeEventListener(event, handler);
});
}
/**
* A React hook that provides real-time battery information.
*
* @returns BatteryState object with battery level, charging status, and more
*
* @example
* ```tsx
* const battery = useBattery();
*
* if (!battery.isSupported) {
* return <p>Battery API not supported</p>;
* }
*
* return (
* <div>
* <p>Battery: {battery.levelPercent}%</p>
* <p>Status: {battery.status}</p>
* </div>
* );
* ```
*/
export function useBattery(): BatteryState {
const [state, setState] = useState<BatteryState>(initialState);
const updateBatteryState = useCallback((battery: BatteryManager) => {
const level = battery.level;
const charging = battery.charging;
setState({
isSupported: true,
isLoading: false,
level,
levelPercent: Math.round(level * 100),
isCharging: charging,
chargingTime: battery.chargingTime,
dischargingTime: battery.dischargingTime,
chargingTimeFormatted: formatTime(battery.chargingTime),
dischargingTimeFormatted: formatTime(battery.dischargingTime),
status: getBatteryStatus(charging, level),
isLow: level < 0.2,
isCritical: level < 0.1,
});
}, []);
useEffect(() => {
// Check if running on server or if API is not supported
if (typeof window === "undefined" || !("getBattery" in navigator)) {
setState((s) => ({
...s,
isSupported: false,
isLoading: false,
}));
return;
}
let battery: BatteryManager | null = null;
let mounted = true;
const handleChange = () => {
if (battery && mounted) {
updateBatteryState(battery);
}
};
// Fetch the battery manager using async/await
(async () => {
try {
const bat: BatteryManager = await (
navigator as any
).getBattery();
if (!mounted) return;
battery = bat;
handleChange();
// Listen for changes
addBatteryListeners(bat, handleChange);
} catch {
if (mounted) {
setState((s) => ({
...s,
isSupported: false,
isLoading: false,
}));
}
}
})();
// Cleanup
return () => {
mounted = false;
if (battery) {
removeBatteryListeners(battery, handleChange);
}
};
}, [updateBatteryState]);
return state;
}
export default useBattery;