← Back to Blog

Flutter Card Localization: Material Containers for Multilingual Content

fluttercardmaterialcontainerlocalizationstyling

Flutter Card Localization: Material Containers for Multilingual Content

Card is a Flutter Material Design widget that displays content in a raised container with rounded corners. In multilingual applications, Card provides consistent visual containment that adapts to different text lengths and reading directions while maintaining Material Design aesthetics.

Understanding Card in Localization Context

Card creates elevated surfaces that group related information with shadows and rounded corners. For multilingual apps, this enables:

  • Consistent content containers across all languages
  • Direction-aware layouts within cards
  • Flexible sizing that adapts to text length
  • Themed appearance that respects locale preferences

Why Card Matters for Multilingual Apps

Card provides:

  • Visual grouping: Contains related content regardless of language
  • Consistent elevation: Same depth perception across locales
  • Flexible content: Expands to fit translated text
  • Theme integration: Respects app-wide styling

Basic Card Implementation

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

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

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

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(
              l10n.cardTitle,
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 8),
            Text(
              l10n.cardDescription,
              style: Theme.of(context).textTheme.bodyMedium,
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                TextButton(
                  onPressed: () {},
                  child: Text(l10n.learnMore),
                ),
                const SizedBox(width: 8),
                FilledButton(
                  onPressed: () {},
                  child: Text(l10n.getStarted),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Card Variants

Outlined Card

class LocalizedOutlinedCard extends StatelessWidget {
  final String title;
  final String content;
  final VoidCallback? onTap;

  const LocalizedOutlinedCard({
    super.key,
    required this.title,
    required this.content,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
        side: BorderSide(
          color: Theme.of(context).colorScheme.outline,
        ),
      ),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        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(
                content,
                style: Theme.of(context).textTheme.bodyMedium,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

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

    return Column(
      children: [
        LocalizedOutlinedCard(
          title: l10n.outlinedCardTitle,
          content: l10n.outlinedCardContent,
          onTap: () {},
        ),
        const SizedBox(height: 16),
        Card(
          elevation: 4,
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  l10n.elevatedCardTitle,
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                const SizedBox(height: 8),
                Text(l10n.elevatedCardContent),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

Filled Card

class LocalizedFilledCard extends StatelessWidget {
  final String title;
  final String subtitle;
  final IconData icon;
  final VoidCallback? onTap;

  const LocalizedFilledCard({
    super.key,
    required this.title,
    required this.subtitle,
    required this.icon,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 0,
      color: Theme.of(context).colorScheme.surfaceVariant,
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Theme.of(context).colorScheme.surface,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(icon),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      title,
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 4),
                    Text(
                      subtitle,
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                  ],
                ),
              ),
              const Icon(Icons.chevron_right),
            ],
          ),
        ),
      ),
    );
  }
}

Content Cards

Article Card

class LocalizedArticleCard extends StatelessWidget {
  final String title;
  final String excerpt;
  final String author;
  final String date;
  final String? imageUrl;
  final VoidCallback? onTap;

  const LocalizedArticleCard({
    super.key,
    required this.title,
    required this.excerpt,
    required this.author,
    required this.date,
    this.imageUrl,
    this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: InkWell(
        onTap: onTap,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (imageUrl != null)
              Image.network(
                imageUrl!,
                height: 160,
                width: double.infinity,
                fit: BoxFit.cover,
              ),
            Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    title,
                    style: Theme.of(context).textTheme.titleMedium,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    excerpt,
                    style: Theme.of(context).textTheme.bodySmall,
                    maxLines: 3,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 12),
                  Row(
                    children: [
                      CircleAvatar(
                        radius: 12,
                        child: Text(author[0].toUpperCase()),
                      ),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          author,
                          style: Theme.of(context).textTheme.bodySmall,
                        ),
                      ),
                      Text(
                        date,
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

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

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

    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        LocalizedArticleCard(
          title: l10n.article1Title,
          excerpt: l10n.article1Excerpt,
          author: l10n.article1Author,
          date: l10n.article1Date,
          imageUrl: 'https://picsum.photos/400/160?1',
          onTap: () {},
        ),
        const SizedBox(height: 16),
        LocalizedArticleCard(
          title: l10n.article2Title,
          excerpt: l10n.article2Excerpt,
          author: l10n.article2Author,
          date: l10n.article2Date,
          imageUrl: 'https://picsum.photos/400/160?2',
          onTap: () {},
        ),
      ],
    );
  }
}

Product Card

class LocalizedProductCard extends StatelessWidget {
  final String name;
  final String price;
  final String? originalPrice;
  final String? badge;
  final double rating;
  final int reviewCount;
  final VoidCallback? onTap;
  final VoidCallback? onAddToCart;

  const LocalizedProductCard({
    super.key,
    required this.name,
    required this.price,
    this.originalPrice,
    this.badge,
    required this.rating,
    required this.reviewCount,
    this.onTap,
    this.onAddToCart,
  });

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

    return Card(
      clipBehavior: Clip.antiAlias,
      child: InkWell(
        onTap: onTap,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Stack(
              children: [
                Container(
                  height: 120,
                  color: Theme.of(context).colorScheme.surfaceVariant,
                  child: const Center(
                    child: Icon(Icons.image, size: 48),
                  ),
                ),
                if (badge != null)
                  Positioned(
                    top: 8,
                    left: Directionality.of(context) == TextDirection.rtl
                        ? null
                        : 8,
                    right: Directionality.of(context) == TextDirection.rtl
                        ? 8
                        : null,
                    child: Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 8,
                        vertical: 4,
                      ),
                      decoration: BoxDecoration(
                        color: Theme.of(context).colorScheme.error,
                        borderRadius: BorderRadius.circular(4),
                      ),
                      child: Text(
                        badge!,
                        style: TextStyle(
                          color: Theme.of(context).colorScheme.onError,
                          fontSize: 12,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ),
              ],
            ),
            Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    name,
                    style: Theme.of(context).textTheme.titleSmall,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      Text(
                        price,
                        style: Theme.of(context).textTheme.titleMedium?.copyWith(
                              color: Theme.of(context).colorScheme.primary,
                              fontWeight: FontWeight.bold,
                            ),
                      ),
                      if (originalPrice != null) ...[
                        const SizedBox(width: 8),
                        Text(
                          originalPrice!,
                          style: Theme.of(context).textTheme.bodySmall?.copyWith(
                                decoration: TextDecoration.lineThrough,
                              ),
                        ),
                      ],
                    ],
                  ),
                  const SizedBox(height: 8),
                  Row(
                    children: [
                      const Icon(Icons.star, size: 16, color: Colors.amber),
                      const SizedBox(width: 4),
                      Text(
                        rating.toString(),
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                      const SizedBox(width: 4),
                      Text(
                        l10n.reviewCount(reviewCount),
                        style: Theme.of(context).textTheme.bodySmall,
                      ),
                    ],
                  ),
                  const SizedBox(height: 12),
                  SizedBox(
                    width: double.infinity,
                    child: FilledButton.tonal(
                      onPressed: onAddToCart,
                      child: Text(l10n.addToCart),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Interactive Cards

Expandable Card

class LocalizedExpandableCard extends StatefulWidget {
  final String title;
  final String summary;
  final String details;

  const LocalizedExpandableCard({
    super.key,
    required this.title,
    required this.summary,
    required this.details,
  });

  @override
  State<LocalizedExpandableCard> createState() =>
      _LocalizedExpandableCardState();
}

class _LocalizedExpandableCardState extends State<LocalizedExpandableCard> {
  bool _isExpanded = false;

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

    return Card(
      child: Column(
        children: [
          InkWell(
            onTap: () => setState(() => _isExpanded = !_isExpanded),
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                children: [
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          widget.title,
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                        const SizedBox(height: 4),
                        Text(
                          widget.summary,
                          style: Theme.of(context).textTheme.bodySmall,
                        ),
                      ],
                    ),
                  ),
                  AnimatedRotation(
                    turns: _isExpanded ? 0.5 : 0,
                    duration: const Duration(milliseconds: 200),
                    child: const Icon(Icons.expand_more),
                  ),
                ],
              ),
            ),
          ),
          AnimatedCrossFade(
            firstChild: const SizedBox.shrink(),
            secondChild: Padding(
              padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Divider(),
                  const SizedBox(height: 8),
                  Text(widget.details),
                ],
              ),
            ),
            crossFadeState: _isExpanded
                ? CrossFadeState.showSecond
                : CrossFadeState.showFirst,
            duration: const Duration(milliseconds: 200),
          ),
        ],
      ),
    );
  }
}

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

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

    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        LocalizedExpandableCard(
          title: l10n.faq1Question,
          summary: l10n.faq1Summary,
          details: l10n.faq1Answer,
        ),
        const SizedBox(height: 8),
        LocalizedExpandableCard(
          title: l10n.faq2Question,
          summary: l10n.faq2Summary,
          details: l10n.faq2Answer,
        ),
        const SizedBox(height: 8),
        LocalizedExpandableCard(
          title: l10n.faq3Question,
          summary: l10n.faq3Summary,
          details: l10n.faq3Answer,
        ),
      ],
    );
  }
}

ARB File Structure

English (app_en.arb)

{
  "@@locale": "en",

  "cardTitle": "Welcome Card",
  "cardDescription": "This is a sample card with localized content that adapts to your language.",
  "learnMore": "Learn More",
  "getStarted": "Get Started",

  "outlinedCardTitle": "Outlined Style",
  "outlinedCardContent": "Cards with borders instead of shadows.",
  "elevatedCardTitle": "Elevated Style",
  "elevatedCardContent": "Cards with shadow depth.",

  "article1Title": "Getting Started with Flutter",
  "article1Excerpt": "Learn the basics of Flutter development and build your first app.",
  "article1Author": "John Smith",
  "article1Date": "Feb 11",
  "article2Title": "Advanced Localization",
  "article2Excerpt": "Master internationalization in Flutter with advanced techniques.",
  "article2Author": "Jane Doe",
  "article2Date": "Feb 10",

  "reviewCount": "({count} reviews)",
  "@reviewCount": {
    "placeholders": {
      "count": {"type": "int"}
    }
  },
  "addToCart": "Add to Cart",

  "faq1Question": "How do I get started?",
  "faq1Summary": "Quick start guide",
  "faq1Answer": "Download the app, create an account, and follow the onboarding tutorial to get started.",
  "faq2Question": "Is there a free trial?",
  "faq2Summary": "Trial information",
  "faq2Answer": "Yes, we offer a 14-day free trial with full access to all features.",
  "faq3Question": "How do I contact support?",
  "faq3Summary": "Support options",
  "faq3Answer": "You can reach our support team via email, chat, or phone during business hours."
}

German (app_de.arb)

{
  "@@locale": "de",

  "cardTitle": "Willkommenskarte",
  "cardDescription": "Dies ist eine Beispielkarte mit lokalisierten Inhalten, die sich an Ihre Sprache anpassen.",
  "learnMore": "Mehr erfahren",
  "getStarted": "Loslegen",

  "outlinedCardTitle": "Umrissener Stil",
  "outlinedCardContent": "Karten mit Rahmen statt Schatten.",
  "elevatedCardTitle": "Erhöhter Stil",
  "elevatedCardContent": "Karten mit Schattentiefe.",

  "article1Title": "Erste Schritte mit Flutter",
  "article1Excerpt": "Lernen Sie die Grundlagen der Flutter-Entwicklung und erstellen Sie Ihre erste App.",
  "article1Author": "Max Mustermann",
  "article1Date": "11. Feb",
  "article2Title": "Erweiterte Lokalisierung",
  "article2Excerpt": "Meistern Sie die Internationalisierung in Flutter mit fortgeschrittenen Techniken.",
  "article2Author": "Erika Musterfrau",
  "article2Date": "10. Feb",

  "reviewCount": "({count} Bewertungen)",
  "addToCart": "In den Warenkorb",

  "faq1Question": "Wie fange ich an?",
  "faq1Summary": "Schnellstartanleitung",
  "faq1Answer": "Laden Sie die App herunter, erstellen Sie ein Konto und folgen Sie dem Onboarding-Tutorial.",
  "faq2Question": "Gibt es eine kostenlose Testversion?",
  "faq2Summary": "Testinformationen",
  "faq2Answer": "Ja, wir bieten eine 14-tägige kostenlose Testversion mit vollem Zugang zu allen Funktionen.",
  "faq3Question": "Wie kontaktiere ich den Support?",
  "faq3Summary": "Supportoptionen",
  "faq3Answer": "Sie können unser Supportteam per E-Mail, Chat oder Telefon während der Geschäftszeiten erreichen."
}

Arabic (app_ar.arb)

{
  "@@locale": "ar",

  "cardTitle": "بطاقة الترحيب",
  "cardDescription": "هذه بطاقة نموذجية بمحتوى مترجم يتكيف مع لغتك.",
  "learnMore": "اعرف المزيد",
  "getStarted": "ابدأ الآن",

  "outlinedCardTitle": "نمط محدد",
  "outlinedCardContent": "بطاقات بحدود بدلاً من الظلال.",
  "elevatedCardTitle": "نمط مرتفع",
  "elevatedCardContent": "بطاقات بعمق الظل.",

  "article1Title": "البدء مع Flutter",
  "article1Excerpt": "تعلم أساسيات تطوير Flutter وأنشئ تطبيقك الأول.",
  "article1Author": "أحمد محمد",
  "article1Date": "11 فبراير",
  "article2Title": "التوطين المتقدم",
  "article2Excerpt": "أتقن التدويل في Flutter بتقنيات متقدمة.",
  "article2Author": "سارة علي",
  "article2Date": "10 فبراير",

  "reviewCount": "({count} مراجعة)",
  "addToCart": "أضف إلى السلة",

  "faq1Question": "كيف أبدأ؟",
  "faq1Summary": "دليل البداية السريعة",
  "faq1Answer": "قم بتنزيل التطبيق وإنشاء حساب واتبع البرنامج التعليمي للبدء.",
  "faq2Question": "هل هناك نسخة تجريبية مجانية؟",
  "faq2Summary": "معلومات التجربة",
  "faq2Answer": "نعم، نقدم نسخة تجريبية مجانية لمدة 14 يومًا مع وصول كامل لجميع الميزات.",
  "faq3Question": "كيف أتواصل مع الدعم؟",
  "faq3Summary": "خيارات الدعم",
  "faq3Answer": "يمكنك الوصول إلى فريق الدعم عبر البريد الإلكتروني أو الدردشة أو الهاتف خلال ساعات العمل."
}

Best Practices Summary

Do's

  1. Use Card.clipBehavior for InkWell inside cards
  2. Add proper padding inside cards for content
  3. Set elevation appropriately for visual hierarchy
  4. Test with long translated text to ensure proper overflow handling
  5. Use consistent card styles throughout the app

Don'ts

  1. Don't nest Cards unnecessarily
  2. Don't use excessive elevation - follow Material guidelines
  3. Don't forget RTL badge positioning in directional layouts
  4. Don't hardcode card widths that may break with long text

Conclusion

Card is a fundamental Material Design component for organizing content in multilingual Flutter applications. By grouping related information in elevated containers, you create clear visual hierarchy that works across all languages. Use Card variants appropriately and ensure your content adapts gracefully to different text lengths and reading directions.

Further Reading