← Back to Blog

Flutter Material Widget Localization: Foundation for Multilingual Material Design

fluttermaterialelevationfoundationlocalizationtheming

Flutter Material Widget Localization: Foundation for Multilingual Material Design

Material is a Flutter widget that provides Material Design visual properties like elevation and ink effects. In multilingual applications, Material serves as the foundation for touch feedback and visual styling that works consistently across all languages and text directions.

Understanding Material in Localization Context

Material creates a piece of material with elevation, shadows, and ink splash effects. For multilingual apps, this enables:

  • Consistent visual foundation across all locales
  • Direction-aware ink effects for RTL layouts
  • Themed appearance that respects locale preferences
  • Accessible visual feedback for all users

Why Material Matters for Multilingual Apps

Material provides:

  • Universal visual language: Material effects work without translation
  • Theme integration: Respects app-wide localized themes
  • Ink support: Enables InkWell and InkResponse effects
  • Elevation system: Consistent depth across all languages

Basic Material Implementation

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

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

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

    return Material(
      elevation: 4,
      borderRadius: BorderRadius.circular(12),
      color: Theme.of(context).colorScheme.surface,
      child: InkWell(
        onTap: () {},
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                l10n.materialTitle,
                style: Theme.of(context).textTheme.titleMedium,
              ),
              const SizedBox(height: 8),
              Text(
                l10n.materialDescription,
                style: Theme.of(context).textTheme.bodyMedium,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Material Types

Different Material Types

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

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

    return Column(
      children: [
        Material(
          type: MaterialType.card,
          elevation: 2,
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Text(l10n.cardMaterial),
          ),
        ),
        const SizedBox(height: 16),
        Material(
          type: MaterialType.canvas,
          color: Theme.of(context).colorScheme.surfaceVariant,
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Text(l10n.canvasMaterial),
          ),
        ),
        const SizedBox(height: 16),
        Material(
          type: MaterialType.circle,
          elevation: 4,
          color: Theme.of(context).colorScheme.primary,
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Icon(
              Icons.check,
              color: Theme.of(context).colorScheme.onPrimary,
            ),
          ),
        ),
        const SizedBox(height: 16),
        Material(
          type: MaterialType.transparency,
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Text(l10n.transparentMaterial),
          ),
        ),
      ],
    );
  }
}

Custom Buttons with Material

Material Button Base

class LocalizedMaterialButton extends StatelessWidget {
  final String label;
  final IconData? icon;
  final VoidCallback? onPressed;
  final Color? backgroundColor;
  final Color? foregroundColor;

  const LocalizedMaterialButton({
    super.key,
    required this.label,
    this.icon,
    this.onPressed,
    this.backgroundColor,
    this.foregroundColor,
  });

  @override
  Widget build(BuildContext context) {
    final bgColor = backgroundColor ?? Theme.of(context).colorScheme.primary;
    final fgColor = foregroundColor ?? Theme.of(context).colorScheme.onPrimary;

    return Material(
      color: bgColor,
      borderRadius: BorderRadius.circular(8),
      elevation: 2,
      child: InkWell(
        onTap: onPressed,
        borderRadius: BorderRadius.circular(8),
        child: Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: 24,
            vertical: 12,
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (icon != null) ...[
                Icon(icon, color: fgColor, size: 20),
                const SizedBox(width: 8),
              ],
              Text(
                label,
                style: TextStyle(
                  color: fgColor,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

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

    return Wrap(
      spacing: 12,
      runSpacing: 12,
      children: [
        LocalizedMaterialButton(
          label: l10n.primaryButton,
          icon: Icons.check,
          onPressed: () {},
        ),
        LocalizedMaterialButton(
          label: l10n.secondaryButton,
          icon: Icons.close,
          backgroundColor: Theme.of(context).colorScheme.secondary,
          foregroundColor: Theme.of(context).colorScheme.onSecondary,
          onPressed: () {},
        ),
        LocalizedMaterialButton(
          label: l10n.tertiaryButton,
          backgroundColor: Theme.of(context).colorScheme.tertiary,
          foregroundColor: Theme.of(context).colorScheme.onTertiary,
          onPressed: () {},
        ),
      ],
    );
  }
}

Material with Shapes

Shaped Material Containers

class LocalizedShapedMaterial extends StatelessWidget {
  final Widget child;
  final ShapeBorder shape;
  final double elevation;
  final Color? color;

  const LocalizedShapedMaterial({
    super.key,
    required this.child,
    required this.shape,
    this.elevation = 2,
    this.color,
  });

  @override
  Widget build(BuildContext context) {
    return Material(
      shape: shape,
      elevation: elevation,
      color: color ?? Theme.of(context).colorScheme.surface,
      child: child,
    );
  }
}

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

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

    return Column(
      children: [
        LocalizedShapedMaterial(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Text(l10n.roundedShape),
          ),
        ),
        const SizedBox(height: 16),
        LocalizedShapedMaterial(
          shape: const StadiumBorder(),
          child: Padding(
            padding: const EdgeInsets.symmetric(
              horizontal: 24,
              vertical: 12,
            ),
            child: Text(l10n.stadiumShape),
          ),
        ),
        const SizedBox(height: 16),
        LocalizedShapedMaterial(
          shape: BeveledRectangleBorder(
            borderRadius: BorderRadius.circular(12),
          ),
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Text(l10n.beveledShape),
          ),
        ),
        const SizedBox(height: 16),
        LocalizedShapedMaterial(
          shape: const CircleBorder(),
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Icon(Icons.star),
          ),
        ),
      ],
    );
  }
}

Material Banners

Localized Banner Component

class LocalizedMaterialBanner extends StatelessWidget {
  final String message;
  final List<Widget> actions;
  final IconData? icon;
  final Color? backgroundColor;

  const LocalizedMaterialBanner({
    super.key,
    required this.message,
    required this.actions,
    this.icon,
    this.backgroundColor,
  });

  @override
  Widget build(BuildContext context) {
    return Material(
      color: backgroundColor ?? Theme.of(context).colorScheme.surfaceVariant,
      elevation: 1,
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (icon != null) ...[
              Icon(icon),
              const SizedBox(width: 16),
            ],
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    message,
                    style: Theme.of(context).textTheme.bodyMedium,
                  ),
                  const SizedBox(height: 8),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: actions,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

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

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

    return Column(
      children: [
        LocalizedMaterialBanner(
          icon: Icons.info_outline,
          message: l10n.infoBannerMessage,
          actions: [
            TextButton(
              onPressed: () {},
              child: Text(l10n.dismissButton),
            ),
            TextButton(
              onPressed: () {},
              child: Text(l10n.learnMoreButton),
            ),
          ],
        ),
        const SizedBox(height: 16),
        LocalizedMaterialBanner(
          icon: Icons.warning_amber,
          message: l10n.warningBannerMessage,
          backgroundColor: Colors.amber.shade100,
          actions: [
            TextButton(
              onPressed: () {},
              child: Text(l10n.fixNowButton),
            ),
          ],
        ),
      ],
    );
  }
}

Material Elevation States

Interactive Elevation Changes

class LocalizedElevatedContainer extends StatefulWidget {
  final Widget child;
  final VoidCallback? onTap;

  const LocalizedElevatedContainer({
    super.key,
    required this.child,
    this.onTap,
  });

  @override
  State<LocalizedElevatedContainer> createState() =>
      _LocalizedElevatedContainerState();
}

class _LocalizedElevatedContainerState
    extends State<LocalizedElevatedContainer> {
  bool _isPressed = false;
  bool _isHovered = false;

  double get _elevation {
    if (_isPressed) return 1;
    if (_isHovered) return 6;
    return 3;
  }

  @override
  Widget build(BuildContext context) {
    return MouseRegion(
      onEnter: (_) => setState(() => _isHovered = true),
      onExit: (_) => setState(() => _isHovered = false),
      child: GestureDetector(
        onTapDown: (_) => setState(() => _isPressed = true),
        onTapUp: (_) => setState(() => _isPressed = false),
        onTapCancel: () => setState(() => _isPressed = false),
        onTap: widget.onTap,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 150),
          child: Material(
            elevation: _elevation,
            borderRadius: BorderRadius.circular(12),
            color: Theme.of(context).colorScheme.surface,
            child: widget.child,
          ),
        ),
      ),
    );
  }
}

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

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

    return Column(
      children: [
        LocalizedElevatedContainer(
          onTap: () {},
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Row(
              children: [
                const Icon(Icons.folder),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        l10n.projectFolder,
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      Text(
                        l10n.projectFolderDesc,
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                    ],
                  ),
                ),
                const Icon(Icons.chevron_right),
              ],
            ),
          ),
        ),
        const SizedBox(height: 16),
        LocalizedElevatedContainer(
          onTap: () {},
          child: Padding(
            padding: const EdgeInsets.all(20),
            child: Row(
              children: [
                const Icon(Icons.settings),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        l10n.settingsFolder,
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      Text(
                        l10n.settingsFolderDesc,
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                    ],
                  ),
                ),
                const Icon(Icons.chevron_right),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

ARB File Structure

English (app_en.arb)

{
  "@@locale": "en",

  "materialTitle": "Material Container",
  "materialDescription": "This is a Material widget with elevation and ink effects.",

  "cardMaterial": "Card Material Type",
  "canvasMaterial": "Canvas Material Type",
  "transparentMaterial": "Transparent Material Type",

  "primaryButton": "Primary",
  "secondaryButton": "Secondary",
  "tertiaryButton": "Tertiary",

  "roundedShape": "Rounded Rectangle",
  "stadiumShape": "Stadium Shape",
  "beveledShape": "Beveled Rectangle",

  "infoBannerMessage": "Your account settings have been updated successfully.",
  "warningBannerMessage": "Your subscription expires in 3 days.",
  "dismissButton": "Dismiss",
  "learnMoreButton": "Learn More",
  "fixNowButton": "Renew Now",

  "projectFolder": "Projects",
  "projectFolderDesc": "View all your projects",
  "settingsFolder": "Settings",
  "settingsFolderDesc": "Configure app preferences"
}

German (app_de.arb)

{
  "@@locale": "de",

  "materialTitle": "Material-Container",
  "materialDescription": "Dies ist ein Material-Widget mit Höhe und Tinteneffekten.",

  "cardMaterial": "Karten-Materialtyp",
  "canvasMaterial": "Leinwand-Materialtyp",
  "transparentMaterial": "Transparenter Materialtyp",

  "primaryButton": "Primär",
  "secondaryButton": "Sekundär",
  "tertiaryButton": "Tertiär",

  "roundedShape": "Abgerundetes Rechteck",
  "stadiumShape": "Stadionform",
  "beveledShape": "Abgeschrägtes Rechteck",

  "infoBannerMessage": "Ihre Kontoeinstellungen wurden erfolgreich aktualisiert.",
  "warningBannerMessage": "Ihr Abonnement läuft in 3 Tagen ab.",
  "dismissButton": "Schließen",
  "learnMoreButton": "Mehr erfahren",
  "fixNowButton": "Jetzt erneuern",

  "projectFolder": "Projekte",
  "projectFolderDesc": "Alle Projekte anzeigen",
  "settingsFolder": "Einstellungen",
  "settingsFolderDesc": "App-Einstellungen konfigurieren"
}

Arabic (app_ar.arb)

{
  "@@locale": "ar",

  "materialTitle": "حاوية Material",
  "materialDescription": "هذا عنصر Material مع ارتفاع وتأثيرات الحبر.",

  "cardMaterial": "نوع مادة البطاقة",
  "canvasMaterial": "نوع مادة اللوحة",
  "transparentMaterial": "نوع مادة شفافة",

  "primaryButton": "أساسي",
  "secondaryButton": "ثانوي",
  "tertiaryButton": "ثالثي",

  "roundedShape": "مستطيل مستدير",
  "stadiumShape": "شكل ملعب",
  "beveledShape": "مستطيل مشطوف",

  "infoBannerMessage": "تم تحديث إعدادات حسابك بنجاح.",
  "warningBannerMessage": "تنتهي صلاحية اشتراكك خلال 3 أيام.",
  "dismissButton": "إغلاق",
  "learnMoreButton": "اعرف المزيد",
  "fixNowButton": "تجديد الآن",

  "projectFolder": "المشاريع",
  "projectFolderDesc": "عرض جميع مشاريعك",
  "settingsFolder": "الإعدادات",
  "settingsFolderDesc": "تكوين تفضيلات التطبيق"
}

Best Practices Summary

Do's

  1. Use Material as foundation for custom touchable widgets
  2. Set appropriate elevation for visual hierarchy
  3. Match borderRadius with InkWell for proper ripple clipping
  4. Use semantic colors from Theme for consistency
  5. Test elevation states on different devices

Don'ts

  1. Don't use Material without purpose - it adds to the widget tree
  2. Don't forget ink effects need Material ancestor
  3. Don't over-elevate - follow Material Design guidelines
  4. Don't mix Material types inconsistently

Conclusion

Material is the foundation for creating Material Design-compliant UI elements in multilingual Flutter applications. By providing elevation, shadows, and ink support, Material enables consistent visual feedback that works across all languages. Use Material strategically as the base for custom interactive widgets that need touch feedback and visual depth.

Further Reading