← Back to Blog

Flutter ShaderMask Localization: Gradient Effects and Visual Masking for Multilingual Apps

fluttershadermaskgradientseffectslocalizationanimation

Flutter ShaderMask Localization: Gradient Effects and Visual Masking for Multilingual Apps

ShaderMask applies shader-based visual effects to its child, enabling gradient fades, text effects, image masking, and dynamic visual treatments. When combined with localization, ShaderMask creates stunning fade effects, gradient text, and direction-aware visual polish that adapts to different languages and reading directions. This guide covers comprehensive strategies for localizing ShaderMask widgets in Flutter multilingual applications.

Understanding ShaderMask Localization

ShaderMask widgets require localization for:

  • Gradient text: Direction-aware gradient fills for headings
  • Fade effects: Content fading respecting reading direction
  • List overlays: Scroll fade indicators for long content
  • Image effects: Gradient overlays with localized captions
  • Interactive elements: State-based visual feedback
  • Loading states: Shimmer and skeleton loading effects

Basic ShaderMask with Localized Content

Start with a simple gradient text example:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.shaderMaskDemoTitle)),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Semantics(
              label: l10n.gradientTextAccessibility(l10n.heroHeadline),
              child: ShaderMask(
                shaderCallback: (bounds) {
                  return LinearGradient(
                    begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
                    end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
                    colors: [
                      Theme.of(context).colorScheme.primary,
                      Theme.of(context).colorScheme.tertiary,
                    ],
                  ).createShader(bounds);
                },
                child: Text(
                  l10n.heroHeadline,
                  style: Theme.of(context).textTheme.headlineLarge?.copyWith(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ),
            const SizedBox(height: 16),
            Text(
              l10n.heroDescription,
              style: Theme.of(context).textTheme.bodyLarge?.copyWith(
                color: Theme.of(context).colorScheme.onSurfaceVariant,
              ),
            ),
            const SizedBox(height: 32),
            ShaderMask(
              shaderCallback: (bounds) {
                return LinearGradient(
                  begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
                  end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
                  colors: [
                    const Color(0xFF6366F1),
                    const Color(0xFFEC4899),
                  ],
                ).createShader(bounds);
              },
              child: ElevatedButton(
                onPressed: () {},
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(
                    horizontal: 32,
                    vertical: 16,
                  ),
                ),
                child: Text(
                  l10n.getStartedButton,
                  style: const TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

ARB File Structure for ShaderMask

{
  "shaderMaskDemoTitle": "Gradient Effects",
  "@shaderMaskDemoTitle": {
    "description": "Title for shader mask demo page"
  },
  "heroHeadline": "Transform Your Ideas",
  "heroDescription": "Beautiful gradient effects that adapt to any language direction",
  "getStartedButton": "Get Started",
  "gradientTextAccessibility": "Headline: {text}"
}

Fade Edge List

Create a list with fading edges that respect RTL:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocalizedFadeEdgeList extends StatelessWidget {
  final List<String> items;

  const LocalizedFadeEdgeList({
    super.key,
    required this.items,
  });

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.fadeListTitle)),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(
              l10n.fadeListDescription,
              style: Theme.of(context).textTheme.bodyLarge,
            ),
          ),
          Expanded(
            child: ShaderMask(
              shaderCallback: (bounds) {
                return const LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: [
                    Colors.transparent,
                    Colors.white,
                    Colors.white,
                    Colors.transparent,
                  ],
                  stops: [0.0, 0.05, 0.95, 1.0],
                ).createShader(bounds);
              },
              blendMode: BlendMode.dstIn,
              child: ListView.builder(
                padding: const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
                itemCount: items.length,
                itemBuilder: (context, index) {
                  return Card(
                    margin: const EdgeInsets.only(bottom: 12),
                    child: ListTile(
                      leading: CircleAvatar(
                        child: Text('${index + 1}'),
                      ),
                      title: Text(items[index]),
                      subtitle: Text(l10n.listItemSubtitle(index + 1)),
                      trailing: const Icon(Icons.chevron_right),
                      onTap: () {},
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.horizontalFadeListTitle)),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(
              l10n.categoriesLabel,
              style: Theme.of(context).textTheme.titleLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
          SizedBox(
            height: 120,
            child: ShaderMask(
              shaderCallback: (bounds) {
                return LinearGradient(
                  begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
                  end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
                  colors: const [
                    Colors.transparent,
                    Colors.white,
                    Colors.white,
                    Colors.transparent,
                  ],
                  stops: const [0.0, 0.05, 0.95, 1.0],
                ).createShader(bounds);
              },
              blendMode: BlendMode.dstIn,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                padding: const EdgeInsets.symmetric(horizontal: 24),
                itemCount: 10,
                itemBuilder: (context, index) {
                  return Container(
                    width: 100,
                    margin: EdgeInsetsDirectional.only(
                      end: 12,
                    ),
                    child: Card(
                      child: InkWell(
                        onTap: () {},
                        borderRadius: BorderRadius.circular(12),
                        child: Padding(
                          padding: const EdgeInsets.all(12),
                          child: Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: [
                              Icon(
                                _getCategoryIcon(index),
                                size: 32,
                                color: Theme.of(context).colorScheme.primary,
                              ),
                              const SizedBox(height: 8),
                              Text(
                                l10n.categoryName(index + 1),
                                style: Theme.of(context).textTheme.labelMedium,
                                textAlign: TextAlign.center,
                                maxLines: 1,
                                overflow: TextOverflow.ellipsis,
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }

  IconData _getCategoryIcon(int index) {
    const icons = [
      Icons.home,
      Icons.work,
      Icons.school,
      Icons.sports,
      Icons.music_note,
      Icons.movie,
      Icons.restaurant,
      Icons.shopping_bag,
      Icons.travel_explore,
      Icons.health_and_safety,
    ];
    return icons[index % icons.length];
  }
}

Gradient Card Header

Create cards with gradient header effects:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocalizedGradientCard extends StatelessWidget {
  final String title;
  final String subtitle;
  final String content;
  final IconData icon;
  final List<Color> gradientColors;
  final VoidCallback? onTap;

  const LocalizedGradientCard({
    super.key,
    required this.title,
    required this.subtitle,
    required this.content,
    required this.icon,
    this.gradientColors = const [Color(0xFF6366F1), Color(0xFF8B5CF6)],
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Semantics(
      label: l10n.gradientCardAccessibility(title, subtitle),
      button: onTap != null,
      child: Card(
        clipBehavior: Clip.antiAlias,
        child: InkWell(
          onTap: onTap,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              // Gradient header with icon
              ShaderMask(
                shaderCallback: (bounds) {
                  return LinearGradient(
                    begin: isRtl ? Alignment.topRight : Alignment.topLeft,
                    end: isRtl ? Alignment.bottomLeft : Alignment.bottomRight,
                    colors: gradientColors,
                  ).createShader(bounds);
                },
                blendMode: BlendMode.srcATop,
                child: Container(
                  height: 100,
                  color: Colors.white,
                  child: Stack(
                    children: [
                      // Decorative pattern
                      Positioned(
                        right: isRtl ? null : -20,
                        left: isRtl ? -20 : null,
                        top: -20,
                        child: Opacity(
                          opacity: 0.3,
                          child: Icon(
                            icon,
                            size: 120,
                            color: Colors.white,
                          ),
                        ),
                      ),
                      // Title and subtitle
                      Padding(
                        padding: const EdgeInsets.all(16),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          mainAxisAlignment: MainAxisAlignment.end,
                          children: [
                            Text(
                              title,
                              style: Theme.of(context).textTheme.titleLarge?.copyWith(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            const SizedBox(height: 4),
                            Text(
                              subtitle,
                              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                                color: Colors.white.withOpacity(0.9),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              // Content area
              Padding(
                padding: const EdgeInsets.all(16),
                child: Text(
                  content,
                  style: Theme.of(context).textTheme.bodyMedium,
                ),
              ),
              // Action row
              Padding(
                padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    TextButton(
                      onPressed: () {},
                      child: Text(l10n.learnMoreButton),
                    ),
                    const SizedBox(width: 8),
                    ElevatedButton(
                      onPressed: onTap,
                      child: Text(l10n.actionButton),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.gradientCardDemoTitle)),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          LocalizedGradientCard(
            icon: Icons.analytics,
            title: l10n.analyticsCardTitle,
            subtitle: l10n.analyticsCardSubtitle,
            content: l10n.analyticsCardContent,
            gradientColors: const [Color(0xFF6366F1), Color(0xFF8B5CF6)],
            onTap: () {},
          ),
          const SizedBox(height: 16),
          LocalizedGradientCard(
            icon: Icons.security,
            title: l10n.securityCardTitle,
            subtitle: l10n.securityCardSubtitle,
            content: l10n.securityCardContent,
            gradientColors: const [Color(0xFF059669), Color(0xFF34D399)],
            onTap: () {},
          ),
          const SizedBox(height: 16),
          LocalizedGradientCard(
            icon: Icons.speed,
            title: l10n.performanceCardTitle,
            subtitle: l10n.performanceCardSubtitle,
            content: l10n.performanceCardContent,
            gradientColors: const [Color(0xFFDC2626), Color(0xFFF87171)],
            onTap: () {},
          ),
        ],
      ),
    );
  }
}

Shimmer Loading Effect

Create a localized shimmer loading skeleton:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocalizedShimmerLoading extends StatefulWidget {
  final bool isLoading;
  final Widget child;

  const LocalizedShimmerLoading({
    super.key,
    required this.isLoading,
    required this.child,
  });

  @override
  State<LocalizedShimmerLoading> createState() => _LocalizedShimmerLoadingState();
}

class _LocalizedShimmerLoadingState extends State<LocalizedShimmerLoading>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 1500),
    )..repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    if (!widget.isLoading) {
      return widget.child;
    }

    return Semantics(
      label: l10n.shimmerLoadingAccessibility,
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return ShaderMask(
            shaderCallback: (bounds) {
              final position = _controller.value * 2 - 1;
              return LinearGradient(
                begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
                end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
                colors: [
                  Colors.grey.shade300,
                  Colors.grey.shade100,
                  Colors.grey.shade300,
                ],
                stops: [
                  (position - 0.3).clamp(0.0, 1.0),
                  position.clamp(0.0, 1.0),
                  (position + 0.3).clamp(0.0, 1.0),
                ],
              ).createShader(bounds);
            },
            blendMode: BlendMode.srcATop,
            child: child,
          );
        },
        child: widget.child,
      ),
    );
  }
}

class ShimmerPlaceholder extends StatelessWidget {
  final double width;
  final double height;
  final BorderRadius? borderRadius;

  const ShimmerPlaceholder({
    super.key,
    required this.width,
    required this.height,
    this.borderRadius,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      width: width,
      height: height,
      decoration: BoxDecoration(
        color: Colors.grey.shade300,
        borderRadius: borderRadius ?? BorderRadius.circular(8),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        children: [
          const ShimmerPlaceholder(
            width: 60,
            height: 60,
            borderRadius: BorderRadius.all(Radius.circular(30)),
          ),
          const SizedBox(width: 16),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                ShimmerPlaceholder(
                  width: double.infinity,
                  height: 16,
                  borderRadius: BorderRadius.circular(4),
                ),
                const SizedBox(height: 8),
                ShimmerPlaceholder(
                  width: 150,
                  height: 12,
                  borderRadius: BorderRadius.circular(4),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

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

  @override
  State<ShimmerLoadingDemo> createState() => _ShimmerLoadingDemoState();
}

class _ShimmerLoadingDemoState extends State<ShimmerLoadingDemo> {
  bool _isLoading = true;
  List<Map<String, String>> _data = [];

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  Future<void> _loadData() async {
    setState(() => _isLoading = true);

    await Future.delayed(const Duration(seconds: 3));

    setState(() {
      _isLoading = false;
      _data = List.generate(10, (index) => {
        'title': 'Item ${index + 1}',
        'subtitle': 'Description for item ${index + 1}',
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.shimmerDemoTitle),
        actions: [
          IconButton(
            onPressed: _loadData,
            icon: const Icon(Icons.refresh),
            tooltip: l10n.refreshTooltip,
          ),
        ],
      ),
      body: LocalizedShimmerLoading(
        isLoading: _isLoading,
        child: _isLoading
            ? ListView.builder(
                padding: const EdgeInsets.all(16),
                itemCount: 10,
                itemBuilder: (context, index) => const ShimmerListItem(),
              )
            : ListView.builder(
                padding: const EdgeInsets.all(16),
                itemCount: _data.length,
                itemBuilder: (context, index) {
                  final item = _data[index];
                  return ListTile(
                    leading: CircleAvatar(child: Text('${index + 1}')),
                    title: Text(item['title']!),
                    subtitle: Text(item['subtitle']!),
                  );
                },
              ),
      ),
    );
  }
}

Text Reveal Animation

Create a gradient text reveal effect:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocalizedTextReveal extends StatefulWidget {
  final String text;
  final TextStyle? style;

  const LocalizedTextReveal({
    super.key,
    required this.text,
    this.style,
  });

  @override
  State<LocalizedTextReveal> createState() => _LocalizedTextRevealState();
}

class _LocalizedTextRevealState extends State<LocalizedTextReveal>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..forward();
  }

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Semantics(
      label: l10n.textRevealAccessibility(widget.text),
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return ShaderMask(
            shaderCallback: (bounds) {
              final progress = _controller.value;
              return LinearGradient(
                begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
                end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
                colors: [
                  Theme.of(context).colorScheme.onSurface,
                  Theme.of(context).colorScheme.onSurface,
                  Colors.transparent,
                ],
                stops: [
                  0.0,
                  progress,
                  progress + 0.01,
                ],
              ).createShader(bounds);
            },
            blendMode: BlendMode.srcIn,
            child: child,
          );
        },
        child: Text(
          widget.text,
          style: widget.style ?? Theme.of(context).textTheme.headlineMedium,
        ),
      ),
    );
  }
}

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

  @override
  State<TextRevealDemo> createState() => _TextRevealDemoState();
}

class _TextRevealDemoState extends State<TextRevealDemo> {
  int _key = 0;

  void _replay() {
    setState(() => _key++);
  }

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.textRevealDemoTitle)),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.textRevealInstructions,
              style: Theme.of(context).textTheme.bodyLarge,
            ),
            const SizedBox(height: 48),
            LocalizedTextReveal(
              key: ValueKey(_key),
              text: l10n.revealHeadline,
              style: Theme.of(context).textTheme.headlineLarge?.copyWith(
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 24),
            LocalizedTextReveal(
              key: ValueKey(_key + 100),
              text: l10n.revealSubheadline,
              style: Theme.of(context).textTheme.titleMedium?.copyWith(
                color: Theme.of(context).colorScheme.onSurfaceVariant,
              ),
            ),
            const Spacer(),
            Center(
              child: ElevatedButton.icon(
                onPressed: _replay,
                icon: const Icon(Icons.replay),
                label: Text(l10n.replayButton),
              ),
            ),
            const SizedBox(height: 32),
          ],
        ),
      ),
    );
  }
}

Radial Gradient Mask

Create a spotlight or vignette effect:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

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

  @override
  State<LocalizedRadialMask> createState() => _LocalizedRadialMaskState();
}

class _LocalizedRadialMaskState extends State<LocalizedRadialMask> {
  Offset _focusPoint = const Offset(0.5, 0.5);

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      appBar: AppBar(title: Text(l10n.radialMaskTitle)),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text(
              l10n.radialMaskInstructions,
              style: Theme.of(context).textTheme.bodyLarge,
            ),
          ),
          Expanded(
            child: GestureDetector(
              onPanUpdate: (details) {
                final box = context.findRenderObject() as RenderBox;
                final localPosition = details.localPosition;
                setState(() {
                  _focusPoint = Offset(
                    (localPosition.dx / box.size.width).clamp(0.0, 1.0),
                    (localPosition.dy / box.size.height).clamp(0.0, 1.0),
                  );
                });
              },
              onTapDown: (details) {
                final box = context.findRenderObject() as RenderBox;
                setState(() {
                  _focusPoint = Offset(
                    (details.localPosition.dx / box.size.width).clamp(0.0, 1.0),
                    (details.localPosition.dy / box.size.height).clamp(0.0, 1.0),
                  );
                });
              },
              child: Semantics(
                label: l10n.radialMaskAccessibility,
                child: Stack(
                  fit: StackFit.expand,
                  children: [
                    // Background image
                    Image.network(
                      'https://picsum.photos/800/1200',
                      fit: BoxFit.cover,
                    ),
                    // Vignette overlay
                    ShaderMask(
                      shaderCallback: (bounds) {
                        return RadialGradient(
                          center: Alignment(
                            _focusPoint.dx * 2 - 1,
                            _focusPoint.dy * 2 - 1,
                          ),
                          radius: 0.8,
                          colors: const [
                            Colors.transparent,
                            Colors.black,
                          ],
                          stops: const [0.4, 1.0],
                        ).createShader(bounds);
                      },
                      blendMode: BlendMode.srcOver,
                      child: Container(
                        color: Colors.black.withOpacity(0.7),
                      ),
                    ),
                    // Info overlay
                    Positioned(
                      bottom: 0,
                      left: 0,
                      right: 0,
                      child: Container(
                        padding: const EdgeInsets.all(24),
                        decoration: BoxDecoration(
                          gradient: LinearGradient(
                            begin: Alignment.bottomCenter,
                            end: Alignment.topCenter,
                            colors: [
                              Colors.black.withOpacity(0.8),
                              Colors.transparent,
                            ],
                          ),
                        ),
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              l10n.spotlightImageTitle,
                              style: Theme.of(context).textTheme.headlineSmall?.copyWith(
                                color: Colors.white,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                            const SizedBox(height: 8),
                            Text(
                              l10n.spotlightImageDescription,
                              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                                color: Colors.white.withOpacity(0.8),
                              ),
                            ),
                          ],
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Multi-Color Gradient Text

Create animated multi-color gradient text:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class LocalizedAnimatedGradientText extends StatefulWidget {
  final String text;
  final TextStyle? style;

  const LocalizedAnimatedGradientText({
    super.key,
    required this.text,
    this.style,
  });

  @override
  State<LocalizedAnimatedGradientText> createState() =>
      _LocalizedAnimatedGradientTextState();
}

class _LocalizedAnimatedGradientTextState extends State<LocalizedAnimatedGradientText>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 3),
    )..repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;
    final isRtl = Directionality.of(context) == TextDirection.rtl;

    return Semantics(
      label: l10n.animatedGradientAccessibility(widget.text),
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, child) {
          return ShaderMask(
            shaderCallback: (bounds) {
              final offset = _controller.value * 2;
              return LinearGradient(
                begin: isRtl ? Alignment.centerRight : Alignment.centerLeft,
                end: isRtl ? Alignment.centerLeft : Alignment.centerRight,
                colors: const [
                  Color(0xFF6366F1),
                  Color(0xFFEC4899),
                  Color(0xFF8B5CF6),
                  Color(0xFF06B6D4),
                  Color(0xFF6366F1),
                ],
                stops: [
                  (offset - 0.5).clamp(0.0, 1.0),
                  (offset - 0.25).clamp(0.0, 1.0),
                  offset.clamp(0.0, 1.0),
                  (offset + 0.25).clamp(0.0, 1.0),
                  (offset + 0.5).clamp(0.0, 1.0),
                ],
              ).createShader(bounds);
            },
            child: child,
          );
        },
        child: Text(
          widget.text,
          style: (widget.style ?? Theme.of(context).textTheme.headlineLarge)?.copyWith(
            color: Colors.white,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context)!;

    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        title: Text(l10n.animatedGradientTitle),
        backgroundColor: Colors.transparent,
        foregroundColor: Colors.white,
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              LocalizedAnimatedGradientText(
                text: l10n.brandName,
                style: Theme.of(context).textTheme.displayMedium,
              ),
              const SizedBox(height: 16),
              Text(
                l10n.brandTagline,
                style: Theme.of(context).textTheme.titleMedium?.copyWith(
                  color: Colors.white.withOpacity(0.7),
                ),
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Complete ARB File for ShaderMask

{
  "@@locale": "en",

  "shaderMaskDemoTitle": "Gradient Effects",
  "heroHeadline": "Transform Your Ideas",
  "heroDescription": "Beautiful gradient effects that adapt to any language direction",
  "getStartedButton": "Get Started",
  "gradientTextAccessibility": "Headline: {text}",
  "@gradientTextAccessibility": {
    "placeholders": {
      "text": {"type": "String"}
    }
  },

  "fadeListTitle": "Fade Edge List",
  "fadeListDescription": "Scroll to see the fade effect at the edges",
  "listItemSubtitle": "Additional details for item {number}",
  "@listItemSubtitle": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },
  "horizontalFadeListTitle": "Category Browser",
  "categoriesLabel": "Categories",
  "categoryName": "Category {number}",
  "@categoryName": {
    "placeholders": {
      "number": {"type": "int"}
    }
  },

  "gradientCardAccessibility": "{title}: {subtitle}",
  "@gradientCardAccessibility": {
    "placeholders": {
      "title": {"type": "String"},
      "subtitle": {"type": "String"}
    }
  },
  "gradientCardDemoTitle": "Feature Cards",
  "analyticsCardTitle": "Analytics",
  "analyticsCardSubtitle": "Real-time insights",
  "analyticsCardContent": "Track your performance metrics with detailed analytics and customizable dashboards.",
  "securityCardTitle": "Security",
  "securityCardSubtitle": "Enterprise-grade protection",
  "securityCardContent": "Keep your data safe with advanced encryption and multi-factor authentication.",
  "performanceCardTitle": "Performance",
  "performanceCardSubtitle": "Lightning fast",
  "performanceCardContent": "Optimized for speed with intelligent caching and lazy loading.",
  "learnMoreButton": "Learn More",
  "actionButton": "Get Started",

  "shimmerLoadingAccessibility": "Content is loading, please wait",
  "shimmerDemoTitle": "Loading Skeleton",
  "refreshTooltip": "Refresh data",

  "textRevealAccessibility": "Revealing text: {text}",
  "@textRevealAccessibility": {
    "placeholders": {
      "text": {"type": "String"}
    }
  },
  "textRevealDemoTitle": "Text Reveal Effect",
  "textRevealInstructions": "Watch the text reveal from the reading direction start",
  "revealHeadline": "Welcome to the Future",
  "revealSubheadline": "Where innovation meets elegance in every detail",
  "replayButton": "Replay Animation",

  "radialMaskTitle": "Spotlight Effect",
  "radialMaskInstructions": "Tap or drag to move the spotlight focus",
  "radialMaskAccessibility": "Interactive spotlight effect on image",
  "spotlightImageTitle": "Featured Destination",
  "spotlightImageDescription": "Explore beautiful places with our spotlight view mode",

  "animatedGradientAccessibility": "Animated gradient text: {text}",
  "@animatedGradientAccessibility": {
    "placeholders": {
      "text": {"type": "String"}
    }
  },
  "animatedGradientTitle": "Brand Showcase",
  "brandName": "FLUTTER",
  "brandTagline": "Build beautiful, natively compiled applications"
}

Best Practices Summary

  1. Respect text direction: Use isRtl to flip gradients for RTL languages
  2. Use appropriate blend modes: BlendMode.srcIn for text, BlendMode.dstIn for fades
  3. Provide semantic labels: Describe gradient effects for accessibility
  4. Animate smoothly: Use AnimationController for dynamic gradient effects
  5. Consider performance: Complex shaders can be expensive on older devices
  6. Test across languages: Verify gradients look correct with different text lengths
  7. Combine with clipping: Use ClipRect to constrain shader effects
  8. Handle edge cases: Ensure shaders work at all container sizes
  9. Use meaningful colors: Choose gradient colors that convey meaning
  10. Maintain readability: Ensure text remains legible with gradient effects

Conclusion

ShaderMask enables powerful visual effects like gradient text, fade edges, shimmer loading, and spotlight effects that can adapt to any language direction. By creating direction-aware shaders and providing proper accessibility labels, you can build visually stunning applications that work seamlessly for users worldwide. The key is balancing visual appeal with performance and accessibility, ensuring that shader effects enhance rather than distract from the content.

Remember to test your shader effects across different devices and screen sizes, and always consider the cultural implications of color choices in your gradient designs for international audiences.