Motion Primitives

Every animation,
interactive.

Explore NibMotion's full animation API with live Flutter demos and copyable code.

Entrance

Fade

Opacity-based enter and exit. The simplest and most versatile animation.

dart
NibMotion(
  initial: NibAnim(opacity: 0),
  animate: NibAnim(opacity: 1),
  exit:    NibAnim(opacity: 0),
  transition: const NibTransition(),
  child: MyWidget(),
)

Fade In

Opacity-only entrance. Simplest NibMotion animation — no transform involved.

dart
class FadeInExample extends StatefulWidget {
  const FadeInExample({super.key});

  @override
  State<FadeInExample> createState() => _FadeInExampleState();
}

class _FadeInExampleState extends State<FadeInExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(opacity: 0),
                animate: const NibAnim(opacity: 1),
                transition: const NibTransition(duration: Duration(milliseconds: 600)),
                child: Container(
                  width: 200,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                  ),
                  child: const Center(
                    child: Text(
                      'Fade In',
                      style: TextStyle(
                        color: Color(0xFFEDF2F7),
                        fontSize: 16,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Slide Up

Pure Y-axis entrance with no opacity change. Card rises into place.

dart
class SlideUpExample extends StatefulWidget {
  const SlideUpExample({super.key});

  @override
  State<SlideUpExample> createState() => _SlideUpExampleState();
}

class _SlideUpExampleState extends State<SlideUpExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(y: 60, opacity: 0),
                animate: const NibAnim(y: 0, opacity: 1),
                transition: const NibTransition(
                  spring: NibSpringDescription.gentle,
                  duration: Duration(milliseconds: 650),
                ),
                child: Container(
                  width: 200,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                  ),
                  child: const Center(
                    child: Text(
                      'Slide Up',
                      style: TextStyle(
                        color: Color(0xFFEDF2F7),
                        fontSize: 16,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Slide Horizontal

Slide in from the right using the X axis. Useful for drawer panels and side sheets.

dart
class SlideHorizontalExample extends StatefulWidget {
  const SlideHorizontalExample({super.key});

  @override
  State<SlideHorizontalExample> createState() => _SlideHorizontalExampleState();
}

class _SlideHorizontalExampleState extends State<SlideHorizontalExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(x: 110, opacity: 0),
                animate: const NibAnim(x: 0, opacity: 1),
                transition: const NibTransition(
                  spring: NibSpringDescription.snappy,
                  duration: Duration(milliseconds: 600),
                ),
                child: Container(
                  width: 200,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                  ),
                  child: const Center(
                    child: Text(
                      'Slide In ←',
                      style: TextStyle(
                        color: Color(0xFFEDF2F7),
                        fontSize: 16,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Fade + Slide

Combined opacity and Y offset — the most common entrance pattern.

dart
class FadeWithSlideExample extends StatefulWidget {
  const FadeWithSlideExample({super.key});

  @override
  State<FadeWithSlideExample> createState() => _FadeWithSlideExampleState();
}

class _FadeWithSlideExampleState extends State<FadeWithSlideExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(opacity: 0, y: 24),
                animate: const NibAnim(opacity: 1, y: 0),
                transition: NibTransition.springGentle,
                child: Container(
                  width: 200,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                  ),
                  child: const Center(
                    child: Text(
                      'Fade + Slide',
                      style: TextStyle(
                        color: Color(0xFFEDF2F7),
                        fontSize: 16,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}
Exit

Exit

Intercepts widget removal, plays the exit animation to completion, then unmounts.

dart
NibPresence(
  children: isVisible
    ? [
        NibMotion(
          key: const ValueKey('modal'),
          initial: NibAnim(opacity: 0, scale: 0.94),
          animate: NibAnim(opacity: 1, scale: 1.0),
          exit:    NibAnim(opacity: 0, scale: 0.94),
          transition: NibTransition.springSnappy,
          child: MyModal(),
        ),
      ]
    : [],
)

Presence Toggle

NibPresence intercepts widget removal so the exit animation plays to completion before unmount.

dart
class PresenceToggleExample extends StatefulWidget {
  const PresenceToggleExample({super.key});

  @override
  State<PresenceToggleExample> createState() => _PresenceToggleExampleState();
}

class _PresenceToggleExampleState extends State<PresenceToggleExample> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              height: 140,
              child: Center(
                child: NibPresence(
                  children: _visible
                      ? [
                          NibMotion(
                            key: const ValueKey('card'),
                            initial: const NibAnim(opacity: 0, scale: 0.9),
                            animate: const NibAnim(opacity: 1, scale: 1.0),
                            exit: const NibAnim(opacity: 0, scale: 0.9),
                            transition: NibTransition.springSnappy,
                            child: Container(
                              width: 200,
                              height: 100,
                              decoration: BoxDecoration(
                                color: const Color(0xFF1C2537),
                                borderRadius: BorderRadius.circular(12),
                                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                              ),
                              child: const Center(
                                child: Text(
                                  'Toggle Me',
                                  style: TextStyle(
                                    color: Color(0xFFEDF2F7),
                                    fontSize: 16,
                                    fontWeight: FontWeight.w600,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ]
                      : [],
                ),
              ),
            ),
            const SizedBox(height: 16),
            FilledButton(
              onPressed: () => setState(() => _visible = !_visible),
              style: FilledButton.styleFrom(backgroundColor: const Color(0xFF1C2537)),
              child: Text(
                _visible ? 'Remove' : 'Show',
                style: const TextStyle(color: Color(0xFF54C5F8)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Slide Exit

Widget slides off-screen on removal. Useful for toasts, drawers, and bottom sheets.

dart
class SlideExitExample extends StatefulWidget {
  const SlideExitExample({super.key});

  @override
  State<SlideExitExample> createState() => _SlideExitExampleState();
}

class _SlideExitExampleState extends State<SlideExitExample> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              height: 140,
              child: Center(
                child: NibPresence(
                  children: _visible
                      ? [
                          NibMotion(
                            key: const ValueKey('toast'),
                            initial: const NibAnim(x: 300, opacity: 0),
                            animate: const NibAnim(x: 0, opacity: 1),
                            exit: const NibAnim(x: 300, opacity: 0),
                            transition: NibTransition.springSnappy,
                            child: Container(
                              width: 200,
                              height: 60,
                              decoration: BoxDecoration(
                                color: const Color(0xFF1C2537),
                                borderRadius: BorderRadius.circular(12),
                                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                              ),
                              child: const Center(
                                child: Text(
                                  '→ Slide Exit',
                                  style: TextStyle(
                                    color: Color(0xFFEDF2F7),
                                    fontSize: 14,
                                    fontWeight: FontWeight.w600,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ]
                      : [],
                ),
              ),
            ),
            const SizedBox(height: 16),
            FilledButton(
              onPressed: () => setState(() => _visible = !_visible),
              style: FilledButton.styleFrom(backgroundColor: const Color(0xFF1C2537)),
              child: Text(
                _visible ? 'Dismiss' : 'Show',
                style: const TextStyle(color: Color(0xFF54C5F8)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Scale + Fade Exit

Scales down and fades out on removal — natural for modals and dialogs.

dart
class ScaleExitExample extends StatefulWidget {
  const ScaleExitExample({super.key});

  @override
  State<ScaleExitExample> createState() => _ScaleExitExampleState();
}

class _ScaleExitExampleState extends State<ScaleExitExample> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              height: 160,
              child: Center(
                child: NibPresence(
                  children: _visible
                      ? [
                          NibMotion(
                            key: const ValueKey('modal'),
                            initial: const NibAnim(opacity: 0, scale: 0.85),
                            animate: const NibAnim(opacity: 1, scale: 1.0),
                            exit: const NibAnim(opacity: 0, scale: 0.85),
                            transition: NibTransition.springGentle,
                            child: Container(
                              width: 200,
                              height: 120,
                              decoration: BoxDecoration(
                                color: const Color(0xFF1C2537),
                                borderRadius: BorderRadius.circular(16),
                                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                              ),
                              child: const Center(
                                child: Text(
                                  'Scale Exit',
                                  style: TextStyle(
                                    color: Color(0xFFEDF2F7),
                                    fontSize: 16,
                                    fontWeight: FontWeight.w600,
                                  ),
                                ),
                              ),
                            ),
                          ),
                        ]
                      : [],
                ),
              ),
            ),
            const SizedBox(height: 16),
            FilledButton(
              onPressed: () => setState(() => _visible = !_visible),
              style: FilledButton.styleFrom(backgroundColor: const Color(0xFF1C2537)),
              child: Text(
                _visible ? 'Dismiss' : 'Show',
                style: const TextStyle(color: Color(0xFF54C5F8)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Gestures

Gesture

Declare gesture states — NibMotion handles the rest. whileTap, whileHover, whileDrag.

dart
NibMotion(
  whileTap:   NibAnim(scale: 0.92, opacity: 0.85),
  whileHover: NibAnim(scale: 1.06, y: -4),
  transition: NibTransition.springSnappy,
  child: MyButton(),
)

While Tap

Animates to target values while pressed, springs back on release.

dart
class WhileTapExample extends StatelessWidget {
  const WhileTapExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: NibMotion(
          whileTap: const NibAnim(scale: 0.4),

          transition: NibTransition.springSnappy,
          child: GestureDetector(
            onTap: () {},
            child: Container(
              width: 180,
              height: 56,
              decoration: BoxDecoration(
                color: const Color(0xFF54C5F8),
                borderRadius: BorderRadius.circular(12),
              ),
              child: const Center(
                child: Text(
                  'Tap Me',
                  style: TextStyle(
                    color: Color(0xFF07080D),
                    fontSize: 16,
                    fontWeight: FontWeight.w700,
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

While Hover

Responds to pointer hover — useful for desktop and web Flutter targets.

dart
class WhileHoverExample extends StatelessWidget {
  const WhileHoverExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: NibMotion(
          whileHover: const NibAnim(scale: 1.05, y: -4),
          transition: const NibTransition(duration: Duration(milliseconds: 200)),
          child: Container(
            width: 200,
            height: 100,
            decoration: BoxDecoration(
              color: const Color(0xFF1C2537),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
            ),
            child: const Center(
              child: Text(
                'Hover Me',
                style: TextStyle(
                  color: Color(0xFFEDF2F7),
                  fontSize: 16,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

While Drag

Makes a widget draggable and animates it while being dragged.

dart
class WhileDragExample extends StatelessWidget {
  const WhileDragExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotion(
              drag: const NibDragConfig(),
              whileDrag: const NibAnim(scale: 1.06, opacity: 0.85),
              transition: NibTransition.springSnappy,
              child: Container(
                width: 180,
                height: 80,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(80)),
                ),
                child: const Center(
                  child: Text(
                    'Drag Me',
                    style: TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 16),
            const Text(
              'Drag anywhere',
              style: TextStyle(color: Color(0xFF718096), fontSize: 13),
            ),
          ],
        ),
      ),
    );
  }
}
Transitions

Transition

Configures duration, curve, delay, and stagger for any animation.

dart
NibMotion(
  animate: NibAnim(opacity: 1),
  transition: NibTransition.springSnappy,
  child: MyWidget(),
)

Spring Presets

Three physics spring presets — gentle, snappy, and wobbly — covering most common use cases.

dart
class SpringPresetsExample extends StatefulWidget {
  const SpringPresetsExample({super.key});

  @override
  State<SpringPresetsExample> createState() => _SpringPresetsExampleState();
}

class _SpringPresetsExampleState extends State<SpringPresetsExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  _SpringCard(label: 'Gentle', transition: NibTransition.springGentle),
                  const SizedBox(width: 12),
                  _SpringCard(label: 'Snappy', transition: NibTransition.springSnappy),
                  const SizedBox(width: 12),
                  _SpringCard(label: 'Wobbly', transition: NibTransition.springWobbly),
                ],
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

class _SpringCard extends StatelessWidget {
  const _SpringCard({required this.label, required this.transition});

  final String label;
  final NibTransition transition;

  @override
  Widget build(BuildContext context) {
    return NibMotion(
      initial: const NibAnim(y: 60),
      animate: const NibAnim(y: 0),
      transition: transition,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 80,
            height: 80,
            decoration: BoxDecoration(
              color: const Color(0xFF1C2537),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
            ),
          ),
          const SizedBox(height: 8),
          Text(
            label,
            style: const TextStyle(color: Color(0xFF718096), fontSize: 12),
          ),
        ],
      ),
    );
  }
}

Tween Duration

Configure duration and easing curve for a classic tween-based transition.

dart
class TweenDurationExample extends StatefulWidget {
  const TweenDurationExample({super.key});

  @override
  State<TweenDurationExample> createState() => _TweenDurationExampleState();
}

class _TweenDurationExampleState extends State<TweenDurationExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  _DurationCard(label: '200ms', duration: const Duration(milliseconds: 200)),
                  const SizedBox(width: 12),
                  _DurationCard(label: '600ms', duration: const Duration(milliseconds: 600)),
                  const SizedBox(width: 12),
                  _DurationCard(label: '1200ms', duration: const Duration(milliseconds: 1200)),
                ],
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

class _DurationCard extends StatelessWidget {
  const _DurationCard({required this.label, required this.duration});

  final String label;
  final Duration duration;

  @override
  Widget build(BuildContext context) {
    return NibMotion(
      initial: const NibAnim(opacity: 0),
      animate: const NibAnim(opacity: 1),
      transition: NibTransition(duration: duration, curve: Curves.easeOut),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Container(
            width: 80,
            height: 80,
            decoration: BoxDecoration(
              color: const Color(0xFF1C2537),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
            ),
          ),
          const SizedBox(height: 8),
          Text(
            label,
            style: const TextStyle(color: Color(0xFF718096), fontSize: 12),
          ),
        ],
      ),
    );
  }
}

Delay & Stagger

Add a delay to offset when an animation starts, or staggerChildren to cascade across children.

dart
class DelayStaggerExample extends StatefulWidget {
  const DelayStaggerExample({super.key});

  @override
  State<DelayStaggerExample> createState() => _DelayStaggerExampleState();
}

class _DelayStaggerExampleState extends State<DelayStaggerExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  _DelayItem(label: 'First — 0ms', delay: Duration.zero),
                  const SizedBox(height: 10),
                  _DelayItem(label: 'Second — 200ms', delay: const Duration(milliseconds: 200)),
                  const SizedBox(height: 10),
                  _DelayItem(label: 'Third — 400ms', delay: const Duration(milliseconds: 400)),
                ],
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

class _DelayItem extends StatelessWidget {
  const _DelayItem({required this.label, required this.delay});

  final String label;
  final Duration delay;

  @override
  Widget build(BuildContext context) {
    return NibMotion(
      initial: const NibAnim(opacity: 0, y: 20),
      animate: const NibAnim(opacity: 1, y: 0),
      transition: NibTransition(
        spring: NibSpringDescription.gentle,
        delay: delay,
      ),
      child: Container(
        width: 240,
        height: 48,
        decoration: BoxDecoration(
          color: const Color(0xFF1C2537),
          borderRadius: BorderRadius.circular(10),
          border: Border.all(color: const Color(0xFF54C5F8).withAlpha(40)),
        ),
        child: Center(
          child: Text(
            label,
            style: const TextStyle(color: Color(0xFFEDF2F7), fontSize: 13),
          ),
        ),
      ),
    );
  }
}
Transform

Rotate

Rotation-based transform animation, driven manually or via gesture.

dart
NibMotion(
  animate: NibAnim(rotate: 0.25), // 0.25 turns = 90°
  transition: NibTransition.springSnappy,
  child: MyWidget(),
)

Rotate on Tap

Rotates 45° while pressed using whileTap, springs back on release.

dart
class RotateOnTapExample extends StatelessWidget {
  const RotateOnTapExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: NibMotion(
          whileTap: const NibAnim(rotate: 0.125),
          transition: NibTransition.springSnappy,
          child: GestureDetector(
            onTap: () {},
            child: Container(
              width: 100,
              height: 100,
              decoration: BoxDecoration(
                color: const Color(0xFF1C2537),
                borderRadius: BorderRadius.circular(20),
                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(80)),
              ),
              child: const Center(
                child: Icon(Icons.star_rounded, color: Color(0xFF54C5F8), size: 40),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Rotate Toggle

Animates between two rotation states — useful for expand/collapse chevrons.

dart
class RotateToggleExample extends StatefulWidget {
  const RotateToggleExample({super.key});

  @override
  State<RotateToggleExample> createState() => _RotateToggleExampleState();
}

class _RotateToggleExampleState extends State<RotateToggleExample> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: GestureDetector(
          onTap: () => setState(() => _expanded = !_expanded),
          child: Container(
            width: 240,
            height: 56,
            decoration: BoxDecoration(
              color: const Color(0xFF1C2537),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFF54C5F8).withAlpha(40)),
            ),
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text(
                  'Expand section',
                  style: TextStyle(color: Color(0xFFEDF2F7), fontSize: 14),
                ),
                NibMotion(
                  animate: NibAnim(rotate: _expanded ? 0.5 : 0.0),
                  transition: NibTransition.springGentle,
                  child: const Icon(Icons.expand_more, color: Color(0xFF54C5F8)),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Rotate Entrance

Spins in from −90° on mount. Uses springWobbly for a satisfying wind-up feel.

dart
class RotateEntranceExample extends StatefulWidget {
  const RotateEntranceExample({super.key});

  @override
  State<RotateEntranceExample> createState() => _RotateEntranceExampleState();
}

class _RotateEntranceExampleState extends State<RotateEntranceExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(rotate: -0.25),
                animate: const NibAnim(rotate: 0),
                transition: NibTransition.springWobbly,
                child: Container(
                  width: 100,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(20),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(80)),
                  ),
                  child: const Center(
                    child: Icon(Icons.auto_awesome, color: Color(0xFF54C5F8), size: 40),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}
List & Stagger

Stagger

Staggers enter/exit animations across a list of children.

dart
NibMotionList(
  initial: NibAnim(opacity: 0, y: 32),
  animate: NibAnim(opacity: 1, y: 0),
  staggerDelay: Duration(milliseconds: 60),
  children: items.map((item) => ItemCard(item)).toList(),
)

Stagger List

Each child animates in with an offset delay — use NibMotionList for automatic staggering.

dart
class StaggerListExample extends StatefulWidget {
  const StaggerListExample({super.key});

  @override
  State<StaggerListExample> createState() => _StaggerListExampleState();
}

class _StaggerListExampleState extends State<StaggerListExample> {
  final _signal = NibMotionReplaySignal();

  static const _items = ['Spring physics', 'Gesture states', 'Exit animations', 'Viewport triggers'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: NibMotionReplayScope(
                signal: _signal,
                child: NibMotionList(
                  initial: const NibAnim(opacity: 0, y: 16),
                  animate: const NibAnim(opacity: 1, y: 0),
                  staggerDelay: const Duration(milliseconds: 80),
                  transition: NibTransition.springGentle,
                  children: _items
                      .map(
                        (label) => Padding(
                          key: ValueKey(label),
                          padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 5),
                          child: Container(
                            height: 48,
                            decoration: BoxDecoration(
                              color: const Color(0xFF1C2537),
                              borderRadius: BorderRadius.circular(10),
                              border: Border.all(color: const Color(0xFF54C5F8).withAlpha(30)),
                            ),
                            child: Center(
                              child: Text(label, style: const TextStyle(color: Color(0xFFEDF2F7), fontSize: 14)),
                            ),
                          ),
                        ),
                      )
                      .toList(),
                ),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 24),
            child: TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ),
        ],
      ),
    );
  }
}

Stagger Reverse

Reverse direction staggers from last child to first — useful for exit sequences.

dart
class StaggerReverseExample extends StatefulWidget {
  const StaggerReverseExample({super.key});

  @override
  State<StaggerReverseExample> createState() => _StaggerReverseExampleState();
}

class _StaggerReverseExampleState extends State<StaggerReverseExample> {
  final _signal = NibMotionReplaySignal();

  static const _items = ['First', 'Second', 'Third', 'Fourth'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Column(
        children: [
          Expanded(
            child: Center(
              child: NibMotionReplayScope(
                signal: _signal,
                child: NibMotionList(
                  initial: const NibAnim(opacity: 0, y: 16),
                  animate: const NibAnim(opacity: 1, y: 0),
                  staggerDelay: const Duration(milliseconds: 100),
                  staggerDirection: NibStaggerDirection.reverse,
                  transition: NibTransition.springGentle,
                  children: _items
                      .map(
                        (label) => Padding(
                          key: ValueKey(label),
                          padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 5),
                          child: Container(
                            height: 48,
                            decoration: BoxDecoration(
                              color: const Color(0xFF1C2537),
                              borderRadius: BorderRadius.circular(10),
                              border: Border.all(color: const Color(0xFF54C5F8).withAlpha(30)),
                            ),
                            child: Center(
                              child: Text(label, style: const TextStyle(color: Color(0xFFEDF2F7), fontSize: 14)),
                            ),
                          ),
                        ),
                      )
                      .toList(),
                ),
              ),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(bottom: 24),
            child: TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ),
        ],
      ),
    );
  }
}

Stagger In View

Stagger triggered when items enter the viewport — no manual scroll listener needed.

dart
class StaggerInViewExample extends StatelessWidget {
  const StaggerInViewExample({super.key});

  static const _items = [
    'Scroll to reveal',
    'Each item fades',
    'In staggered order',
    'Using whileInView',
    'With per-item delay',
    'No scroll listener',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: ListView.builder(
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
        itemCount: _items.length,
        itemBuilder: (context, index) => Padding(
          padding: const EdgeInsets.only(bottom: 10),
          child: NibMotion(
            initial: const NibAnim(opacity: 0, y: 20),
            whileInView: const NibAnim(opacity: 1, y: 0),
            viewport: const NibInViewConfig(amount: 0.3, once: false),
            transition: NibTransition(
              spring: NibSpringDescription.gentle,
              delay: Duration(milliseconds: index * 60),
            ),
            child: Container(
              height: 48,
              decoration: BoxDecoration(
                color: const Color(0xFF1C2537),
                borderRadius: BorderRadius.circular(10),
                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(30)),
              ),
              child: Center(
                child: Text(_items[index], style: const TextStyle(color: Color(0xFFEDF2F7), fontSize: 14)),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Viewport

In View

Triggers an animation when a widget enters the viewport.

dart
NibMotion(
  initial: NibAnim(opacity: 0, y: 24),
  whileInView: NibAnim(opacity: 1, y: 0),
  transition: NibTransition.springGentle,
  child: MyWidget(),
)

Fade In View

Fades in when the widget enters the viewport. Replays each time it enters.

dart
class InViewFadeExample extends StatelessWidget {
  const InViewFadeExample({super.key});

  static const _labels = [
    'Scroll down',
    'Each card fades',
    'As it enters view',
    'Opacity only',
    'Replays on re-enter',
    'whileInView',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: ListView.builder(
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
        itemCount: _labels.length,
        itemBuilder: (context, index) => Padding(
          padding: const EdgeInsets.only(bottom: 12),
          child: NibMotion(
            initial: const NibAnim(opacity: 0),
            whileInView: const NibAnim(opacity: 1),
            viewport: const NibInViewConfig(amount: 0.3, once: false),
            transition: const NibTransition(duration: Duration(milliseconds: 500)),
            child: Container(
              height: 56,
              decoration: BoxDecoration(
                color: const Color(0xFF1C2537),
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(30)),
              ),
              child: Center(
                child: Text(_labels[index], style: const TextStyle(color: Color(0xFFEDF2F7), fontSize: 14)),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Slide In View

Slides up from below when scrolled into view.

dart
class InViewSlideExample extends StatelessWidget {
  const InViewSlideExample({super.key});

  static const _labels = [
    'Scroll down',
    'Each card slides up',
    'Y axis only',
    'No opacity change',
    'Spring physics',
    'whileInView',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: ListView.builder(
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
        itemCount: _labels.length,
        itemBuilder: (context, index) => Padding(
          padding: const EdgeInsets.only(bottom: 12),
          child: NibMotion(
            initial: const NibAnim(y: 32),
            whileInView: const NibAnim(y: 0),
            viewport: const NibInViewConfig(amount: 0.3, once: false),
            transition: NibTransition.springGentle,
            child: Container(
              height: 56,
              decoration: BoxDecoration(
                color: const Color(0xFF1C2537),
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(30)),
              ),
              child: Center(
                child: Text(_labels[index], style: const TextStyle(color: Color(0xFFEDF2F7), fontSize: 14)),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Animate Once

Trigger the animation only the first time the widget enters the viewport.

dart
class InViewOnceExample extends StatelessWidget {
  const InViewOnceExample({super.key});

  static const _labels = [
    'Scroll down',
    'Animates once only',
    'Scroll back up',
    'Then back down',
    "Won't replay",
    'once: true',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: ListView.builder(
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
        itemCount: _labels.length,
        itemBuilder: (context, index) => Padding(
          padding: const EdgeInsets.only(bottom: 12),
          child: NibMotion(
            initial: const NibAnim(opacity: 0),
            whileInView: const NibAnim(opacity: 1),
            viewport: const NibInViewConfig(amount: 0.3, once: true),
            transition: const NibTransition(duration: Duration(milliseconds: 500)),
            child: Container(
              height: 56,
              decoration: BoxDecoration(
                color: const Color(0xFF1C2537),
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(30)),
              ),
              child: Center(
                child: Text(_labels[index], style: const TextStyle(color: Color(0xFFEDF2F7), fontSize: 14)),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Physics

Spring

Physics-based spring motion driven by mass, stiffness, and damping.

dart
NibMotion(
  initial: NibAnim(opacity: 0, y: 16),
  animate: NibAnim(opacity: 1, y: 0),
  transition: NibTransition(
    spring: NibSpringDescription.wobbly,
  ),
  child: MyWidget(),
)

Spring Entrance

Physics-based spring entrance using mass, stiffness, and damping instead of a fixed duration.

dart
class SpringEntranceExample extends StatefulWidget {
  const SpringEntranceExample({super.key});

  @override
  State<SpringEntranceExample> createState() => _SpringEntranceExampleState();
}

class _SpringEntranceExampleState extends State<SpringEntranceExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(y: 60),
                animate: const NibAnim(y: 0),
                transition: NibTransition.springGentle,
                child: Container(
                  width: 200,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                  ),
                  child: const Center(
                    child: Text(
                      'Spring Gentle',
                      style: TextStyle(color: Color(0xFFEDF2F7), fontSize: 16, fontWeight: FontWeight.w600),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Wobbly Bounce

springWobbly overshoots and oscillates — great for playful UI like FABs or confirmations.

dart
class WobblyBounceExample extends StatefulWidget {
  const WobblyBounceExample({super.key});

  @override
  State<WobblyBounceExample> createState() => _WobblyBounceExampleState();
}

class _WobblyBounceExampleState extends State<WobblyBounceExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(scale: 0.0),
                animate: const NibAnim(scale: 1.0),
                transition: NibTransition.springWobbly,
                child: Container(
                  width: 100,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF54C5F8),
                    borderRadius: BorderRadius.circular(20),
                  ),
                  child: const Center(
                    child: Icon(Icons.check_rounded, color: Color(0xFF07080D), size: 40),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Custom Spring

Full control over mass, stiffness, and damping with NibSpringDescription.

dart
class CustomSpringExample extends StatefulWidget {
  const CustomSpringExample({super.key});

  @override
  State<CustomSpringExample> createState() => _CustomSpringExampleState();
}

class _CustomSpringExampleState extends State<CustomSpringExample> {
  final _signal = NibMotionReplaySignal();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotionReplayScope(
              signal: _signal,
              child: NibMotion(
                initial: const NibAnim(y: 80),
                animate: const NibAnim(y: 0),
                transition: const NibTransition(
                  spring: NibSpringDescription(
                    mass: 1.0,
                    stiffness: 200.0,
                    damping: 16.0,
                  ),
                ),
                child: Container(
                  width: 200,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                  ),
                  child: const Center(
                    child: Text(
                      'Custom Spring',
                      style: TextStyle(color: Color(0xFFEDF2F7), fontSize: 15, fontWeight: FontWeight.w600),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 8),
            const Text(
              'mass: 1.0  stiffness: 200  damping: 16',
              style: TextStyle(color: Color(0xFF718096), fontSize: 11, fontFamily: 'monospace'),
            ),
            const SizedBox(height: 16),
            TextButton.icon(
              onPressed: _signal.replay,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}
Text

Text

Animate text per character, word, or line, sweep gradients across glyphs, reveal blocks with a clip-mask wipe, or type text out like a typewriter.

dart
NibText(
  'Hello world',
  config: NibTextConfig(
    animate: NibAnim(opacity: 1, y: 0),
    initial: NibAnim(opacity: 0, y: 12),
  ),
)

Gradient Sweep

An animated gradient sweeps across the glyphs of static text via a srcIn mask — the text itself never moves.

dart
class TextGradientExample extends StatelessWidget {
  const TextGradientExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: NibGradientText(
          'Gradient Sweep',
          gradient: const LinearGradient(
            colors: [Color(0xFF54C5F8), Color(0xFFEDF2F7), Color(0xFF54C5F8)],
          ),
          duration: const Duration(milliseconds: 2000),
          style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w700),
        ),
      ),
    );
  }
}

Typewriter

Characters are revealed one at a time at a fixed speed, with a blinking cursor — the one widget in the text system that legitimately uses setState.

dart
class TypewriterExample extends StatefulWidget {
  const TypewriterExample({super.key});

  @override
  State<TypewriterExample> createState() => _TypewriterExampleState();
}

class _TypewriterExampleState extends State<TypewriterExample> {
  int _replayCount = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            KeyedSubtree(
              key: ValueKey(_replayCount),
              child: NibTypewriter(
                'Typed one character at a time.',
                speed: const Duration(milliseconds: 50),
                cursor: '|',
                repeat: true,
                style: const TextStyle(
                  color: Color(0xFFEDF2F7),
                  fontSize: 18,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: () => setState(() => _replayCount++),
              icon: const Icon(
                Icons.replay,
                size: 16,
                color: Color(0xFF718096),
              ),
              label: const Text(
                'Replay',
                style: TextStyle(color: Color(0xFF718096), fontSize: 13),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Text Reveal

A block of text wipes into view via an animated clip mask, with no per-character splitting.

dart
class TextRevealExample extends StatefulWidget {
  const TextRevealExample({super.key});

  @override
  State<TextRevealExample> createState() => _TextRevealExampleState();
}

class _TextRevealExampleState extends State<TextRevealExample> {
  int _replayCount = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            KeyedSubtree(
              key: ValueKey(_replayCount),
              child: NibTextReveal(
                direction: NibTextRevealDirection.left,
                duration: const Duration(milliseconds: 700),
                child: Container(
                  width: 220,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(
                      color: const Color(0xFF54C5F8).withAlpha(50),
                    ),
                  ),
                  child: const Center(
                    child: Text(
                      'Text Reveal',
                      style: TextStyle(
                        color: Color(0xFFEDF2F7),
                        fontSize: 16,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: () => setState(() => _replayCount++),
              icon: const Icon(
                Icons.replay,
                size: 16,
                color: Color(0xFF718096),
              ),
              label: const Text(
                'Replay',
                style: TextStyle(color: Color(0xFF718096), fontSize: 13),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Shaders

Shaders

GPU-driven fragment-shader effects — a noise-pattern dissolve transition (NibDissolve) and a diagonal shimmer sweep (NibShimmer) for skeleton loaders, both painted directly via RenderObject with no per-frame widget rebuild.

dart
NibDissolve(
  direction: NibDissolveDirection.outward,
  duration: const Duration(milliseconds: 500),
  child: myWidget,
)

Dissolve

Noise-pattern fragment shader transition — a textured, GPU-driven alternative to a plain opacity fade.

dart
class DissolveExample extends StatefulWidget {
  const DissolveExample({super.key});

  @override
  State<DissolveExample> createState() => _DissolveExampleState();
}

class _DissolveExampleState extends State<DissolveExample> {
  bool _dissolved = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibDissolve(
              key: ValueKey(_dissolved),
              direction: _dissolved
                  ? NibDissolveDirection.outward
                  : NibDissolveDirection.inward,
              duration: const Duration(milliseconds: 700),
              noiseScale: 0.02,
              child: Container(
                width: 200,
                height: 100,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: const Center(
                  child: Text(
                    'Dissolve',
                    style: TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: () => setState(() => _dissolved = !_dissolved),
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: Text(
                _dissolved ? 'Dissolve In' : 'Dissolve Out',
                style: const TextStyle(color: Color(0xFF718096), fontSize: 13),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Shimmer

GPU-accelerated diagonal shimmer sweep — a drop-in replacement for CSS-style skeleton loaders.

dart
class ShimmerExample extends StatelessWidget {
  const ShimmerExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: NibShimmer(
          baseColor: const Color(0xFF1C2537),
          shimmerColor: const Color(0xFF54C5F8),
          speed: 1.5,
          angle: -0.3,
          child: Container(
            width: 200,
            height: 100,
            decoration: BoxDecoration(
              color: const Color(0xFF1C2537),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
            ),
            child: const Center(
              child: Text(
                'Loading…',
                style: TextStyle(
                  color: Color(0xFFEDF2F7),
                  fontSize: 16,
                  fontWeight: FontWeight.w600,
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}
Presets

Presets

A library of ready-made animation widgets — NibBounce, NibShake, NibRubberBand, NibFloat, NibPulse, and NibFlip — for common UI motion patterns without hand-writing keyframes.

dart
NibBounce(
  child: const Text('Welcome!'),
)

Bounce + Shake

NibBounce plays a drop-in entrance automatically; NibShake plays a validation-style shake on demand via NibShakeController.

dart
class BounceShakeExample extends StatefulWidget {
  const BounceShakeExample({super.key});

  @override
  State<BounceShakeExample> createState() => _BounceShakeExampleState();
}

class _BounceShakeExampleState extends State<BounceShakeExample> {
  final _shakeController = NibShakeController();

  @override
  void dispose() {
    _shakeController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibBounce(
              child: Container(
                width: 200,
                height: 80,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: const Center(
                  child: Text(
                    'Bounce In',
                    style: TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            NibShake(
              controller: _shakeController,
              child: Container(
                width: 200,
                height: 80,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: const Center(
                  child: Text(
                    'Invalid input',
                    style: TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _shakeController.shake,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Shake', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Rubber Band

An overshoot-and-settle stretch animation, replayable on tap.

dart
class RubberBandExample extends StatefulWidget {
  const RubberBandExample({super.key});

  @override
  State<RubberBandExample> createState() => _RubberBandExampleState();
}

class _RubberBandExampleState extends State<RubberBandExample> {
  int _tick = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibRubberBand(
              key: ValueKey('rubber-band-$_tick'),
              child: Container(
                width: 200,
                height: 100,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: const Center(
                  child: Text(
                    'Stretchy!',
                    style: TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: () => setState(() => _tick++),
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Float + Pulse

NibFloat and NibPulse both loop forever — gentle ambient motion with no user interaction required.

dart
class FloatPulseExample extends StatelessWidget {
  const FloatPulseExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibFloat(
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: const Center(
                  child: Icon(Icons.cloud_outlined, color: Color(0xFF54C5F8), size: 32),
                ),
              ),
            ),
            const SizedBox(width: 24),
            NibPulse(
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: const Center(
                  child: Icon(Icons.favorite, color: Color(0xFF54C5F8), size: 32),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Gestures

Drag & Pinch

Standalone drag, pinch-to-zoom, and swipe-to-dismiss widgets with bounds, axis locking, and spring snap-back — independent of NibMotion's animate/variants system.

dart
NibDraggable(
  bounds: const Rect.fromLTRB(-80, -45, 80, 45),
  snapBack: true,
  child: MyCard(),
)

Draggable

A NibDraggable wrapper with axis-free bounds and spring snap-back to the origin on release.

dart
class DraggableExample extends StatefulWidget {
  const DraggableExample({super.key});

  @override
  State<DraggableExample> createState() => _DraggableExampleState();
}

class _DraggableExampleState extends State<DraggableExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Container(
              width: 240,
              height: 150,
              decoration: BoxDecoration(
                color: const Color(0xFF1C2537).withAlpha(60),
                borderRadius: BorderRadius.circular(16),
                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(30)),
              ),
              child: Center(
                child: NibDraggable(
                  bounds: const Rect.fromLTRB(-80, -45, 80, 45),
                  snapBack: true,
                  spring: NibSpringDescription.gentle,
                  child: Container(
                    width: 150,
                    height: 80,
                    decoration: BoxDecoration(
                      color: const Color(0xFF1C2537),
                      borderRadius: BorderRadius.circular(12),
                      border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                    ),
                    child: const Center(
                      child: Text(
                        'drag me',
                        style: TextStyle(
                          color: Color(0xFFEDF2F7),
                          fontSize: 14,
                          fontWeight: FontWeight.w500,
                        ),
                      ),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            const Text(
              'bounds + snapBack',
              style: TextStyle(color: Color(0xFF718096), fontSize: 13),
            ),
          ],
        ),
      ),
    );
  }
}

Pinch to Zoom

NibPinch clamps the scale between minScale/maxScale and springs back to initialScale on release.

dart
class PinchExample extends StatefulWidget {
  const PinchExample({super.key});

  @override
  State<PinchExample> createState() => _PinchExampleState();
}

class _PinchExampleState extends State<PinchExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibPinch(
              minScale: 0.5,
              maxScale: 4.0,
              snapBackOnRelease: true,
              spring: NibSpringDescription.gentle,
              child: Container(
                width: 160,
                height: 160,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(16),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: const Center(
                  child: Text(
                    'pinch me',
                    style: TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 14,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            const Text(
              'minScale: 0.5  maxScale: 4.0',
              style: TextStyle(color: Color(0xFF718096), fontSize: 13),
            ),
          ],
        ),
      ),
    );
  }
}

Swipe to Dismiss

NibSwipeDismiss on a list-item card — swipe past the threshold to dismiss, or release early to spring back.

dart
class SwipeDismissExample extends StatefulWidget {
  const SwipeDismissExample({super.key});

  @override
  State<SwipeDismissExample> createState() => _SwipeDismissExampleState();
}

class _SwipeDismissExampleState extends State<SwipeDismissExample> {
  bool _dismissed = false;

  void _reset() => setState(() => _dismissed = false);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              width: 260,
              height: 80,
              child: _dismissed
                  ? Center(
                      child: TextButton.icon(
                        onPressed: _reset,
                        icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
                        label: const Text('Reset', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
                      ),
                    )
                  : NibSwipeDismiss(
                      dismissDirection: DismissDirection.horizontal,
                      dismissThreshold: 0.4,
                      spring: NibSpringDescription.gentle,
                      onDismissed: () => setState(() => _dismissed = true),
                      background: Container(
                        decoration: BoxDecoration(
                          color: const Color(0xFFEF4444).withAlpha(60),
                          borderRadius: BorderRadius.circular(12),
                        ),
                        alignment: Alignment.centerRight,
                        padding: const EdgeInsets.symmetric(horizontal: 20),
                        child: const Icon(Icons.delete_outline, color: Color(0xFFEDF2F7)),
                      ),
                      child: Container(
                        decoration: BoxDecoration(
                          color: const Color(0xFF1C2537),
                          borderRadius: BorderRadius.circular(12),
                          border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                        ),
                        child: const Center(
                          child: Text(
                            'swipe to dismiss',
                            style: TextStyle(
                              color: Color(0xFFEDF2F7),
                              fontSize: 14,
                              fontWeight: FontWeight.w500,
                            ),
                          ),
                        ),
                      ),
                    ),
            ),
            const SizedBox(height: 24),
            const Text(
              'dismissThreshold: 0.4',
              style: TextStyle(color: Color(0xFF718096), fontSize: 13),
            ),
          ],
        ),
      ),
    );
  }
}
Timeline

Timeline

Imperative orchestration of multiple NibMotionControllers against a single shared clock, plus a fluent per-controller animate/wait/loop sequence builder.

dart
final timeline = NibTimeline([
  NibTimelineEntry(controller: a, target: const NibAnim(opacity: 1), at: Duration.zero),
  NibTimelineEntry(controller: b, target: const NibAnim(opacity: 1), at: const Duration(milliseconds: 150)),
]);
await timeline.play();

Basic Timeline

Three controllers fired off a single shared clock using `at:` offsets — a declarative playbook for a multi-element entrance.

dart
class TimelineBasicExample extends StatefulWidget {
  const TimelineBasicExample({super.key});

  @override
  State<TimelineBasicExample> createState() => _TimelineBasicExampleState();
}

class _TimelineBasicExampleState extends State<TimelineBasicExample> {
  final _titleController = NibMotionController();
  final _subtitleController = NibMotionController();
  NibTimeline? _timeline;

  void _play() {
    _titleController.set(const NibAnim(opacity: 0, y: 16));
    _subtitleController.set(const NibAnim(opacity: 0, y: 16));

    _timeline?.dispose();
    _timeline = NibTimeline([
      NibTimelineEntry(
        controller: _titleController,
        target: const NibAnim(opacity: 1, y: 0),
        at: Duration.zero,
      ),
      NibTimelineEntry(
        controller: _subtitleController,
        target: const NibAnim(opacity: 1, y: 0),
        at: const Duration(milliseconds: 150),
      ),
    ]);
    _timeline!.play();
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => _play());
  }

  @override
  void dispose() {
    _timeline?.dispose();
    _titleController.dispose();
    _subtitleController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotion(
              initial: const NibAnim(opacity: 0, y: 16),
              controller: _titleController,
              transition: const NibTransition(
                duration: Duration(milliseconds: 300),
              ),
              child: const Text(
                'Title',
                style: TextStyle(
                  color: Color(0xFFEDF2F7),
                  fontSize: 20,
                  fontWeight: FontWeight.w700,
                ),
              ),
            ),
            const SizedBox(height: 8),
            NibMotion(
              initial: const NibAnim(opacity: 0, y: 16),
              controller: _subtitleController,
              transition: const NibTransition(
                duration: Duration(milliseconds: 300),
              ),
              child: const Text(
                'Subtitle follows 150ms later',
                style: TextStyle(color: Color(0xFF94A3B8), fontSize: 14),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _play,
              icon: const Icon(
                Icons.play_arrow,
                size: 16,
                color: Color(0xFF718096),
              ),
              label: const Text(
                'Play',
                style: TextStyle(color: Color(0xFF718096), fontSize: 13),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Animation Sequence

A single controller driven through a chained animate/wait sequence via the fluent NibAnimationSequence builder.

dart
class AnimationSequenceExample extends StatefulWidget {
  const AnimationSequenceExample({super.key});

  @override
  State<AnimationSequenceExample> createState() =>
      _AnimationSequenceExampleState();
}

class _AnimationSequenceExampleState extends State<AnimationSequenceExample> {
  final _controller = NibMotionController();

  void _play() {
    _controller.set(const NibAnim(y: 0, opacity: 1, scale: 1));
    NibAnimationSequence(_controller)
        .animate(
          const NibAnim(y: -20, opacity: 0),
          transition: const NibTransition(
            duration: Duration(milliseconds: 200),
          ),
        )
        .wait(const Duration(milliseconds: 100))
        .animate(
          const NibAnim(y: 0, opacity: 1, scale: 1.1),
          transition: NibTransition.springSnappy,
        )
        .wait(const Duration(milliseconds: 150))
        .animate(
          const NibAnim(scale: 1),
          transition: NibTransition.springGentle,
        )
        .play();
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) => _play());
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            NibMotion(
              controller: _controller,
              child: Container(
                width: 200,
                height: 100,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(
                    color: const Color(0xFF54C5F8).withAlpha(50),
                  ),
                ),
                child: const Center(
                  child: Text(
                    'Sequence',
                    style: TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _play,
              icon: const Icon(
                Icons.play_arrow,
                size: 16,
                color: Color(0xFF718096),
              ),
              label: const Text(
                'Play',
                style: TextStyle(color: Color(0xFF718096), fontSize: 13),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Transform

Path Motion

Moves or draws a widget along a custom Path — for curved motion trajectories or signature/SVG-style line-draw reveals.

dart
NibPathMotion(
  path: myPath,
  duration: const Duration(milliseconds: 1000),
  curve: Curves.easeInOut,
  rotate: true,
  child: MyWidget(),
)

Path Motion

Moves a widget along a custom cubic Bezier Path, rotating to follow the tangent direction.

dart
class PathMotionExample extends StatefulWidget {
  const PathMotionExample({super.key});

  @override
  State<PathMotionExample> createState() => _PathMotionExampleState();
}

class _PathMotionExampleState extends State<PathMotionExample> {
  final _controller = NibPathMotionController();

  Path _buildPath(Size size) {
    final path = Path();
    path.moveTo(0, size.height);
    path.quadraticBezierTo(size.width * 0.5, 0, size.width, size.height);
    return path;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    const trackSize = Size(220, 100);

    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              width: trackSize.width,
              height: trackSize.height,
              child: NibPathMotion(
                controller: _controller,
                path: _buildPath(trackSize),
                duration: const Duration(milliseconds: 1400),
                curve: Curves.easeInOut,
                rotate: true,
                child: const Icon(Icons.flight, color: Color(0xFF54C5F8), size: 28),
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _controller.restart,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}

Draw Path

Animates the progressive reveal of a stroked Path, like a signature or SVG line draw.

dart
class DrawPathExample extends StatefulWidget {
  const DrawPathExample({super.key});

  @override
  State<DrawPathExample> createState() => _DrawPathExampleState();
}

class _DrawPathExampleState extends State<DrawPathExample> {
  final _controller = NibDrawPathController();

  Path _buildPath(Size size) {
    final path = Path();
    path.moveTo(size.width * 0.05, size.height * 0.6);
    path.cubicTo(
      size.width * 0.3,
      0,
      size.width * 0.7,
      size.height,
      size.width * 0.95,
      size.height * 0.4,
    );
    return path;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    const trackSize = Size(220, 100);

    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            SizedBox(
              width: trackSize.width,
              height: trackSize.height,
              child: NibDrawPath(
                controller: _controller,
                path: _buildPath(trackSize),
                strokeWidth: 3,
                color: const Color(0xFF54C5F8),
                duration: const Duration(milliseconds: 1200),
                curve: Curves.easeOut,
              ),
            ),
            const SizedBox(height: 24),
            TextButton.icon(
              onPressed: _controller.restart,
              icon: const Icon(Icons.replay, size: 16, color: Color(0xFF718096)),
              label: const Text('Replay', style: TextStyle(color: Color(0xFF718096), fontSize: 13)),
            ),
          ],
        ),
      ),
    );
  }
}
Scroll

Parallax

Moves a child at a different rate than the surrounding scroll view for a depth effect, and reveals widgets with an animation as they enter the viewport.

dart
NibParallax(
  scrollController: myScrollController,
  factor: 0.5,
  child: const Image(image: AssetImage('background.png')),
)

Parallax Layers

Background layer moves at half the scroll rate via NibParallax's factor, creating a depth effect.

dart
class ParallaxExample extends StatefulWidget {
  const ParallaxExample({super.key});

  @override
  State<ParallaxExample> createState() => _ParallaxExampleState();
}

class _ParallaxExampleState extends State<ParallaxExample> {
  final _scrollController = ScrollController();

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: Stack(
        children: [
          NibParallax(
            scrollController: _scrollController,
            factor: 0.5,
            child: Container(
              height: 800,
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: [
                    const Color(0xFF1C2537),
                    const Color(0xFF54C5F8).withAlpha(40),
                  ],
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                ),
              ),
            ),
          ),
          ListView.builder(
            controller: _scrollController,
            padding: const EdgeInsets.all(24),
            itemCount: 8,
            itemBuilder: (context, index) => Container(
              margin: const EdgeInsets.only(bottom: 16),
              height: 80,
              decoration: BoxDecoration(
                color: const Color(0xFF1C2537),
                borderRadius: BorderRadius.circular(12),
                border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
              ),
              child: Center(
                child: Text(
                  'Foreground ${index + 1}',
                  style: const TextStyle(
                    color: Color(0xFFEDF2F7),
                    fontSize: 14,
                    fontWeight: FontWeight.w500,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Scroll Reveal

Cards fade and slide up as they enter the viewport, using NibScrollReveal.

dart
class ScrollRevealExample extends StatelessWidget {
  const ScrollRevealExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: ListView.builder(
        padding: const EdgeInsets.all(24),
        itemCount: 8,
        itemBuilder: (context, index) {
          return Padding(
            padding: const EdgeInsets.only(bottom: 20),
            child: NibScrollReveal(
              amount: 0.3,
              child: Container(
                height: 90,
                decoration: BoxDecoration(
                  color: const Color(0xFF1C2537),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                ),
                child: Center(
                  child: Text(
                    'Revealed ${index + 1}',
                    style: const TextStyle(
                      color: Color(0xFFEDF2F7),
                      fontSize: 16,
                      fontWeight: FontWeight.w600,
                    ),
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}
Debugger

Debugger

A conditional debug overlay showing a live FPS counter and a scrollable list of every registered MotionValue — zero-overhead no-op passthrough when disabled.

dart
NibMotionDebugger(
  enabled: kDebugMode,
  child: MyApp(),
)

Basic Debugger Overlay

Wrap any subtree in NibMotionDebugger to get a live FPS counter and a scrollable list of every registered MotionValue while developing.

dart
class DebuggerBasicExample extends StatefulWidget {
  const DebuggerBasicExample({super.key});

  @override
  State<DebuggerBasicExample> createState() => _DebuggerBasicExampleState();
}

class _DebuggerBasicExampleState extends State<DebuggerBasicExample> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF0D1117),
      body: NibMotionDebugger(
        enabled: true,
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              NibMotion(
                animate: NibAnim(opacity: _visible ? 1 : 0.3),
                transition: const NibTransition(duration: Duration(milliseconds: 300)),
                child: Container(
                  width: 200,
                  height: 100,
                  decoration: BoxDecoration(
                    color: const Color(0xFF1C2537),
                    borderRadius: BorderRadius.circular(12),
                    border: Border.all(color: const Color(0xFF54C5F8).withAlpha(50)),
                  ),
                  child: const Center(
                    child: Text(
                      'Watch the panel',
                      style: TextStyle(
                        color: Color(0xFFEDF2F7),
                        fontSize: 14,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                ),
              ),
              const SizedBox(height: 24),
              TextButton(
                onPressed: () => setState(() => _visible = !_visible),
                child: Text(
                  _visible ? 'Fade Out' : 'Fade In',
                  style: const TextStyle(color: Color(0xFF718096)),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}