Lucide Motion

App-wide defaults

Set shared icon props with the MotionIconConfig provider.

<MotionIconConfig> is a React Context provider that sets default props for every icon in its subtree. Per-icon props always win — the provider is a fallback layer.

Resolution order

For each prop, the engine resolves in this order:

  1. Per-icon prop (highest priority)
  2. Nearest <MotionIconConfig> value
  3. Built-in default (lowest priority)

Basic usage

Wrap your app (or a section of it) and pass any of the config-eligible props:

import { MotionIconConfig } from "lucide-motion";

export default function RootLayout({ children }) {
  return (
    <MotionIconConfig duration={0.3} stagger={0.05} easing="easeOut">
      {children}
    </MotionIconConfig>
  );
}

Now every icon inside inherits those timing defaults.

Live example

inherits
inherits
overrides duration
<MotionIconConfig duration={0.2} stagger={0.04} easing="linear">
  <Heart size={36} />                         {/* uses config */}
  <Settings size={36} />                      {/* uses config */}
  <Rocket size={36} duration={1.4} />         {/* overrides duration */}
</MotionIconConfig>

Nesting

Providers can nest. Inner values override outer ones for keys you re-pass.

<MotionIconConfig duration={0.3} reducedMotion="system">
  {/* All icons here use duration=0.3, reducedMotion=system */}

  <section>
    <MotionIconConfig duration={0.8}>
      {/* Icons here use duration=0.8, but still reducedMotion=system */}
    </MotionIconConfig>
  </section>
</MotionIconConfig>

What it accepts

Every motion-related and styling prop on <DrawIcon />:

interface MotionIconConfigValue {
  size?: number;
  color?: string;
  strokeWidth?: number;
  absoluteStrokeWidth?: boolean;
  duration?: number;
  delay?: number;
  stagger?: number;
  easing?: Easing | Easing[];
  repeat?: number;
  trigger?: Trigger;
  onLeave?: OnLeave;
  reducedMotion?: ReducedMotion;
  mode?: ModeName | ModeFactory;
}

Props specific to a single icon (nodes, className, style, onClick, variants, ref) are not propagated — they wouldn't make sense as shared defaults.

Function-valued mode through context

A mode factory works as an app-wide default too — but pass the same factory reference (define it once at module scope, don't inline it), or every render produces a new function and breaks memoization.

Standardize timing across your app

<MotionIconConfig duration={0.35} stagger={0.04} easing="easeOut">
  <App />
</MotionIconConfig>

Make all buttons use parent-hover

Wrap a sub-tree that contains primarily buttons:

<MotionIconConfig trigger="parent-hover" onLeave="redraw">
  <ButtonRow />
</MotionIconConfig>

Config file pattern

Put your defaults in a regular module and pass them in:

lib/icon-config.ts
export const iconDefaults = {
  duration: 0.35,
  stagger: 0.04,
  easing: "easeOut",
  reducedMotion: "system",
} as const;
app/layout.tsx
import { MotionIconConfig } from "lucide-motion";
import { iconDefaults } from "@/lib/icon-config";

export default function RootLayout({ children }) {
  return (
    <MotionIconConfig {...iconDefaults}>
      {children}
    </MotionIconConfig>
  );
}

On this page