Flutter Opacity Localization: Transparency Control for Multilingual Apps
The Opacity widget controls the transparency of its child, enabling fade effects, disabled states, ghost elements, and progressive visibility. When combined with localization, Opacity creates accessible visual hierarchies, context-aware visibility, and localized fade animations that adapt to different languages and cultural expectations. This guide covers comprehensive strategies for localizing Opacity widgets in Flutter multilingual applications.
Understanding Opacity Localization
Opacity widgets require localization for:
- Disabled states: Visual indicators for inactive elements
- Loading states: Faded placeholders during data fetching
- Secondary content: De-emphasized supporting information
- Ghost elements: Placeholder or preview states
- Progressive disclosure: Revealing content based on context
- Accessibility announcements: Describing visibility states
Basic Opacity with Localized Content
Start with a simple disabled state example:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedOpacityDemo extends StatefulWidget {
const LocalizedOpacityDemo({super.key});
@override
State<LocalizedOpacityDemo> createState() => _LocalizedOpacityDemoState();
}
class _LocalizedOpacityDemoState extends State<LocalizedOpacityDemo> {
bool _termsAccepted = false;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.registrationTitle)),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
l10n.createAccountHeading,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 24),
TextField(
decoration: InputDecoration(
labelText: l10n.emailLabel,
hintText: l10n.emailHint,
),
),
const SizedBox(height: 16),
TextField(
obscureText: true,
decoration: InputDecoration(
labelText: l10n.passwordLabel,
hintText: l10n.passwordHint,
),
),
const SizedBox(height: 24),
CheckboxListTile(
value: _termsAccepted,
onChanged: (value) {
setState(() => _termsAccepted = value ?? false);
},
title: Text(l10n.acceptTermsLabel),
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
),
const Spacer(),
Semantics(
label: _termsAccepted
? l10n.submitButtonActiveAccessibility
: l10n.submitButtonDisabledAccessibility,
button: true,
enabled: _termsAccepted,
child: Opacity(
opacity: _termsAccepted ? 1.0 : 0.5,
child: IgnorePointer(
ignoring: !_termsAccepted,
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Text(l10n.createAccountButton),
),
),
),
),
const SizedBox(height: 16),
if (!_termsAccepted)
Text(
l10n.acceptTermsReminder,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.error,
),
textAlign: TextAlign.center,
),
],
),
),
);
}
}
ARB File Structure for Opacity
{
"registrationTitle": "Register",
"@registrationTitle": {
"description": "Title for registration page"
},
"createAccountHeading": "Create Your Account",
"emailLabel": "Email",
"emailHint": "Enter your email address",
"passwordLabel": "Password",
"passwordHint": "Enter a secure password",
"acceptTermsLabel": "I accept the terms and conditions",
"createAccountButton": "Create Account",
"acceptTermsReminder": "Please accept the terms to continue",
"submitButtonActiveAccessibility": "Create account button, ready to submit",
"submitButtonDisabledAccessibility": "Create account button, disabled until terms are accepted"
}
Disabled Item List
Create a list with disabled items:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedDisabledList extends StatelessWidget {
const LocalizedDisabledList({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final menuItems = [
(l10n.menuProfile, Icons.person, true),
(l10n.menuSettings, Icons.settings, true),
(l10n.menuPremium, Icons.star, false),
(l10n.menuAnalytics, Icons.analytics, false),
(l10n.menuSupport, Icons.help, true),
(l10n.menuLogout, Icons.logout, true),
];
return Scaffold(
appBar: AppBar(title: Text(l10n.menuTitle)),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: menuItems.length,
itemBuilder: (context, index) {
final (title, icon, isEnabled) = menuItems[index];
return _MenuItemTile(
title: title,
icon: icon,
isEnabled: isEnabled,
disabledReason: isEnabled ? null : l10n.premiumFeatureRequired,
onTap: isEnabled ? () {} : null,
);
},
),
);
}
}
class _MenuItemTile extends StatelessWidget {
final String title;
final IconData icon;
final bool isEnabled;
final String? disabledReason;
final VoidCallback? onTap;
const _MenuItemTile({
required this.title,
required this.icon,
required this.isEnabled,
this.disabledReason,
this.onTap,
});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Semantics(
label: isEnabled
? title
: l10n.disabledMenuItemAccessibility(title, disabledReason ?? ''),
button: true,
enabled: isEnabled,
child: Opacity(
opacity: isEnabled ? 1.0 : 0.4,
child: Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: Icon(icon),
title: Text(title),
subtitle: isEnabled
? null
: Text(
disabledReason ?? '',
style: TextStyle(
color: Theme.of(context).colorScheme.error,
fontSize: 12,
),
),
trailing: isEnabled
? const Icon(Icons.chevron_right)
: const Icon(Icons.lock_outline, size: 20),
onTap: onTap,
),
),
),
);
}
}
Loading State Placeholders
Create faded loading states:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedLoadingPlaceholder extends StatefulWidget {
final bool isLoading;
final Widget child;
final Widget? placeholder;
const LocalizedLoadingPlaceholder({
super.key,
required this.isLoading,
required this.child,
this.placeholder,
});
@override
State<LocalizedLoadingPlaceholder> createState() =>
_LocalizedLoadingPlaceholderState();
}
class _LocalizedLoadingPlaceholderState
extends State<LocalizedLoadingPlaceholder>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _pulseAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1000),
)..repeat(reverse: true);
_pulseAnimation = Tween<double>(begin: 0.3, end: 0.7).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
if (!widget.isLoading) {
return widget.child;
}
return Semantics(
label: l10n.loadingContentAccessibility,
child: AnimatedBuilder(
animation: _pulseAnimation,
builder: (context, child) {
return Opacity(
opacity: _pulseAnimation.value,
child: child,
);
},
child: widget.placeholder ?? _buildDefaultPlaceholder(context),
),
);
}
Widget _buildDefaultPlaceholder(BuildContext context) {
return Container(
height: 100,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(12),
),
);
}
}
class ContentLoadingDemo extends StatefulWidget {
const ContentLoadingDemo({super.key});
@override
State<ContentLoadingDemo> createState() => _ContentLoadingDemoState();
}
class _ContentLoadingDemoState extends State<ContentLoadingDemo> {
bool _isLoading = true;
List<Map<String, String>> _items = [];
@override
void initState() {
super.initState();
_loadContent();
}
Future<void> _loadContent() async {
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 2));
setState(() {
_isLoading = false;
_items = List.generate(5, (i) => {
'title': 'Item ${i + 1}',
'subtitle': 'Description for item ${i + 1}',
});
});
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(
title: Text(l10n.contentListTitle),
actions: [
IconButton(
onPressed: _loadContent,
icon: const Icon(Icons.refresh),
tooltip: l10n.refreshTooltip,
),
],
),
body: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _isLoading ? 5 : _items.length,
itemBuilder: (context, index) {
return LocalizedLoadingPlaceholder(
isLoading: _isLoading,
placeholder: _buildPlaceholderCard(context),
child: _isLoading
? const SizedBox()
: _buildContentCard(context, _items[index]),
);
},
),
);
}
Widget _buildPlaceholderCard(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(8),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 16,
width: double.infinity,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 8),
Container(
height: 12,
width: 150,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(4),
),
),
],
),
),
],
),
),
);
}
Widget _buildContentCard(BuildContext context, Map<String, String> item) {
return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: const CircleAvatar(
backgroundImage: NetworkImage('https://picsum.photos/100'),
),
title: Text(item['title']!),
subtitle: Text(item['subtitle']!),
trailing: const Icon(Icons.chevron_right),
onTap: () {},
),
);
}
}
Secondary Content Hierarchy
Create visual hierarchy with opacity:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedContentHierarchy extends StatelessWidget {
const LocalizedContentHierarchy({super.key});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.articleTitle)),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Primary content - full opacity
Text(
l10n.articleHeadline,
style: Theme.of(context).textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
// Secondary content - reduced opacity
Opacity(
opacity: 0.7,
child: Semantics(
label: l10n.articleMetaAccessibility,
child: Row(
children: [
Text(
l10n.articleAuthor,
style: Theme.of(context).textTheme.bodyMedium,
),
const Text(' · '),
Text(
l10n.articleDate,
style: Theme.of(context).textTheme.bodyMedium,
),
const Text(' · '),
Text(
l10n.articleReadTime,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
),
const SizedBox(height: 24),
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
'https://picsum.photos/600/300',
width: double.infinity,
height: 200,
fit: BoxFit.cover,
),
),
const SizedBox(height: 8),
// Image caption - tertiary importance
Opacity(
opacity: 0.5,
child: Text(
l10n.imageCaption,
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
),
const SizedBox(height: 24),
// Main body - primary
Text(
l10n.articleBody,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
height: 1.8,
),
),
const SizedBox(height: 32),
// Related section - secondary
Opacity(
opacity: 0.8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.relatedArticlesLabel,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
_buildRelatedItem(context, l10n.relatedArticle1),
_buildRelatedItem(context, l10n.relatedArticle2),
_buildRelatedItem(context, l10n.relatedArticle3),
],
),
),
],
),
),
);
}
Widget _buildRelatedItem(BuildContext context, String title) {
return ListTile(
contentPadding: EdgeInsets.zero,
leading: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.network(
'https://picsum.photos/80/60',
width: 80,
height: 60,
fit: BoxFit.cover,
),
),
title: Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
onTap: () {},
);
}
}
Ghost Elements and Previews
Create preview states with opacity:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedGhostPreview extends StatefulWidget {
const LocalizedGhostPreview({super.key});
@override
State<LocalizedGhostPreview> createState() => _LocalizedGhostPreviewState();
}
class _LocalizedGhostPreviewState extends State<LocalizedGhostPreview> {
String _selectedTemplate = '';
final List<String> _templates = ['minimal', 'modern', 'classic', 'bold'];
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.templateSelectorTitle)),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Text(
l10n.selectTemplateDescription,
style: Theme.of(context).textTheme.bodyLarge,
),
),
Expanded(
child: GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 0.75,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: _templates.length,
itemBuilder: (context, index) {
final template = _templates[index];
final isSelected = _selectedTemplate == template;
return GestureDetector(
onTap: () {
setState(() => _selectedTemplate = template);
},
child: Semantics(
label: l10n.templateAccessibility(
_getTemplateName(l10n, template),
isSelected,
),
selected: isSelected,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: isSelected ? 1.0 : 0.5,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
width: isSelected ? 3 : 1,
),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(11),
),
child: Image.network(
'https://picsum.photos/200/300?random=$index',
fit: BoxFit.cover,
width: double.infinity,
),
),
),
Padding(
padding: const EdgeInsets.all(12),
child: Text(
_getTemplateName(l10n, template),
style: Theme.of(context).textTheme.titleSmall,
),
),
],
),
),
),
),
);
},
),
),
if (_selectedTemplate.isNotEmpty)
Container(
padding: const EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
minimumSize: const Size(double.infinity, 48),
),
child: Text(l10n.applyTemplateButton),
),
),
],
),
);
}
String _getTemplateName(AppLocalizations l10n, String template) {
switch (template) {
case 'minimal':
return l10n.templateMinimal;
case 'modern':
return l10n.templateModern;
case 'classic':
return l10n.templateClassic;
case 'bold':
return l10n.templateBold;
default:
return template;
}
}
}
Progressive Reveal
Create step-based visibility:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedProgressiveReveal extends StatefulWidget {
const LocalizedProgressiveReveal({super.key});
@override
State<LocalizedProgressiveReveal> createState() =>
_LocalizedProgressiveRevealState();
}
class _LocalizedProgressiveRevealState
extends State<LocalizedProgressiveReveal> {
int _currentStep = 0;
final int _totalSteps = 4;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final steps = [
(l10n.step1Title, l10n.step1Description, Icons.person),
(l10n.step2Title, l10n.step2Description, Icons.email),
(l10n.step3Title, l10n.step3Description, Icons.lock),
(l10n.step4Title, l10n.step4Description, Icons.check_circle),
];
return Scaffold(
appBar: AppBar(
title: Text(l10n.setupWizardTitle),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(4),
child: LinearProgressIndicator(
value: (_currentStep + 1) / _totalSteps,
),
),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: steps.length,
itemBuilder: (context, index) {
final (title, description, icon) = steps[index];
final isCompleted = index < _currentStep;
final isCurrent = index == _currentStep;
final isLocked = index > _currentStep;
double opacity;
if (isCompleted || isCurrent) {
opacity = 1.0;
} else {
opacity = 0.3;
}
return Semantics(
label: _getStepAccessibility(
l10n,
title,
isCompleted,
isCurrent,
isLocked,
),
child: AnimatedOpacity(
duration: const Duration(milliseconds: 300),
opacity: opacity,
child: Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
leading: CircleAvatar(
backgroundColor: isCompleted
? Colors.green
: isCurrent
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surfaceVariant,
child: isCompleted
? const Icon(Icons.check, color: Colors.white)
: Icon(
icon,
color: isCurrent ? Colors.white : null,
),
),
title: Text(title),
subtitle: Text(description),
trailing: isLocked
? const Icon(Icons.lock_outline, size: 20)
: isCurrent
? const Icon(Icons.arrow_forward)
: null,
onTap: isCurrent ? _handleStepTap : null,
),
),
),
);
},
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
if (_currentStep > 0)
Expanded(
child: OutlinedButton(
onPressed: () {
setState(() => _currentStep--);
},
child: Text(l10n.previousButton),
),
),
if (_currentStep > 0) const SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: _currentStep < _totalSteps - 1
? () {
setState(() => _currentStep++);
}
: () {},
child: Text(
_currentStep < _totalSteps - 1
? l10n.nextButton
: l10n.finishButton,
),
),
),
],
),
),
],
),
);
}
void _handleStepTap() {
// Handle current step interaction
}
String _getStepAccessibility(
AppLocalizations l10n,
String title,
bool isCompleted,
bool isCurrent,
bool isLocked,
) {
if (isCompleted) {
return l10n.stepCompletedAccessibility(title);
} else if (isCurrent) {
return l10n.stepCurrentAccessibility(title);
} else {
return l10n.stepLockedAccessibility(title);
}
}
}
Animated Opacity Transitions
Create smooth fade transitions:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class LocalizedFadeTransition extends StatefulWidget {
final Widget child;
final bool isVisible;
final Duration duration;
const LocalizedFadeTransition({
super.key,
required this.child,
required this.isVisible,
this.duration = const Duration(milliseconds: 300),
});
@override
State<LocalizedFadeTransition> createState() => _LocalizedFadeTransitionState();
}
class _LocalizedFadeTransitionState extends State<LocalizedFadeTransition>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: widget.duration,
value: widget.isVisible ? 1.0 : 0.0,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
);
}
@override
void didUpdateWidget(LocalizedFadeTransition oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isVisible != oldWidget.isVisible) {
if (widget.isVisible) {
_controller.forward();
} else {
_controller.reverse();
}
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _animation,
child: widget.child,
);
}
}
class FadeTransitionDemo extends StatefulWidget {
const FadeTransitionDemo({super.key});
@override
State<FadeTransitionDemo> createState() => _FadeTransitionDemoState();
}
class _FadeTransitionDemoState extends State<FadeTransitionDemo> {
bool _showDetails = false;
bool _showBanner = true;
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Scaffold(
appBar: AppBar(title: Text(l10n.fadeTransitionTitle)),
body: Column(
children: [
LocalizedFadeTransition(
isVisible: _showBanner,
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
color: Theme.of(context).colorScheme.primaryContainer,
child: Row(
children: [
Expanded(
child: Text(
l10n.bannerMessage,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
IconButton(
onPressed: () {
setState(() => _showBanner = false);
},
icon: const Icon(Icons.close),
tooltip: l10n.dismissBannerTooltip,
),
],
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.productTitle,
style: Theme.of(context).textTheme.headlineSmall,
),
const SizedBox(height: 8),
Text(l10n.productShortDescription),
const SizedBox(height: 12),
TextButton(
onPressed: () {
setState(() => _showDetails = !_showDetails);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
_showDetails
? l10n.hideDetailsButton
: l10n.showDetailsButton,
),
const SizedBox(width: 4),
Icon(
_showDetails
? Icons.expand_less
: Icons.expand_more,
size: 20,
),
],
),
),
LocalizedFadeTransition(
isVisible: _showDetails,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider(),
const SizedBox(height: 8),
Text(
l10n.productDetailedDescription,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {},
child: Text(l10n.addToCartButton),
),
),
const SizedBox(width: 8),
IconButton(
onPressed: () {},
icon: const Icon(Icons.favorite_border),
tooltip: l10n.addToFavoritesTooltip,
),
],
),
],
),
),
],
),
),
),
],
),
),
),
if (!_showBanner)
TextButton(
onPressed: () {
setState(() => _showBanner = true);
},
child: Text(l10n.showBannerButton),
),
],
),
);
}
}
Complete ARB File for Opacity
{
"@@locale": "en",
"registrationTitle": "Register",
"createAccountHeading": "Create Your Account",
"emailLabel": "Email",
"emailHint": "Enter your email address",
"passwordLabel": "Password",
"passwordHint": "Enter a secure password",
"acceptTermsLabel": "I accept the terms and conditions",
"createAccountButton": "Create Account",
"acceptTermsReminder": "Please accept the terms to continue",
"submitButtonActiveAccessibility": "Create account button, ready to submit",
"submitButtonDisabledAccessibility": "Create account button, disabled until terms are accepted",
"menuTitle": "Menu",
"menuProfile": "Profile",
"menuSettings": "Settings",
"menuPremium": "Premium Features",
"menuAnalytics": "Analytics",
"menuSupport": "Support",
"menuLogout": "Log Out",
"premiumFeatureRequired": "Premium subscription required",
"disabledMenuItemAccessibility": "{item}, disabled: {reason}",
"@disabledMenuItemAccessibility": {
"placeholders": {
"item": {"type": "String"},
"reason": {"type": "String"}
}
},
"contentListTitle": "Content List",
"loadingContentAccessibility": "Loading content, please wait",
"refreshTooltip": "Refresh",
"articleTitle": "Article",
"articleHeadline": "The Future of Mobile Development",
"articleAuthor": "John Smith",
"articleDate": "January 26, 2026",
"articleReadTime": "5 min read",
"articleMetaAccessibility": "Article metadata: author, date, and reading time",
"imageCaption": "Photo credit: Example Photography",
"articleBody": "Mobile development continues to evolve at a rapid pace. New frameworks and tools emerge regularly, offering developers more powerful ways to build cross-platform applications. Flutter has become a leading choice for many teams, providing a single codebase that works across iOS, Android, web, and desktop platforms.",
"relatedArticlesLabel": "Related Articles",
"relatedArticle1": "Getting Started with Flutter",
"relatedArticle2": "Best Practices for Mobile UI",
"relatedArticle3": "Cross-Platform Development Guide",
"templateSelectorTitle": "Choose Template",
"selectTemplateDescription": "Select a template for your project",
"templateMinimal": "Minimal",
"templateModern": "Modern",
"templateClassic": "Classic",
"templateBold": "Bold",
"templateAccessibility": "{name} template, {selected, select, true{selected} other{not selected}}",
"@templateAccessibility": {
"placeholders": {
"name": {"type": "String"},
"selected": {"type": "bool"}
}
},
"applyTemplateButton": "Apply Template",
"setupWizardTitle": "Setup Wizard",
"step1Title": "Personal Info",
"step1Description": "Enter your name and details",
"step2Title": "Contact Info",
"step2Description": "Provide your email address",
"step3Title": "Security",
"step3Description": "Set up your password",
"step4Title": "Complete",
"step4Description": "Review and finish setup",
"previousButton": "Previous",
"nextButton": "Next",
"finishButton": "Finish",
"stepCompletedAccessibility": "Step {title} completed",
"stepCurrentAccessibility": "Current step: {title}",
"stepLockedAccessibility": "Step {title} locked",
"@stepCompletedAccessibility": {
"placeholders": {"title": {"type": "String"}}
},
"@stepCurrentAccessibility": {
"placeholders": {"title": {"type": "String"}}
},
"@stepLockedAccessibility": {
"placeholders": {"title": {"type": "String"}}
},
"fadeTransitionTitle": "Fade Effects",
"bannerMessage": "Welcome! Check out our new features.",
"dismissBannerTooltip": "Dismiss",
"productTitle": "Premium Widget Pack",
"productShortDescription": "A collection of beautiful Flutter widgets for your app.",
"showDetailsButton": "Show Details",
"hideDetailsButton": "Hide Details",
"productDetailedDescription": "This premium pack includes over 50 professionally designed widgets, ready to use in your Flutter projects. Each widget is fully customizable and supports dark mode, RTL layouts, and accessibility features.",
"addToCartButton": "Add to Cart",
"addToFavoritesTooltip": "Add to favorites",
"showBannerButton": "Show Banner"
}
Best Practices Summary
- Provide accessibility labels: Always describe what reduced opacity means to screen readers
- Use appropriate opacity levels: 0.5 for disabled, 0.7 for secondary, 1.0 for primary
- Animate transitions: Smooth opacity changes are less jarring
- Combine with IgnorePointer: Prevent interaction with faded disabled elements
- Maintain minimum contrast: Ensure text remains readable at reduced opacity
- Consider context: Opacity meanings may vary across cultures
- Use semantic grouping: Group related elements by opacity level
- Test with accessibility tools: Verify visibility for users with low vision
- Provide alternative indicators: Don't rely solely on opacity for state
- Document visual hierarchy: Establish consistent opacity rules across the app
Conclusion
Opacity enables subtle visual hierarchies through disabled states, loading placeholders, secondary content, and progressive reveals that enhance multilingual applications. By providing proper accessibility labels and maintaining sufficient contrast, you can build applications that communicate importance effectively while remaining accessible to all users. The key is using opacity thoughtfully to guide attention without making content unreadable.
Remember to test your opacity effects with various accessibility settings and ensure that important information remains visible and understandable regardless of visual treatment.