Flutter FadeTransition Localization: Elegant Fading Effects for Multilingual Apps
FadeTransition provides explicit control over opacity animations using an Animation controller. Proper localization ensures that fading elements, visibility transitions, and content reveals work seamlessly across languages with appropriate accessibility feedback. This guide covers comprehensive strategies for localizing FadeTransition widgets in Flutter.
Understanding FadeTransition Localization
FadeTransition widgets require localization for:
- Content visibility: Fading text and UI elements with proper announcements
- Loading states: Skeleton loaders and content reveals
- Modal overlays: Backdrop fading with accessibility support
- Image galleries: Smooth image transitions with alt text
- Notification badges: Attention indicators with screen reader feedback
- Tutorial highlights: Guided tours with localized instructions
Basic FadeTransition with Accessibility
Start with a simple content reveal with proper accessibility:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedContentReveal extends StatefulWidget {
const LocalizedContentReveal({super.key});
@override
State<LocalizedContentReveal> createState() => _LocalizedContentRevealState();
}
class _LocalizedContentRevealState extends State<LocalizedContentReveal>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
bool _isContentVisible = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
_fadeAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
}
void _toggleContent() {
setState(() {
_isContentVisible = !_isContentVisible;
if (_isContentVisible) {
_controller.forward();
} else {
_controller.reverse();
}
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.contentRevealTitle)),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.spoilerAlertTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 8),
Text(l10n.spoilerWarningMessage),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _toggleContent,
icon: Icon(_isContentVisible
? Icons.visibility_off
: Icons.visibility),
label: Text(_isContentVisible
? l10n.hideContentButton
: l10n.revealContentButton),
),
],
),
),
),
const SizedBox(height: 24),
FadeTransition(
opacity: _fadeAnimation,
child: Semantics(
hidden: !_isContentVisible,
liveRegion: true,
child: Card(
color: Theme.of(context).colorScheme.secondaryContainer,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.auto_awesome,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(width: 8),
Text(
l10n.revealedContentHeader,
style: Theme.of(context).textTheme.titleMedium,
),
],
),
const SizedBox(height: 12),
Text(l10n.revealedContentText),
],
),
),
),
),
),
],
),
),
);
}
}
ARB File Structure for FadeTransition
{
"contentRevealTitle": "Content Reveal",
"@contentRevealTitle": {
"description": "Title for content reveal screen"
},
"spoilerAlertTitle": "Spoiler Alert!",
"@spoilerAlertTitle": {
"description": "Warning title for hidden content"
},
"spoilerWarningMessage": "The following content contains spoilers. Click to reveal at your own risk.",
"@spoilerWarningMessage": {
"description": "Warning message before revealing content"
},
"hideContentButton": "Hide Content",
"@hideContentButton": {
"description": "Button to hide revealed content"
},
"revealContentButton": "Reveal Content",
"@revealContentButton": {
"description": "Button to reveal hidden content"
},
"revealedContentHeader": "Secret Revealed",
"@revealedContentHeader": {
"description": "Header for revealed content"
},
"revealedContentText": "The treasure is hidden beneath the old oak tree in the northern corner of the garden. Look for the stone marked with an X.",
"@revealedContentText": {
"description": "The revealed secret content"
}
}
Skeleton Loading with Fade
Create a skeleton loader that fades into content:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedSkeletonLoader extends StatefulWidget {
const LocalizedSkeletonLoader({super.key});
@override
State<LocalizedSkeletonLoader> createState() => _LocalizedSkeletonLoaderState();
}
class _LocalizedSkeletonLoaderState extends State<LocalizedSkeletonLoader>
with TickerProviderStateMixin {
late AnimationController _shimmerController;
late AnimationController _fadeController;
late Animation<double> _fadeAnimation;
bool _isLoading = true;
@override
void initState() {
super.initState();
_shimmerController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
)..repeat();
_fadeController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_fadeAnimation = CurvedAnimation(
parent: _fadeController,
curve: Curves.easeOut,
);
_loadContent();
}
Future<void> _loadContent() async {
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
setState(() => _isLoading = false);
_shimmerController.stop();
_fadeController.forward();
}
}
Future<void> _refresh() async {
setState(() => _isLoading = true);
_fadeController.reset();
_shimmerController.repeat();
await _loadContent();
}
@override
void dispose() {
_shimmerController.dispose();
_fadeController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.articleListTitle),
actions: [
IconButton(
onPressed: _refresh,
icon: const Icon(Icons.refresh),
tooltip: l10n.refreshTooltip,
),
],
),
body: Semantics(
liveRegion: true,
label: _isLoading
? l10n.loadingContentAccessibility
: l10n.contentLoadedAccessibility,
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 5,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 16),
child: _isLoading
? _SkeletonCard(animation: _shimmerController)
: FadeTransition(
opacity: _fadeAnimation,
child: _ArticleCard(
title: l10n.sampleArticleTitle(index + 1),
description: l10n.sampleArticleDescription,
date: l10n.articleDate(index + 1),
),
),
);
},
),
),
);
}
}
class _SkeletonCard extends StatelessWidget {
final AnimationController animation;
const _SkeletonCard({required this.animation});
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_SkeletonLine(width: 200, animation: animation),
const SizedBox(height: 12),
_SkeletonLine(width: double.infinity, animation: animation),
const SizedBox(height: 8),
_SkeletonLine(width: 250, animation: animation),
const SizedBox(height: 12),
_SkeletonLine(width: 100, animation: animation),
],
),
),
);
},
);
}
}
class _SkeletonLine extends StatelessWidget {
final double width;
final AnimationController animation;
const _SkeletonLine({required this.width, required this.animation});
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: 16,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.surfaceVariant,
Theme.of(context).colorScheme.surface,
Theme.of(context).colorScheme.surfaceVariant,
],
stops: [
animation.value - 0.3,
animation.value,
animation.value + 0.3,
].map((s) => s.clamp(0.0, 1.0)).toList(),
),
),
);
}
}
class _ArticleCard extends StatelessWidget {
final String title;
final String description;
final String date;
const _ArticleCard({
required this.title,
required this.description,
required this.date,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
description,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
Text(
date,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.outline,
),
),
],
),
),
);
}
}
Image Gallery with Fade Transitions
Create an image gallery with smooth fading:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedImageGallery extends StatefulWidget {
const LocalizedImageGallery({super.key});
@override
State<LocalizedImageGallery> createState() => _LocalizedImageGalleryState();
}
class _LocalizedImageGalleryState extends State<LocalizedImageGallery>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
int _currentIndex = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
_fadeAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
_controller.value = 1.0;
}
Future<void> _changeImage(int newIndex) async {
if (newIndex == _currentIndex) return;
await _controller.reverse();
setState(() => _currentIndex = newIndex);
await _controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final images = [
(l10n.galleryImage1Title, l10n.galleryImage1Description, Colors.blue),
(l10n.galleryImage2Title, l10n.galleryImage2Description, Colors.green),
(l10n.galleryImage3Title, l10n.galleryImage3Description, Colors.orange),
(l10n.galleryImage4Title, l10n.galleryImage4Description, Colors.purple),
];
return Scaffold(
appBar: AppBar(title: Text(l10n.galleryTitle)),
body: Column(
children: [
Expanded(
flex: 3,
child: FadeTransition(
opacity: _fadeAnimation,
child: Semantics(
image: true,
label: l10n.galleryImageAccessibility(
_currentIndex + 1,
images[_currentIndex].$1,
),
child: Container(
margin: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: images[_currentIndex].$3.withOpacity(0.3),
borderRadius: BorderRadius.circular(16),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.image,
size: 80,
color: images[_currentIndex].$3,
),
const SizedBox(height: 16),
Text(
images[_currentIndex].$1,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(
images[_currentIndex].$2,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
],
),
),
),
),
),
),
Text(
l10n.imageCounter(_currentIndex + 1, images.length),
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 16),
Expanded(
flex: 1,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: images.length,
itemBuilder: (context, index) {
final isSelected = index == _currentIndex;
return Semantics(
button: true,
selected: isSelected,
label: l10n.selectImageAccessibility(index + 1),
child: GestureDetector(
onTap: () => _changeImage(index),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 8),
width: 80,
decoration: BoxDecoration(
color: images[index].$3.withOpacity(
isSelected ? 0.5 : 0.2,
),
borderRadius: BorderRadius.circular(12),
border: isSelected
? Border.all(
color: Theme.of(context).colorScheme.primary,
width: 3,
)
: null,
),
child: Center(
child: Text(
'${index + 1}',
style: Theme.of(context).textTheme.titleLarge,
),
),
),
),
);
},
),
),
),
const SizedBox(height: 24),
],
),
);
}
}
Modal Dialog with Backdrop Fade
Create a modal dialog with fading backdrop:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedFadeModal extends StatefulWidget {
const LocalizedFadeModal({super.key});
@override
State<LocalizedFadeModal> createState() => _LocalizedFadeModalState();
}
class _LocalizedFadeModalState extends State<LocalizedFadeModal>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _backdropFade;
late Animation<double> _dialogFade;
late Animation<double> _dialogScale;
bool _isModalVisible = false;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 250),
);
_backdropFade = Tween<double>(
begin: 0.0,
end: 0.6,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.0, 0.5),
));
_dialogFade = CurvedAnimation(
parent: _controller,
curve: const Interval(0.2, 1.0, curve: Curves.easeOut),
);
_dialogScale = Tween<double>(
begin: 0.8,
end: 1.0,
).animate(CurvedAnimation(
parent: _controller,
curve: const Interval(0.2, 1.0, curve: Curves.easeOutBack),
));
}
void _showModal() {
setState(() => _isModalVisible = true);
_controller.forward();
}
void _hideModal() {
_controller.reverse().then((_) {
setState(() => _isModalVisible = false);
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.modalDemoTitle)),
body: Stack(
children: [
Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.info_outline,
size: 64,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 24),
Text(
l10n.modalDemoDescription,
style: Theme.of(context).textTheme.bodyLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: _showModal,
icon: const Icon(Icons.open_in_new),
label: Text(l10n.showModalButton),
),
],
),
),
if (_isModalVisible) ...[
// Backdrop
AnimatedBuilder(
animation: _backdropFade,
builder: (context, child) {
return GestureDetector(
onTap: _hideModal,
child: Container(
color: Colors.black.withOpacity(_backdropFade.value),
),
);
},
),
// Modal dialog
Center(
child: FadeTransition(
opacity: _dialogFade,
child: ScaleTransition(
scale: _dialogScale,
child: Semantics(
container: true,
label: l10n.modalDialogAccessibility,
child: Material(
borderRadius: BorderRadius.circular(24),
child: Container(
width: 320,
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer,
shape: BoxShape.circle,
),
child: Icon(
Icons.check,
size: 48,
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
),
),
const SizedBox(height: 24),
Text(
l10n.modalTitle,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 12),
Text(
l10n.modalMessage,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _hideModal,
child: Text(l10n.cancelButton),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: _hideModal,
child: Text(l10n.confirmButton),
),
),
],
),
],
),
),
),
),
),
),
),
],
],
),
);
}
}
Onboarding with Fade Transitions
Create an onboarding flow with fading slides:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedFadeOnboarding extends StatefulWidget {
const LocalizedFadeOnboarding({super.key});
@override
State<LocalizedFadeOnboarding> createState() => _LocalizedFadeOnboardingState();
}
class _LocalizedFadeOnboardingState extends State<LocalizedFadeOnboarding>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
int _currentStep = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_fadeAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
_controller.value = 1.0;
}
Future<void> _goToStep(int step) async {
if (step == _currentStep || step < 0) return;
await _controller.reverse();
setState(() => _currentStep = step);
await _controller.forward();
}
void _nextStep() {
if (_currentStep < 2) {
_goToStep(_currentStep + 1);
}
}
void _previousStep() {
if (_currentStep > 0) {
_goToStep(_currentStep - 1);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final steps = [
(
l10n.onboardingStep1Title,
l10n.onboardingStep1Description,
Icons.explore,
Colors.blue,
),
(
l10n.onboardingStep2Title,
l10n.onboardingStep2Description,
Icons.favorite,
Colors.pink,
),
(
l10n.onboardingStep3Title,
l10n.onboardingStep3Description,
Icons.rocket_launch,
Colors.orange,
),
];
final currentStepData = steps[_currentStep];
return Scaffold(
body: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Progress indicators
Row(
children: List.generate(steps.length, (index) {
return Semantics(
label: l10n.stepIndicatorAccessibility(
index + 1,
steps.length,
index == _currentStep,
),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: index == _currentStep ? 32 : 8,
height: 8,
decoration: BoxDecoration(
color: index == _currentStep
? currentStepData.$4
: Theme.of(context).colorScheme.outline,
borderRadius: BorderRadius.circular(4),
),
),
);
}),
),
TextButton(
onPressed: () {},
child: Text(l10n.skipButton),
),
],
),
),
Expanded(
child: FadeTransition(
opacity: _fadeAnimation,
child: Semantics(
liveRegion: true,
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: currentStepData.$4.withOpacity(0.2),
shape: BoxShape.circle,
),
child: Icon(
currentStepData.$3,
size: 80,
color: currentStepData.$4,
),
),
const SizedBox(height: 48),
Text(
currentStepData.$1,
style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
currentStepData.$2,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(24),
child: Row(
children: [
if (_currentStep > 0)
Expanded(
child: OutlinedButton(
onPressed: _previousStep,
child: Text(l10n.previousButton),
),
),
if (_currentStep > 0) const SizedBox(width: 16),
Expanded(
flex: _currentStep == 0 ? 1 : 1,
child: ElevatedButton(
onPressed: _currentStep < steps.length - 1
? _nextStep
: () {},
child: Text(
_currentStep < steps.length - 1
? l10n.nextButton
: l10n.getStartedButton,
),
),
),
],
),
),
],
),
),
);
}
}
Notification Badge with Fade
Create an attention-grabbing notification badge:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedNotificationBadge extends StatefulWidget {
const LocalizedNotificationBadge({super.key});
@override
State<LocalizedNotificationBadge> createState() => _LocalizedNotificationBadgeState();
}
class _LocalizedNotificationBadgeState extends State<LocalizedNotificationBadge>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _fadeAnimation;
int _notificationCount = 0;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
_fadeAnimation = CurvedAnimation(
parent: _controller,
curve: Curves.easeOut,
);
}
void _addNotification() {
setState(() => _notificationCount++);
if (_notificationCount == 1) {
_controller.forward();
}
}
void _clearNotifications() {
_controller.reverse().then((_) {
setState(() => _notificationCount = 0);
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.notificationBadgeTitle),
actions: [
Stack(
alignment: Alignment.center,
children: [
Semantics(
button: true,
label: _notificationCount > 0
? l10n.notificationsWithCount(_notificationCount)
: l10n.noNotifications,
child: IconButton(
icon: const Icon(Icons.notifications),
onPressed: _notificationCount > 0
? _clearNotifications
: null,
),
),
if (_notificationCount > 0)
Positioned(
top: 8,
right: 8,
child: FadeTransition(
opacity: _fadeAnimation,
child: Container(
padding: const EdgeInsets.all(4),
constraints: const BoxConstraints(
minWidth: 18,
minHeight: 18,
),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.error,
shape: BoxShape.circle,
),
child: Center(
child: Text(
_notificationCount > 99
? '99+'
: '$_notificationCount',
style: TextStyle(
color: Theme.of(context).colorScheme.onError,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
),
),
),
],
),
],
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.currentNotificationCount(_notificationCount),
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: _addNotification,
icon: const Icon(Icons.add),
label: Text(l10n.addNotificationButton),
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: _notificationCount > 0 ? _clearNotifications : null,
icon: const Icon(Icons.clear_all),
label: Text(l10n.clearNotificationsButton),
),
],
),
),
);
}
}
Complete ARB File for FadeTransition
{
"@@locale": "en",
"contentRevealTitle": "Content Reveal",
"spoilerAlertTitle": "Spoiler Alert!",
"spoilerWarningMessage": "The following content contains spoilers. Click to reveal at your own risk.",
"hideContentButton": "Hide Content",
"revealContentButton": "Reveal Content",
"revealedContentHeader": "Secret Revealed",
"revealedContentText": "The treasure is hidden beneath the old oak tree in the northern corner of the garden. Look for the stone marked with an X.",
"articleListTitle": "Articles",
"refreshTooltip": "Refresh articles",
"loadingContentAccessibility": "Loading content",
"contentLoadedAccessibility": "Content loaded",
"sampleArticleTitle": "Article {number}",
"@sampleArticleTitle": {
"placeholders": {"number": {"type": "int"}}
},
"sampleArticleDescription": "This is a sample article description that provides an overview of the content.",
"articleDate": "{days} days ago",
"@articleDate": {
"placeholders": {"days": {"type": "int"}}
},
"galleryTitle": "Gallery",
"galleryImage1Title": "Mountain Sunrise",
"galleryImage1Description": "A beautiful sunrise over the mountains",
"galleryImage2Title": "Forest Path",
"galleryImage2Description": "A serene path through the forest",
"galleryImage3Title": "Ocean Waves",
"galleryImage3Description": "Waves crashing on the shore",
"galleryImage4Title": "City Lights",
"galleryImage4Description": "The city skyline at night",
"imageCounter": "{current} of {total}",
"@imageCounter": {
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"}
}
},
"galleryImageAccessibility": "Image {number}: {title}",
"@galleryImageAccessibility": {
"placeholders": {
"number": {"type": "int"},
"title": {"type": "String"}
}
},
"selectImageAccessibility": "Select image {number}",
"@selectImageAccessibility": {
"placeholders": {"number": {"type": "int"}}
},
"modalDemoTitle": "Modal Demo",
"modalDemoDescription": "Tap the button below to see a modal dialog with fade animation",
"showModalButton": "Show Modal",
"modalDialogAccessibility": "Confirmation dialog",
"modalTitle": "Success!",
"modalMessage": "Your action has been completed successfully. What would you like to do next?",
"cancelButton": "Cancel",
"confirmButton": "Confirm",
"onboardingStep1Title": "Discover",
"onboardingStep1Description": "Explore thousands of amazing features designed to make your life easier and more productive.",
"onboardingStep2Title": "Personalize",
"onboardingStep2Description": "Customize your experience to match your unique style and preferences.",
"onboardingStep3Title": "Get Started",
"onboardingStep3Description": "You're all set! Begin your journey and unlock your full potential.",
"stepIndicatorAccessibility": "Step {current} of {total}, {isActive, select, true{active} other{inactive}}",
"@stepIndicatorAccessibility": {
"placeholders": {
"current": {"type": "int"},
"total": {"type": "int"},
"isActive": {"type": "String"}
}
},
"skipButton": "Skip",
"previousButton": "Previous",
"nextButton": "Next",
"getStartedButton": "Get Started",
"notificationBadgeTitle": "Notifications",
"notificationsWithCount": "{count} notifications",
"@notificationsWithCount": {
"placeholders": {"count": {"type": "int"}}
},
"noNotifications": "No notifications",
"currentNotificationCount": "{count} notifications",
"@currentNotificationCount": {
"placeholders": {"count": {"type": "int"}}
},
"addNotificationButton": "Add Notification",
"clearNotificationsButton": "Clear All"
}
Best Practices Summary
- Use semantic hiding: Set
Semantics.hiddenwhen content is faded out - Announce visibility changes: Use live regions for important state changes
- Combine with scale: Add subtle scale animations for more polish
- Choose appropriate durations: 200-400ms for UI elements, longer for modals
- Handle backdrop interactions: Allow dismissing by tapping faded backdrops
- Provide loading feedback: Use skeleton loaders with fade transitions
- Test with screen readers: Verify announcements work correctly
- Use curved animations: Apply easing for natural-feeling fades
- Stagger multiple elements: Create visual hierarchy with delayed fades
- Maintain consistency: Use the same fade patterns throughout your app
Conclusion
FadeTransition is an essential widget for creating smooth opacity animations in multilingual Flutter apps. By providing proper accessibility announcements, using semantic hiding, and choosing appropriate animation durations, you create polished experiences for users worldwide. The patterns shown here—content reveals, skeleton loaders, image galleries, and modal dialogs—can be adapted for any application requiring elegant visibility transitions.
Remember to test your fading animations with screen readers to ensure that visibility changes are properly announced regardless of the user's language or accessibility needs.