# Shimmer 4 replaces Flutter's material library, so the highlight stops being a colour trick

> flutter_shimmer paints a moving highlight by masking the child with a ShaderMaskLayer using BlendMode.srcIn, which means placeholder colours are replaced rather than tinted. Version 4.0 also moved off package:flutter/material.dart onto the standalone material_ui package, which is the migration most projects will hit.

**hnvn/flutter_shimmer** — A package provides an easy way to add shimmer effect in Flutter project

- Repository: https://github.com/hnvn/flutter_shimmer
- Stars: 1,860 · Forks: 204
- Language: Dart
- License: BSD-3-Clause
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/hnvn-flutter-shimmer

## The 4.0 dependency change is the upgrade decision

The dependency block is short:

```yaml
dependencies:
  shimmer: ^4.0.0
  material_ui: ^1.0.1
```

The second entry is the change. Shimmer 4.0 uses Flutter's standalone material_ui package, and the README states the requirement as Flutter 3.44 or newer. Apps that still import package:flutter/material.dart are told to wrap those subtrees in MaterialUiCompatibilityBridge, and the import becomes:

```dart
import 'package:material_ui/material_ui.dart';
import 'package:shimmer/shimmer.dart';
```

So this is not a drop in version bump. Upgrading means changing an import across the file that uses Shimmer and, in a codebase still on Flutter's own material library, wrapping the affected subtrees.

The reason a package would move to a standalone UI library is worth noting even if you stay on 3.x: it decouples the widget from Flutter's bundled material surface. The cost is that consumers on an older Flutter cannot take the new major version at all.

## fromColors is the path most projects take

Shimmer.fromColors is described as the usual constructor, and its argument is not a colour for the placeholder. It is a colour for the gradient, and the child is built from solid shapes:

```dart
Shimmer.fromColors(
  baseColor: Colors.grey.shade300,
  highlightColor: Colors.grey.shade100,
  child: Column(
    children: [
      Container(height: 200, color: Colors.white),
      const SizedBox(height: 16),
      Container(height: 12, color: Colors.white),
      const SizedBox(height: 8),
      Container(height: 12, width: 200, color: Colors.white),
    ],
  ),
);
```

The README is explicit about the mechanism: the gradient replaces those colors, and transparent pixels stay transparent. The white Containers in the example are therefore irrelevant, and would work with any colour, because the mask decides what is drawn.

That is why the dark theme version differs only in its two arguments:

```dart
Shimmer.fromColors(
  baseColor: Colors.grey.shade800,
  highlightColor: Colors.grey.shade600,
  child: placeholder,
);
```

Note the shades. The base is darker than the highlight in both cases, which is what makes the sweep visible as a moving lighter band.

## The default constructor is for when the gradient is the point

Shimmer(...) takes a gradient directly, and the README says to use it when you need a RadialGradient, a SweepGradient, or a LinearGradient that follows the theme:

```dart
Shimmer(
  gradient: LinearGradient(
    colors: [
      Theme.of(context).colorScheme.surfaceContainerHighest,
      Theme.of(context).colorScheme.surface,
      Theme.of(context).colorScheme.surfaceContainerHighest,
    ],
    stops: const [0.35, 0.5, 0.65],
  ),
  child: placeholder,
);
```

The stops matter here. A gradient that goes high, low, high with stops at 0.35, 0.5 and 0.65 produces a narrow band with soft shoulders, which is what makes a theme derived gradient look deliberate rather than like a colour change halfway across the widget.

The parameter table shows where the two constructors diverge. gradient is required on Shimmer(...) and fromColors builds it for you from baseColor and highlightColor. child is required either way, and is described as the opaque area the highlight is blended onto.

## Direction, period, loop and enabled are the four knobs

The behavioural parameters are documented in a table with defaults, and two of them are the ones people get wrong.

```dart
Shimmer.fromColors(
  direction: ShimmerDirection.rtl,
  period: const Duration(milliseconds: 1200),
  loop: 0, // 0 = forever
  enabled: isLoading,
  baseColor: Colors.grey.shade300,
  highlightColor: Colors.grey.shade100,
  child: placeholder,
);
```

direction defaults to ltr and accepts ltr, rtl, ttb and btt, so the sweep can be horizontal or vertical. period defaults to 1500ms and is the duration of one pass, which means a faster period makes the band move quicker for the same gradient width.

loop defaults to 0, and 0 repeats forever. Any other number is a count of passes before stopping. enabled defaults to true and setting it false pauses the animation, which is what the performance section recommends when loading finishes so the ticker stops.

enabled is tied to your loading state directly in the example above, which is the pattern to copy: pass isLoading rather than toggling the widget's visibility, because an invisible widget and a paused animation cost different amounts.

## How the effect is actually drawn, and why your child matters

The how it works section is short enough to quote in full because it explains every limitation.

Shimmer drives an AnimationController and paints a ShaderMaskLayer over the child with BlendMode.srcIn. The highlight rectangle is three times the child size, so the band can travel fully across the widget.

That single sentence explains the child rules in the performance section. srcIn means the shader result replaces the child pixels where the mask is opaque, and transparent pixels of the child stay transparent. Anything drawn with colour inside the child is therefore thrown away and replaced.

The README's guidance follows directly: wrap a list of placeholders in one Shimmer rather than one Shimmer per row, keep the child simple and static, and expect fancy widgets to look wrong. Images, text with decoration and elevation are named as examples, because each of those paints in a way the mask does not reproduce.

One Shimmer per row is the specific performance mistake being called out, and it is also the obvious thing to write when you have a ListView.builder of skeleton rows.

Further detail on internals is pointed at docs/overview.md and docs/optimization.md in the repository.

## Where a shimmer is the wrong choice

The first wrong case is fast responses. A highlight that sweeps on a request that returns almost immediately can be unnecessary animation, and the period parameter does not help because the issue is duration rather than speed. The answer is to not show a skeleton for operations the user expects to be quick, which the enabled parameter makes easy once the state exists.

The second is a layout you cannot predict. Shimmer replaces colours, so it cannot communicate the shape of real content: an image, a name, a price. If the content is highly variable, a spinner or a plain progress indicator communicates less and more honestly.

The third is accessibility and reduced motion preferences. The README documents no option for honouring a reduced motion setting, so the loop runs forever by default. The enabled flag gives you the mechanism to stop it, but the decision of when to is yours.

The fourth is the version ceiling. If your app is on a Flutter older than 3.44, 4.0 is not available to you, and staying on 3.x means the compatibility bridge question does not arise but neither does the standalone UI library.

For a demo, the example/ app contains a loading list and a slide to unlock highlight, run from the repository root with cd example && flutter run.

## Conclusion

Use Shimmer for skeleton screens where the placeholder shape already matches the real layout, and where you can wrap a whole list in a single Shimmer rather than one per row. Do not use it on children with images, styled text or elevation, because the shader replaces their colours and the README says they often look wrong as a result. Verify first that you are on Flutter 3.44 or newer before upgrading to 4.0, since the package now imports material_ui instead of package:flutter/material.dart and older code needs MaterialUiCompatibilityBridge around the affected subtrees.

## FAQ

### How do I add Shimmer to a Flutter project?

Add shimmer: ^4.0.0 and material_ui: ^1.0.1 to dependencies. Shimmer 4.0 uses the standalone material_ui package and needs Flutter 3.44 or newer, and imports become import 'package:material_ui/material_ui.dart' alongside import 'package:shimmer/shimmer.dart'.

### How do I stop the Shimmer animation when loading finishes?

Set enabled to false, for example enabled: isLoading. The README notes that toggling enabled pauses the animation so the ticker stops, rather than removing the widget.

### Why does my Shimmer child look wrong with images or styled text?

Shimmer paints a ShaderMaskLayer with BlendMode.srcIn over the child, so the shader replaces the child's colours rather than tinting them. The README says to keep the child simple and static, since images, decorated text and elevation often look wrong for that reason.

### What are the default values of the Shimmer parameters?

direction defaults to ltr and accepts ltr, rtl, ttb and btt, period defaults to 1500ms for one pass, loop defaults to 0 which repeats forever, and enabled defaults to true so the animation runs.

### Should each skeleton row have its own Shimmer widget?

No. The README's performance guidance is to wrap a list of placeholders in one Shimmer rather than one Shimmer per row, and to keep the child simple and static.

## Sources

- [hnvn/flutter_shimmer on GitHub](https://github.com/hnvn/flutter_shimmer)
- [Issues](https://github.com/hnvn/flutter_shimmer/issues)
- [License: BSD-3-Clause](https://github.com/hnvn/flutter_shimmer/blob/master/LICENSE)
- [README](https://github.com/hnvn/flutter_shimmer/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hnvn-flutter-shimmer
