npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-adaptive-perf

v1.0.5

Published

Adaptive performance library for React — automatically adjusts animations and effects based on device capability

Readme

react-adaptive-perf

Adaptive performance for React. Automatically adjusts animations, effects, and rendering complexity based on what the device can actually handle.

npm version CI license

Live Demo — Try it out and see how your device performs!

Why?

Your MacBook Pro and a budget Android phone get the same 60-particle confetti animation. One runs it smoothly. The other drops to 15fps and drains the battery.

This library detects device capability at runtime and gives you a simple API to adapt accordingly.

Install

npm install react-adaptive-perf

Quick Start

import { PerformanceProvider, useAdaptiveValue } from 'react-adaptive-perf';

function App() {
  return (
    <PerformanceProvider>
      <ParticleEffect />
    </PerformanceProvider>
  );
}

function ParticleEffect() {
  const particleCount = useAdaptiveValue({
    high: 500,
    medium: 200,
    low: 50,
    minimal: 0,
  });

  return <Particles count={particleCount} />;
}

How It Works

On mount, the library runs quick benchmarks (CSS transforms, Canvas 2D, WebGL) to measure actual rendering performance. Combined with hardware detection, it assigns one of four tiers:

| Tier | Typical Device | Recommendation | |------|----------------|----------------| | high | Modern desktop, flagship phones | Full animations, all effects | | medium | Mid-range devices, older flagships | Reduced particle counts, simpler transitions | | low | Budget phones, old hardware | Minimal animations, static alternatives | | minimal | Very old devices, reduced-motion preference | No animations |

The minimal tier is also applied when the user has prefers-reduced-motion enabled.

API

<PerformanceProvider>

Wrap your app to enable detection.

<PerformanceProvider
  config={{
    progressiveEnhancement: true, // Start minimal, upgrade if capable
    detailedBenchmark: true,      // Run CSS + Canvas + WebGL benchmarks
    benchmarkDuration: 200,       // Duration per benchmark (ms)
    persistOverride: true,        // Remember user's manual tier selection
    onTierDetected: (tier, hardware, benchmark) => {
      console.log('Detected:', tier);
    },
  }}
>
  <App />
</PerformanceProvider>

Benchmark Control

Fine-tune benchmark behavior for better INP (Interaction to Next Paint):

<PerformanceProvider
  config={{
    // Yield to main thread every 50ms during benchmarks (default: 50)
    // Allows user interactions while benchmark runs
    // Set to 0 to disable yielding (continuous benchmark)
    benchmarkYieldInterval: 50,

    // Throttle progress callback updates (default: 100ms)
    // Reduces re-renders during benchmark
    // Set to 0 for real-time updates
    progressThrottle: 100,

    // Delay before starting benchmark (default: 0)
    // Useful to let the page settle before measuring
    benchmarkDelay: 500,
  }}
>

usePerformance()

Access the current performance context.

const {
  tier,           // 'high' | 'medium' | 'low' | 'minimal'
  isDetecting,    // true while benchmarks are running
  hardware,       // { cpuCores, deviceMemory, gpu, isMobile, ... }
  benchmark,      // { css, canvas, webgl, composite }
  shouldAnimate,  // false if tier is 'minimal'
  setTier,        // Manually override tier
  clearOverride,  // Reset to auto-detected tier
} = usePerformance();

useAdaptiveValue(values)

Return different values based on the current tier.

const animationDuration = useAdaptiveValue({
  high: 600,
  medium: 300,
  low: 150,
  minimal: 0,
});

const quality = useAdaptiveValue({
  high: 'ultra',
  medium: 'high',
  low: 'medium',
  minimal: 'low',
});

// Values cascade: if 'low' isn't defined, it uses 'medium', then 'high'
const effects = useAdaptiveValue({
  high: ['blur', 'glow', 'shadow'],
  low: ['shadow'],
  minimal: [],
});

<AdaptiveMotion>

CSS transition wrapper that adapts to performance tier.

<AdaptiveMotion
  animate={isHovered}
  initial={{ scale: 1, opacity: 0.8 }}
  target={{ scale: 1.1, opacity: 1 }}
  className="card"
>
  Hover me
</AdaptiveMotion>

<AdaptiveImage>

Switches between animated and static images.

<AdaptiveImage
  animated="/hero.gif"
  static="/hero.jpg"
  animationTier="medium"  // Show animated version on medium+ devices
  alt="Hero"
/>

Integrations

Framer Motion

Wrap Framer Motion components to automatically reduce complexity on slower devices.

import { motion } from 'framer-motion';
import { AdaptiveFramerMotion } from 'react-adaptive-perf';

<AdaptiveFramerMotion
  as={motion.div}
  animate={{ scale: 1.1, rotate: 10 }}
  transition={{ type: 'spring' }}
  disableOn={['minimal']}
>
  Animated content
</AdaptiveFramerMotion>

Or use the hook for more control:

import { useAdaptiveFramerProps } from 'react-adaptive-perf';

function Card() {
  const props = useAdaptiveFramerProps({
    animate: { scale: 1.1, rotate: 10 },
    transition: { type: 'spring', stiffness: 300 },
  });

  return <motion.div {...props}>Card</motion.div>;
}

Lottie

Automatically falls back to a static image on low-performance devices.

import { AdaptiveLottie } from 'react-adaptive-perf';

<AdaptiveLottie
  animationData={heroAnimation}
  fallbackSrc="/hero-static.webp"
  animationTier="medium"
  loop
  autoplay
/>

Requires lottie-web as a peer dependency.

Debug Panel

Add a development overlay to visualise performance metrics and test different tiers.

import { DebugPanel } from 'react-adaptive-perf';

<DebugPanel
  position="bottom-right"
  devOnly          // Auto-hide in production
  showLiveFps      // Real-time FPS counter
/>

The panel shows:

  • Current tier with live FPS
  • Individual benchmark scores (CSS, Canvas, WebGL)
  • Hardware info (CPU cores, RAM, GPU)
  • Manual tier override controls

For a minimal indicator:

import { FPSBadge } from 'react-adaptive-perf';

<FPSBadge position="top-right" />

User Preferences

By default, manual tier overrides persist to localStorage. Users who select "High" quality keep that preference across sessions.

const { setTier, clearOverride, isOverridden } = usePerformance();

// User selects a tier
setTier('high');

// Reset to auto-detected
clearOverride();

Disable persistence:

<PerformanceProvider config={{ persistOverride: false }}>

Benchmarks

The detailed benchmark runs three tests:

| Test | Weight | What It Measures | |------|--------|------------------| | CSS | 40% | DOM transforms, filters, opacity transitions | | Canvas | 30% | 2D particle rendering with gradients | | WebGL | 30% | GPU shader performance with 1000 points |

Results are combined into a composite FPS score that determines the tier.

const { benchmark } = usePerformance();

// {
//   css: { fps: 58, jankScore: 0.02 },
//   canvas: { fps: 52, jankScore: 0.05 },
//   webgl: { fps: 45, jankScore: 0.08 },
//   composite: { fps: 52, tier: 'medium' }
// }

Hardware Detection

In addition to benchmarks, the library reads hardware signals:

const { hardware } = usePerformance();

// {
//   cpuCores: 8,
//   deviceMemory: 16,
//   isMobile: false,
//   gpu: 'ANGLE (Apple, Apple M1 Pro, OpenGL 4.1)',
//   prefersReducedMotion: false,
//   webglSupport: 'webgl2',
//   maxTextureSize: 16384,
//   screenRefreshRate: 120,
//   devicePixelRatio: 2,
// }

TypeScript

Fully typed. Key types:

import type {
  PerformanceTier,
  HardwareProfile,
  BenchmarkResult,
  DetailedBenchmarkResult,
  AdaptiveFramerMotionProps,
  AdaptiveLottieProps,
} from 'react-adaptive-perf';

Progressive Enhancement

The recommended approach for best Core Web Vitals. Start with a fast, static experience, then upgrade to animations if the device can handle it.

<PerformanceProvider config={{ progressiveEnhancement: true }}>
  <App />
</PerformanceProvider>

How it works:

  1. Initial render - Starts with tier: 'minimal' (no animations)
  2. Page loads - Waits for window.onload to complete
  3. Benchmark runs - Tests device capability when idle
  4. Upgrade only - If device scores well, tier upgrades (never downgrades)

Benefits:

  • LCP - No animations blocking initial paint
  • INP - Benchmark runs when page is idle
  • CLS - No downgrades = no layout shifts from removing animations

You can combine with initialTier if you want to start at a different baseline:

<PerformanceProvider config={{ progressiveEnhancement: true, initialTier: 'low' }}>

SSR & Hydration

The library is SSR-safe. During server rendering and initial hydration, it uses initialTier (default: 'high', or 'minimal' with progressiveEnhancement). Detection runs after hydration completes.

To prevent hydration mismatches when rendering tier-specific content, use <WhenDetected>:

import { WhenDetected, usePerformance } from 'react-adaptive-perf';

function Hero() {
  const { tier } = usePerformance();

  return (
    <WhenDetected fallback={<HeroSkeleton />}>
      {tier === 'high' ? <HeavyAnimation /> : <LightAnimation />}
    </WhenDetected>
  );
}

Or check isDetecting manually:

const { tier, isDetecting } = usePerformance();

if (isDetecting) return <Skeleton />;
return tier === 'high' ? <Heavy /> : <Light />;

Browser Support

Works in all modern browsers. Gracefully degrades when APIs aren't available:

  • WebGL benchmark skipped if WebGL unsupported
  • Hardware memory detection falls back to estimates
  • Reduced motion detection requires matchMedia support

License

MIT © Roland Farkas