← Back to Blog

Flutter PhysicalModel Localization: Shadows and Elevation for Multilingual Apps

flutterphysicalmodelshadowselevationlocalizationrtl

Flutter PhysicalModel Localization: Shadows and Elevation for Multilingual Apps

PhysicalModel creates a physical representation with customizable elevation, shadows, and colors. When building multilingual apps, PhysicalModel helps create depth and visual hierarchy that adapts to different languages, text directions, and cultural design preferences. This guide covers comprehensive strategies for using PhysicalModel in Flutter localization.

Understanding PhysicalModel in Localization

PhysicalModel widgets benefit localization for:

  • Elevated cards: Product cards with localized content and shadows
  • Status indicators: Visual depth for different states with localized labels
  • Button effects: Raised buttons with translated text
  • RTL shadow direction: Adjusting shadow offsets for RTL languages
  • Theme adaptation: Material elevation that respects dark/light modes
  • Accessibility: Providing visual cues for important localized content

Basic PhysicalModel with Localized Content

Start with a simple elevated card:

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

class LocalizedPhysicalModelDemo extends StatelessWidget {
  const LocalizedPhysicalModelDemo({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.physicalModelTitle)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Basic elevated card
            PhysicalModel(
              color: Theme.of(context).colorScheme.surface,
              elevation: 8,
              shadowColor: Colors.black45,
              borderRadius: BorderRadius.circular(12),
              child: Padding(
                padding: const EdgeInsets.all(20),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      l10n.featuredProductTitle,
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 8),
                    Text(l10n.featuredProductDescription),
                    const SizedBox(height: 16),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Text(
                          l10n.productPrice('\$49.99'),
                          style: Theme.of(context).textTheme.titleMedium?.copyWith(
                            color: Theme.of(context).colorScheme.primary,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        ElevatedButton(
                          onPressed: () {},
                          child: Text(l10n.addToCartButton),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

ARB File Structure for PhysicalModel

{
  "physicalModelTitle": "Physical Model Demo",
  "@physicalModelTitle": {
    "description": "Title for physical model demo page"
  },
  "featuredProductTitle": "Featured Product",
  "featuredProductDescription": "Premium quality item with excellent features and durability.",
  "productPrice": "{price}",
  "@productPrice": {
    "placeholders": {
      "price": {"type": "String"}
    }
  },
  "addToCartButton": "Add to Cart"
}

RTL-Aware Shadow Effects

Adjust shadows based on text direction:

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

class RtlAwareShadowCards extends StatelessWidget {
  const RtlAwareShadowCards({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.shadowCardsTitle)),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Card with directional shadow
          _buildDirectionalShadowCard(
            context,
            l10n,
            isRtl,
            l10n.inboxCardTitle,
            l10n.inboxCardSubtitle,
            Icons.inbox,
            Colors.blue,
          ),
          const SizedBox(height: 16),
          _buildDirectionalShadowCard(
            context,
            l10n,
            isRtl,
            l10n.sentCardTitle,
            l10n.sentCardSubtitle,
            Icons.send,
            Colors.green,
          ),
          const SizedBox(height: 16),
          _buildDirectionalShadowCard(
            context,
            l10n,
            isRtl,
            l10n.draftsCardTitle,
            l10n.draftsCardSubtitle,
            Icons.drafts,
            Colors.orange,
          ),
        ],
      ),
    );
  }

  Widget _buildDirectionalShadowCard(
    BuildContext context,
    AppLocalizations l10n,
    bool isRtl,
    String title,
    String subtitle,
    IconData icon,
    Color color,
  ) {
    // Shadow offset respects text direction
    final shadowOffset = isRtl ? const Offset(-4, 4) : const Offset(4, 4);

    return Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
            color: color.withOpacity(0.3),
            offset: shadowOffset,
            blurRadius: 8,
            spreadRadius: 0,
          ),
        ],
      ),
      child: PhysicalModel(
        color: Theme.of(context).colorScheme.surface,
        elevation: 0, // Using custom shadow instead
        borderRadius: BorderRadius.circular(12),
        child: ListTile(
          contentPadding: const EdgeInsets.all(16),
          leading: Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: color.withOpacity(0.1),
              borderRadius: BorderRadius.circular(8),
            ),
            child: Icon(icon, color: color),
          ),
          title: Text(
            title,
            style: Theme.of(context).textTheme.titleMedium,
          ),
          subtitle: Text(subtitle),
          trailing: Icon(
            isRtl ? Icons.chevron_left : Icons.chevron_right,
            color: Theme.of(context).colorScheme.onSurfaceVariant,
          ),
        ),
      ),
    );
  }
}

Elevation States with Localized Labels

Create cards with different elevation states:

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

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

  @override
  State<LocalizedElevationStates> createState() => _LocalizedElevationStatesState();
}

class _LocalizedElevationStatesState extends State<LocalizedElevationStates> {
  int? _selectedIndex;
  int? _hoveredIndex;

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

    final plans = [
      (
        title: l10n.planBasicTitle,
        price: l10n.planBasicPrice,
        features: l10n.planBasicFeatures,
        color: Colors.grey,
      ),
      (
        title: l10n.planProTitle,
        price: l10n.planProPrice,
        features: l10n.planProFeatures,
        color: Colors.blue,
      ),
      (
        title: l10n.planEnterpriseTitle,
        price: l10n.planEnterprisePrice,
        features: l10n.planEnterpriseFeatures,
        color: Colors.purple,
      ),
    ];

    return Scaffold(
      appBar: AppBar(title: Text(l10n.pricingPlansTitle)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              l10n.selectPlanPrompt,
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(height: 8),
            Text(
              l10n.selectPlanDescription,
              style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                color: Theme.of(context).colorScheme.onSurfaceVariant,
              ),
            ),
            const SizedBox(height: 24),
            Expanded(
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: List.generate(plans.length, (index) {
                  final plan = plans[index];
                  final isSelected = _selectedIndex == index;
                  final isHovered = _hoveredIndex == index;

                  // Dynamic elevation based on state
                  double elevation = 2;
                  if (isSelected) {
                    elevation = 12;
                  } else if (isHovered) {
                    elevation = 6;
                  }

                  return Expanded(
                    child: Padding(
                      padding: EdgeInsets.only(
                        left: index > 0 ? 8 : 0,
                        right: index < plans.length - 1 ? 8 : 0,
                      ),
                      child: MouseRegion(
                        onEnter: (_) => setState(() => _hoveredIndex = index),
                        onExit: (_) => setState(() => _hoveredIndex = null),
                        child: GestureDetector(
                          onTap: () => setState(() => _selectedIndex = index),
                          child: AnimatedContainer(
                            duration: const Duration(milliseconds: 200),
                            child: PhysicalModel(
                              color: isSelected
                                  ? plan.color.withOpacity(0.1)
                                  : Theme.of(context).colorScheme.surface,
                              elevation: elevation,
                              shadowColor: plan.color.withOpacity(0.4),
                              borderRadius: BorderRadius.circular(16),
                              child: Container(
                                decoration: BoxDecoration(
                                  borderRadius: BorderRadius.circular(16),
                                  border: isSelected
                                      ? Border.all(color: plan.color, width: 2)
                                      : null,
                                ),
                                padding: const EdgeInsets.all(20),
                                child: Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    if (isSelected)
                                      Container(
                                        padding: const EdgeInsets.symmetric(
                                          horizontal: 8,
                                          vertical: 4,
                                        ),
                                        decoration: BoxDecoration(
                                          color: plan.color,
                                          borderRadius: BorderRadius.circular(12),
                                        ),
                                        child: Text(
                                          l10n.selectedLabel,
                                          style: const TextStyle(
                                            color: Colors.white,
                                            fontSize: 12,
                                            fontWeight: FontWeight.bold,
                                          ),
                                        ),
                                      ),
                                    const SizedBox(height: 8),
                                    Text(
                                      plan.title,
                                      style: Theme.of(context).textTheme.titleLarge,
                                    ),
                                    const SizedBox(height: 8),
                                    Text(
                                      plan.price,
                                      style: Theme.of(context).textTheme.headlineMedium?.copyWith(
                                        color: plan.color,
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),
                                    const SizedBox(height: 16),
                                    Expanded(
                                      child: Text(
                                        plan.features,
                                        style: Theme.of(context).textTheme.bodySmall,
                                      ),
                                    ),
                                    ElevatedButton(
                                      onPressed: () {},
                                      style: ElevatedButton.styleFrom(
                                        backgroundColor: plan.color,
                                        foregroundColor: Colors.white,
                                        minimumSize: const Size.fromHeight(44),
                                      ),
                                      child: Text(l10n.choosePlanButton),
                                    ),
                                  ],
                                ),
                              ),
                            ),
                          ),
                        ),
                      ),
                    ),
                  );
                }),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Notification Cards with Depth

Create notification cards with visual depth:

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

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

  @override
  State<LocalizedNotificationCards> createState() =>
      _LocalizedNotificationCardsState();
}

class _LocalizedNotificationCardsState
    extends State<LocalizedNotificationCards> {
  final List<Map<String, dynamic>> _notifications = [];

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _initializeNotifications();
  }

  void _initializeNotifications() {
    final l10n = AppLocalizations.of(context)!;
    _notifications.clear();
    _notifications.addAll([
      {
        'type': 'success',
        'title': l10n.notificationSuccessTitle,
        'message': l10n.notificationSuccessMessage,
        'icon': Icons.check_circle,
        'color': Colors.green,
        'isRead': false,
      },
      {
        'type': 'warning',
        'title': l10n.notificationWarningTitle,
        'message': l10n.notificationWarningMessage,
        'icon': Icons.warning,
        'color': Colors.orange,
        'isRead': false,
      },
      {
        'type': 'info',
        'title': l10n.notificationInfoTitle,
        'message': l10n.notificationInfoMessage,
        'icon': Icons.info,
        'color': Colors.blue,
        'isRead': true,
      },
      {
        'type': 'error',
        'title': l10n.notificationErrorTitle,
        'message': l10n.notificationErrorMessage,
        'icon': Icons.error,
        'color': Colors.red,
        'isRead': false,
      },
    ]);
  }

  void _markAsRead(int index) {
    setState(() {
      _notifications[index]['isRead'] = true;
    });
  }

  void _dismissNotification(int index) {
    setState(() {
      _notifications.removeAt(index);
    });
  }

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

    return Scaffold(
      appBar: AppBar(
        title: Text(l10n.notificationsTitle),
        actions: [
          TextButton(
            onPressed: () {
              setState(() {
                for (var n in _notifications) {
                  n['isRead'] = true;
                }
              });
            },
            child: Text(l10n.markAllReadButton),
          ),
        ],
      ),
      body: _notifications.isEmpty
          ? Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(
                    Icons.notifications_off,
                    size: 64,
                    color: Theme.of(context).colorScheme.onSurfaceVariant,
                  ),
                  const SizedBox(height: 16),
                  Text(
                    l10n.noNotificationsMessage,
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                ],
              ),
            )
          : ListView.builder(
              padding: const EdgeInsets.all(16),
              itemCount: _notifications.length,
              itemBuilder: (context, index) {
                final notification = _notifications[index];
                final isRead = notification['isRead'] as bool;
                final color = notification['color'] as Color;

                // Unread notifications have higher elevation
                final elevation = isRead ? 2.0 : 8.0;

                return Padding(
                  padding: const EdgeInsets.only(bottom: 12),
                  child: Dismissible(
                    key: Key('notification_$index'),
                    direction: DismissDirection.horizontal,
                    onDismissed: (_) => _dismissNotification(index),
                    background: Container(
                      alignment: isRtl
                          ? Alignment.centerRight
                          : Alignment.centerLeft,
                      padding: const EdgeInsets.symmetric(horizontal: 20),
                      decoration: BoxDecoration(
                        color: Colors.red,
                        borderRadius: BorderRadius.circular(12),
                      ),
                      child: Icon(
                        Icons.delete,
                        color: Colors.white,
                      ),
                    ),
                    secondaryBackground: Container(
                      alignment: isRtl
                          ? Alignment.centerLeft
                          : Alignment.centerRight,
                      padding: const EdgeInsets.symmetric(horizontal: 20),
                      decoration: BoxDecoration(
                        color: Colors.green,
                        borderRadius: BorderRadius.circular(12),
                      ),
                      child: Icon(
                        Icons.check,
                        color: Colors.white,
                      ),
                    ),
                    child: PhysicalModel(
                      color: isRead
                          ? Theme.of(context).colorScheme.surfaceVariant
                          : Theme.of(context).colorScheme.surface,
                      elevation: elevation,
                      shadowColor: color.withOpacity(0.3),
                      borderRadius: BorderRadius.circular(12),
                      child: InkWell(
                        onTap: () => _markAsRead(index),
                        borderRadius: BorderRadius.circular(12),
                        child: Container(
                          decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(12),
                            border: !isRead
                                ? Border(
                                    left: isRtl
                                        ? BorderSide.none
                                        : BorderSide(color: color, width: 4),
                                    right: isRtl
                                        ? BorderSide(color: color, width: 4)
                                        : BorderSide.none,
                                  )
                                : null,
                          ),
                          padding: const EdgeInsets.all(16),
                          child: Row(
                            children: [
                              Container(
                                padding: const EdgeInsets.all(10),
                                decoration: BoxDecoration(
                                  color: color.withOpacity(0.1),
                                  shape: BoxShape.circle,
                                ),
                                child: Icon(
                                  notification['icon'] as IconData,
                                  color: color,
                                ),
                              ),
                              const SizedBox(width: 16),
                              Expanded(
                                child: Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    Row(
                                      children: [
                                        Expanded(
                                          child: Text(
                                            notification['title'] as String,
                                            style: Theme.of(context)
                                                .textTheme
                                                .titleMedium
                                                ?.copyWith(
                                                  fontWeight: isRead
                                                      ? FontWeight.normal
                                                      : FontWeight.bold,
                                                ),
                                          ),
                                        ),
                                        if (!isRead)
                                          Container(
                                            width: 8,
                                            height: 8,
                                            decoration: BoxDecoration(
                                              color: color,
                                              shape: BoxShape.circle,
                                            ),
                                          ),
                                      ],
                                    ),
                                    const SizedBox(height: 4),
                                    Text(
                                      notification['message'] as String,
                                      style: Theme.of(context)
                                          .textTheme
                                          .bodyMedium
                                          ?.copyWith(
                                            color: Theme.of(context)
                                                .colorScheme
                                                .onSurfaceVariant,
                                          ),
                                    ),
                                  ],
                                ),
                              ),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
    );
  }
}

Raised Buttons with Localized Text

Create raised buttons with physical elevation:

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

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

  @override
  State<LocalizedRaisedButtons> createState() => _LocalizedRaisedButtonsState();
}

class _LocalizedRaisedButtonsState extends State<LocalizedRaisedButtons> {
  bool _isLoading = false;
  Set<String> _selectedOptions = {};

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

    return Scaffold(
      appBar: AppBar(title: Text(l10n.buttonsShowcaseTitle)),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Primary action button
            Text(
              l10n.primaryActionsLabel,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 12),
            _PhysicalButton(
              label: l10n.submitButton,
              color: Theme.of(context).colorScheme.primary,
              onPressed: () => _simulateAction(),
              isLoading: _isLoading,
            ),
            const SizedBox(height: 32),

            // Secondary actions
            Text(
              l10n.secondaryActionsLabel,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: _PhysicalButton(
                    label: l10n.saveButton,
                    color: Colors.green,
                    icon: Icons.save,
                    onPressed: () {},
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: _PhysicalButton(
                    label: l10n.cancelButton,
                    color: Colors.grey,
                    icon: Icons.close,
                    onPressed: () {},
                  ),
                ),
              ],
            ),
            const SizedBox(height: 32),

            // Toggle buttons
            Text(
              l10n.toggleOptionsLabel,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 12),
            Wrap(
              spacing: 12,
              runSpacing: 12,
              children: [
                _buildToggleButton(l10n.optionEmail, 'email', Icons.email),
                _buildToggleButton(l10n.optionSms, 'sms', Icons.sms),
                _buildToggleButton(l10n.optionPush, 'push', Icons.notifications),
              ],
            ),
            const SizedBox(height: 32),

            // Danger zone
            Text(
              l10n.dangerZoneLabel,
              style: Theme.of(context).textTheme.titleMedium?.copyWith(
                color: Colors.red,
              ),
            ),
            const SizedBox(height: 12),
            _PhysicalButton(
              label: l10n.deleteAccountButton,
              color: Colors.red,
              icon: Icons.delete_forever,
              onPressed: () => _showDeleteConfirmation(context, l10n),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildToggleButton(String label, String key, IconData icon) {
    final isSelected = _selectedOptions.contains(key);
    return GestureDetector(
      onTap: () {
        setState(() {
          if (isSelected) {
            _selectedOptions.remove(key);
          } else {
            _selectedOptions.add(key);
          }
        });
      },
      child: PhysicalModel(
        color: isSelected
            ? Theme.of(context).colorScheme.primaryContainer
            : Theme.of(context).colorScheme.surface,
        elevation: isSelected ? 6 : 2,
        shadowColor: isSelected
            ? Theme.of(context).colorScheme.primary.withOpacity(0.3)
            : Colors.black26,
        borderRadius: BorderRadius.circular(25),
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(25),
            border: Border.all(
              color: isSelected
                  ? Theme.of(context).colorScheme.primary
                  : Colors.transparent,
            ),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Icon(
                icon,
                size: 18,
                color: isSelected
                    ? Theme.of(context).colorScheme.primary
                    : Theme.of(context).colorScheme.onSurface,
              ),
              const SizedBox(width: 8),
              Text(
                label,
                style: TextStyle(
                  color: isSelected
                      ? Theme.of(context).colorScheme.primary
                      : Theme.of(context).colorScheme.onSurface,
                  fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  void _simulateAction() async {
    setState(() => _isLoading = true);
    await Future.delayed(const Duration(seconds: 2));
    setState(() => _isLoading = false);
  }

  void _showDeleteConfirmation(BuildContext context, AppLocalizations l10n) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(l10n.deleteConfirmTitle),
        content: Text(l10n.deleteConfirmMessage),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text(l10n.cancelButton),
          ),
          ElevatedButton(
            onPressed: () => Navigator.pop(context),
            style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
            child: Text(l10n.deleteButton),
          ),
        ],
      ),
    );
  }
}

class _PhysicalButton extends StatefulWidget {
  final String label;
  final Color color;
  final IconData? icon;
  final VoidCallback onPressed;
  final bool isLoading;

  const _PhysicalButton({
    required this.label,
    required this.color,
    this.icon,
    required this.onPressed,
    this.isLoading = false,
  });

  @override
  State<_PhysicalButton> createState() => _PhysicalButtonState();
}

class _PhysicalButtonState extends State<_PhysicalButton> {
  bool _isPressed = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapDown: (_) => setState(() => _isPressed = true),
      onTapUp: (_) {
        setState(() => _isPressed = false);
        widget.onPressed();
      },
      onTapCancel: () => setState(() => _isPressed = false),
      child: AnimatedContainer(
        duration: const Duration(milliseconds: 100),
        child: PhysicalModel(
          color: widget.color,
          elevation: _isPressed ? 2 : 6,
          shadowColor: widget.color.withOpacity(0.5),
          borderRadius: BorderRadius.circular(12),
          child: Container(
            padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                if (widget.isLoading) ...[
                  SizedBox(
                    width: 20,
                    height: 20,
                    child: CircularProgressIndicator(
                      strokeWidth: 2,
                      valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
                    ),
                  ),
                  const SizedBox(width: 12),
                ] else if (widget.icon != null) ...[
                  Icon(widget.icon, color: Colors.white),
                  const SizedBox(width: 8),
                ],
                Text(
                  widget.label,
                  style: const TextStyle(
                    color: Colors.white,
                    fontWeight: FontWeight.bold,
                    fontSize: 16,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Complete ARB File for PhysicalModel

{
  "@@locale": "en",

  "physicalModelTitle": "Physical Model Demo",
  "featuredProductTitle": "Featured Product",
  "featuredProductDescription": "Premium quality item with excellent features and durability.",
  "productPrice": "{price}",
  "@productPrice": {
    "placeholders": {"price": {"type": "String"}}
  },
  "addToCartButton": "Add to Cart",

  "shadowCardsTitle": "Shadow Cards",
  "inboxCardTitle": "Inbox",
  "inboxCardSubtitle": "12 unread messages",
  "sentCardTitle": "Sent",
  "sentCardSubtitle": "45 messages this week",
  "draftsCardTitle": "Drafts",
  "draftsCardSubtitle": "3 drafts saved",

  "pricingPlansTitle": "Pricing Plans",
  "selectPlanPrompt": "Choose Your Plan",
  "selectPlanDescription": "Select the plan that best fits your needs",
  "planBasicTitle": "Basic",
  "planBasicPrice": "$9/mo",
  "planBasicFeatures": "5 projects\n10GB storage\nEmail support",
  "planProTitle": "Pro",
  "planProPrice": "$29/mo",
  "planProFeatures": "Unlimited projects\n100GB storage\nPriority support\nAdvanced analytics",
  "planEnterpriseTitle": "Enterprise",
  "planEnterprisePrice": "$99/mo",
  "planEnterpriseFeatures": "Unlimited everything\nDedicated account manager\n24/7 phone support\nCustom integrations",
  "selectedLabel": "SELECTED",
  "choosePlanButton": "Choose Plan",

  "notificationsTitle": "Notifications",
  "markAllReadButton": "Mark All Read",
  "noNotificationsMessage": "No notifications",
  "notificationSuccessTitle": "Payment Successful",
  "notificationSuccessMessage": "Your payment of $49.99 has been processed.",
  "notificationWarningTitle": "Storage Almost Full",
  "notificationWarningMessage": "You've used 90% of your storage space.",
  "notificationInfoTitle": "New Feature Available",
  "notificationInfoMessage": "Check out the new dashboard analytics.",
  "notificationErrorTitle": "Sync Failed",
  "notificationErrorMessage": "Unable to sync your data. Please try again.",

  "buttonsShowcaseTitle": "Buttons Showcase",
  "primaryActionsLabel": "Primary Actions",
  "secondaryActionsLabel": "Secondary Actions",
  "toggleOptionsLabel": "Toggle Options",
  "dangerZoneLabel": "Danger Zone",
  "submitButton": "Submit",
  "saveButton": "Save",
  "cancelButton": "Cancel",
  "deleteButton": "Delete",
  "optionEmail": "Email",
  "optionSms": "SMS",
  "optionPush": "Push",
  "deleteAccountButton": "Delete Account",
  "deleteConfirmTitle": "Delete Account?",
  "deleteConfirmMessage": "This action cannot be undone. All your data will be permanently deleted."
}

Best Practices Summary

  1. Adjust shadows for RTL: Offset shadows appropriately for text direction
  2. Use elevation for hierarchy: Higher elevation indicates more importance
  3. State-based elevation: Change elevation based on selection, hover, or press states
  4. Consistent shadow colors: Match shadow colors to the element's accent color
  5. Accessibility considerations: Don't rely solely on shadows for visual differentiation
  6. Performance: PhysicalModel is efficient for single-layer elevation effects
  7. Border radius matching: Ensure border radius matches between PhysicalModel and child
  8. Theme awareness: Adjust shadow opacity for dark vs light themes
  9. Animation: Animate elevation changes for smooth interactions
  10. Test across locales: Verify shadow effects look correct with different text lengths

Conclusion

PhysicalModel provides an efficient way to add depth and elevation to Flutter widgets. When building multilingual applications, consider how shadows and elevation interact with text direction and layout changes. By properly adapting physical effects for RTL languages and different text lengths, you create a polished experience that feels natural across all supported locales.

Use PhysicalModel for simple elevation needs and consider combining with custom box shadows for more complex directional effects that respond to the user's locale and reading direction.