Skip to content
Blog

ShaderMask in Flutter: Painting Gradient Effects on Any Widget

Use ShaderMask to paint gradients, blends, and custom shader effects over any widget — gradient text, fading edges, and tinted icons.

Published on • November 2, 2026

AI Assistant

Gradient text and tinted icons are staples of modern UI, yet Flutter ships no GradientText. ShaderMask fills the gap: it paints the child, then masks the result with a shader you supply (docs.flutter.dev).

How shaderCallback Works

shaderCallback(Rect rect) builds a Shader for the child’s painted bounds — always derive the gradient from rect so the effect tracks resizes; blendMode (default BlendMode.modulate) decides how it composites:

ShaderMask(
  shaderCallback: (rect) => const LinearGradient(
    colors: [Color(0xFF6D28D9), Color(0xFFEC4899)],
  ).createShader(rect),
  child: const Text('REDLINESOFT',
      style: TextStyle(fontSize: 40, fontWeight: FontWeight.bold)),
)

Gradient Text with BlendMode.srcIn

The canonical gradient-text pattern keeps source pixels only where the shader paints alpha — BlendMode.srcIn:

ShaderMask(
  blendMode: BlendMode.srcIn,
  shaderCallback: (b) => const LinearGradient(
    begin: Alignment.topLeft, end: Alignment.bottomRight,
    colors: [Colors.deepPurple, Colors.pinkAccent],
  ).createShader(b),
  child: Text('Pro Plan', style: Theme.of(context).textTheme.headlineMedium),
)

Fading Edges and Gradient Icons

Swap stops for Colors.transparent to fade a child out — wrap a horizontal ListView with the same mask to fade its edges — or tint any monochrome widget (Icon, SvgPicture, a Row of glyphs):

ShaderMask(
  shaderCallback: (b) => const LinearGradient(
    begin: Alignment.centerLeft, end: Alignment.centerRight,
    colors: [Colors.transparent, Colors.black, Colors.transparent],
  ).createShader(b),
  child: const Icon(Icons.star_rounded, size: 56, color: Colors.white),
)

End-to-End: Gradient Badge Widget

class GradientBadge extends StatelessWidget {
  const GradientBadge({super.key, required this.label});
  final String label;
  @override
  Widget build(BuildContext context) => ShaderMask(
        blendMode: BlendMode.srcIn,
        shaderCallback: (b) =>
            const LinearGradient(colors: [Colors.amber, Colors.redAccent]).createShader(b),
        child: Text(label, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.w700)),
      );
}

Gotchas and Best Practices

  • saveLayer cost: ShaderMask renders the child into an offscreen layer before compositing — a real frame-time hit on complex subtrees. Use a Container + BoxDecoration gradient when you only need a background.
  • Dispose your controllers: if an AnimationController drives the shader (shimmer, sweep), dispose it or the layer outlives the widget.
  • No layout impact: the mask only affects painting; hit testing and semantics pass through unchanged.
  • Accessibility: check contrast at both gradient ends — a light stop can silently fail WCAG against the background.

Takeaway: reach for ShaderMask when you must recolor a widget’s pixels, and for BoxDecoration when you only need a gradient background.