Skip to content
8bitbishPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

FluxHover

A directional magnetic hover background effect for React. The highlight enters from the direction the cursor approached, follows the cursor softly while hovered, and exits toward where the cursor leaves.

Built with React, TypeScript, and Framer Motion.

Live demo →


Install

npm install @8bitbish/flux-hover framer-motion

react, react-dom, and framer-motion are peer dependencies.


Quick start

import { FluxHover } from '@8bitbish/flux-hover';

function NavButton() {
  return (
    <FluxHover background="rgba(255,255,255,0.08)" style={{ borderRadius: 10 }}>
      <button>Features</button>
    </FluxHover>
  );
}

The blob automatically inherits the borderRadius of the wrapper element — set it once via style or a CSS class and you're done.


Props

Prop Type Default Description
children ReactNode — Content to wrap
background string "rgba(255,255,255,0.09)" CSS background of the hover blob (color, gradient, etc.)
pressColor string — Color overlaid on the blob on pointer-down. Accepts any CSS color including rgba.
intensity number 0.25 Magnetic movement strength 0–1. Higher = more travel
duration number 0.22 Enter/exit animation duration in seconds
opacity number 1 Peak opacity of the blob 0–1
scale number 1 Scale of the blob at rest
enterDelay number 40 Delay in ms before hover activates. Cancels if cursor leaves first — useful for segmented controls
clip boolean false Clip blob to wrapper bounds (overflow: hidden). Blob slides in from the entry edge
focusVisible boolean true Show blob on :focus-visible (keyboard nav)
disabled boolean false Disable all hover and press effects
as keyof JSX.IntrinsicElements "div" Render wrapper as a different element
className string — Extra class on the wrapper element
style CSSProperties — Inline styles on the wrapper element. Use this to set borderRadius.

Any extra props (e.g. href, onClick) are forwarded to the underlying element.


Examples

Ghost button

<FluxHover
  background="rgba(255,255,255,0.07)"
  pressColor="rgba(255,255,255,0.12)"
  style={{ borderRadius: 10 }}
>
  <button>Click me</button>
</FluxHover>

Tinted button

<FluxHover
  background="rgba(99,102,241,0.22)"
  pressColor="rgba(99,102,241,0.2)"
  enterDelay={0}
  style={{ borderRadius: 10 }}
>
  <button style={{ color: '#a5b4fc' }}>Get started</button>
</FluxHover>

Segmented control

<div style={{ display: 'inline-flex', gap: 4, padding: 4, background: '#111', borderRadius: 11, overflow: 'hidden' }}>
  {['All', 'Design', 'Engineering'].map((tab) => (
    <FluxHover key={tab} style={{ borderRadius: 7 }}>
      <button>{tab}</button>
    </FluxHover>
  ))}
</div>

Card hover

<FluxHover
  as="article"
  background="rgba(99,102,241,0.08)"
  intensity={0.4}
  enterDelay={0}
  style={{ padding: 24, border: '1px solid #1e1e1e', borderRadius: 16 }}
>
  <h3>Card title</h3>
  <p>Some content</p>
</FluxHover>

FluxHoverProvider

Set project-wide defaults once. Every FluxHover in the tree inherits them, and any prop passed directly to an instance overrides the default.

import { FluxHoverProvider, FluxHover } from '@8bitbish/flux-hover';

<FluxHoverProvider defaults={{
  background:  "rgba(255,255,255,0.09)",
  pressColor:  "rgba(255,255,255,0.15)",
  duration:    0.22,
  intensity:   0.25,
  enterDelay:  40,
}}>
  <App />
</FluxHoverProvider>

// Inherits all provider defaults
<FluxHover style={{ borderRadius: 7 }}>
  <button>Tab</button>
</FluxHover>

// Overrides just what differs
<FluxHover background="rgba(99,102,241,0.22)" enterDelay={0} style={{ borderRadius: 10 }}>
  <button>Get started</button>
</FluxHover>

useFluxHover hook

For lower-level control, the hook is exported directly:

import { useFluxHover } from '@8bitbish/flux-hover';
import { motion, useTransform } from 'framer-motion';

function CustomButton() {
  const {
    wrapperRef,
    blobX, blobY,
    blobOpacity, blobScale,
    pressScale, pressOverlay,
    onPointerEnter, onPointerMove, onPointerLeave,
    onPointerDown, onPointerUp,
    onFocus, onBlur,
  } = useFluxHover({ intensity: 0.25, duration: 0.22 });

  const combinedScale = useTransform(() => blobScale.get() * pressScale.get());

  return (
    <div
      ref={wrapperRef}
      style={{ position: 'relative', display: 'inline-flex', borderRadius: 8 }}
      onPointerEnter={onPointerEnter}
      onPointerMove={onPointerMove}
      onPointerLeave={onPointerLeave}
      onPointerDown={onPointerDown}
      onPointerUp={onPointerUp}
      onFocus={onFocus}
      onBlur={onBlur}
    >
      <motion.span
        aria-hidden
        style={{
          position: 'absolute', inset: 0,
          borderRadius: 'inherit',
          background: 'rgba(255,255,255,0.09)',
          overflow: 'hidden',
          pointerEvents: 'none',
          x: blobX, y: blobY,
          opacity: blobOpacity,
          scale: combinedScale,
        }}
      >
        <motion.span
          aria-hidden
          style={{
            position: 'absolute', inset: 0,
            background: 'rgba(255,255,255,0.15)',
            opacity: pressOverlay,
          }}
        />
      </motion.span>
      <span style={{ position: 'relative', zIndex: 1 }}>
        Custom content
      </span>
    </div>
  );
}

Customization notes

Subtle default feel — defaults are tuned for modern dark UIs. Small background opacity (0.07–0.12) and moderate intensity (0.25) produce the most polished result.

Segmented controls — use enterDelay={40} to debounce fast sweeps, preventing the effect from firing when the cursor briefly crosses an item.

Standalone buttons — pass enterDelay={0} to override the default delay for elements that should respond instantly.

Gradient backgrounds — any CSS background value works:

background="linear-gradient(135deg, rgba(99,102,241,0.25), rgba(168,85,247,0.25))"

Reduced motion — FluxHover automatically detects prefers-reduced-motion and skips all animations, falling back to an instant appear/disappear.

Focus visible — keyboard users get the same hover highlight on :focus-visible. Pass focusVisible={false} to opt out.


License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages