Fiber UI LogoFiberUI

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, or unknown
  • Low Battery Alerts - isLow (< 20%) and isCritical (< 10%) flags
  • Percentage Helper - levelPercent for 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

PropertyTypeDescription
isSupportedbooleanWhether Battery API is available
isLoadingbooleantrue while fetching battery info
levelnumber | nullBattery level from 0 to 1
levelPercentnumber | nullBattery level as percentage (0-100)
isChargingboolean | nullWhether device is charging
chargingTimenumber | nullSeconds until full (Infinity if not charging)
dischargingTimenumber | nullSeconds until empty (Infinity if charging)
chargingTimeFormattedstring | nullHuman-readable time to full (e.g., "1h 30m")
dischargingTimeFormattedstring | nullHuman-readable time remaining
status"charging" | "discharging" | "full" | "unknown"Current battery status
isLowbooleantrue if level < 20%
isCriticalbooleantrue 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;