Tipografia in Flutter: TextStyle, TextTheme e google_fonts

Foto di Patrick Fore su Unsplash

GuidePrincipiante35 min Flutter 3.x

Tipografia in Flutter: TextStyle, TextTheme e google_fonts

La tipografia è uno degli elementi che distingue un'app curata da una raffazzonata. In Flutter tutto il testo passa dal widget Text e dalla classe TextStyle, ma scrivere stili a mano in ogni schermata porta rapidamente a incoerenze: titoli di dimensioni diverse, pesi sbagliati, colori che non rispettano il tema.

In questa guida per principianti vedremo come:

  • usare TextStyle e le sue proprietà principali (fontSize, fontWeight, letterSpacing, height);
  • applicare font di Google con il pacchetto google_fonts;
  • centralizzare la tipografia dell'app con TextTheme dentro ThemeData;
  • richiamare gli stili con Theme.of(context).textTheme;
  • includere i font come asset locali per funzionare anche offline;
  • rispettare le impostazioni di accessibilità dell'utente.

Alla fine avrai una piccola app con una scala tipografica coerente e riutilizzabile in tutte le schermate.

  1. 1

    Creare il progetto e aggiungere google_fonts

    Partiamo da un progetto nuovo e aggiungiamo il pacchetto google_fonts, che permette di usare oltre mille font gratuiti senza scaricarli manualmente.

    flutter create tipografia_demo
    cd tipografia_demo
    flutter pub add google_fonts
    

    Il comando flutter pub add aggiorna automaticamente il pubspec.yaml ed esegue flutter pub get.

    Come funziona google_fonts? Alla prima esecuzione il pacchetto scarica il font da fonts.google.com e lo mette in cache sul dispositivo. Serve quindi la connessione al primo avvio: nell'ultimo passo vedremo come evitarlo includendo i font come asset.

    Svuotiamo poi lib/main.dart e scriviamo uno scheletro minimo su cui lavorare.

    import 'package:flutter/material.dart';
    
    void main() => runApp(const TipografiaApp());
    
    class TipografiaApp extends StatelessWidget {
      const TipografiaApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Tipografia Demo',
          debugShowCheckedModeBanner: false,
          home: const HomePage(),
        );
      }
    }
    
    class HomePage extends StatelessWidget {
      const HomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Tipografia')),
          body: const Center(child: Text('Ciao Flutter!')),
        );
      }
    }

    Risultato atteso

    L'app si avvia mostrando una AppBar con il titolo "Tipografia" e il testo "Ciao Flutter!" al centro, con il font di sistema.

  2. 2

    Le proprietà fondamentali di TextStyle

    Il widget Text accetta un parametro style di tipo TextStyle. Le proprietà che userai praticamente sempre sono:

    Proprietà A cosa serve
    fontSize Dimensione in logical pixel
    fontWeight Spessore del tratto (w400 = normale, w700 = bold)
    color Colore del testo
    letterSpacing Spaziatura tra le lettere (anche negativa)
    height Interlinea come moltiplicatore di fontSize (1.4 = 140%)
    fontStyle FontStyle.italic per il corsivo
    decoration Sottolineato, barrato, ecc.

    Due consigli pratici fin da subito:

    • usa maxLines + overflow: TextOverflow.ellipsis sui testi che possono essere lunghi, per evitare gli overflow;
    • height è un moltiplicatore, non un valore in pixel: per un paragrafo leggibile punta a 1.4–1.6.

    Sostituiamo il body della HomePage con alcuni esempi.

    class HomePage extends StatelessWidget {
      const HomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Tipografia')),
          body: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'Titolo in evidenza',
                  style: TextStyle(
                    fontSize: 28,
                    fontWeight: FontWeight.w700,
                    letterSpacing: -0.5,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  'Sottotitolo descrittivo',
                  style: TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.w500,
                    color: Colors.grey.shade600,
                  ),
                ),
                const SizedBox(height: 16),
                const Text(
                  'Questo è un paragrafo di esempio che mostra come l\'interlinea '
                  'renda il testo molto più leggibile su più righe.',
                  style: TextStyle(fontSize: 15, height: 1.5),
                ),
                const SizedBox(height: 16),
                const Text(
                  'Un titolo molto lungo che non deve mandare in overflow il layout',
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: TextStyle(fontSize: 18, fontStyle: FontStyle.italic),
                ),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    La schermata mostra quattro testi con dimensioni, pesi e interlinee diverse; l'ultimo viene troncato con i tre puntini invece di uscire dallo schermo.

  3. 3

    Applicare un font di Google a un singolo testo

    Il pacchetto google_fonts espone un metodo statico per ogni famiglia di font. Ad esempio GoogleFonts.poppins() restituisce un TextStyle già configurato con quella famiglia.

    Puoi passare le stesse proprietà di TextStyle direttamente al metodo:

    GoogleFonts.poppins(fontSize: 24, fontWeight: FontWeight.w600)
    

    Oppure partire da uno stile esistente e cambiarne solo la famiglia con il parametro textStyle:

    GoogleFonts.poppins(textStyle: const TextStyle(fontSize: 24))
    

    Attenzione ai nomi: il metodo segue il nome del font in lowerCamelCase: Roboto Mono diventa GoogleFonts.robotoMono(), Playfair Display diventa GoogleFonts.playfairDisplay().

    Proviamo a confrontare tre font diversi nella stessa schermata.

    import 'package:flutter/material.dart';
    import 'package:google_fonts/google_fonts.dart';
    
    class FontDemo extends StatelessWidget {
      const FontDemo({super.key});
    
      @override
      Widget build(BuildContext context) {
        const frase = 'Flutter rende la UI divertente';
    
        return Padding(
          padding: const EdgeInsets.all(24),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(frase, style: GoogleFonts.poppins(fontSize: 22, fontWeight: FontWeight.w600)),
              const SizedBox(height: 16),
              Text(frase, style: GoogleFonts.playfairDisplay(fontSize: 22, fontStyle: FontStyle.italic)),
              const SizedBox(height: 16),
              Text(frase, style: GoogleFonts.robotoMono(fontSize: 18, letterSpacing: 0.5)),
            ],
          ),
        );
      }
    }

    Risultato atteso

    Le tre frasi vengono renderizzate con font chiaramente diversi (sans-serif geometrico, serif corsivo e monospace).

  4. 4

    Centralizzare la tipografia con TextTheme in ThemeData

    Scrivere gli stili dentro ogni widget non scala: se domani cambi font, devi toccare decine di file. La soluzione è definire la scala tipografica una sola volta dentro ThemeData.textTheme.

    Material 3 definisce 15 stili raggruppati in cinque famiglie, dal più grande al più piccolo:

    • displayLarge/Medium/Small – numeri e testi molto grandi (usali con parsimonia);
    • headlineLarge/Medium/Small – titoli di schermata;
    • titleLarge/Medium/Small – titoli di card e sezioni;
    • bodyLarge/Medium/Small – testo di contenuto (bodyMedium è il default);
    • labelLarge/Medium/Small – etichette di bottoni e chip.

    google_fonts offre una scorciatoia comodissima: GoogleFonts.poppinsTextTheme() restituisce un TextTheme completo in cui tutti e 15 gli stili usano quel font. Puoi poi sovrascrivere i singoli stili con copyWith.

    Un pattern molto usato è combinare due font: uno per i titoli, uno per il corpo del testo.

    import 'package:flutter/material.dart';
    import 'package:google_fonts/google_fonts.dart';
    
    class TipografiaApp extends StatelessWidget {
      const TipografiaApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        // Scala base basata su Inter per il corpo del testo...
        final base = GoogleFonts.interTextTheme();
    
        // ...e Poppins per i titoli.
        final textTheme = base.copyWith(
          headlineMedium: GoogleFonts.poppins(
            fontSize: 28,
            fontWeight: FontWeight.w700,
            letterSpacing: -0.5,
          ),
          titleMedium: GoogleFonts.poppins(
            fontSize: 18,
            fontWeight: FontWeight.w600,
          ),
          bodyMedium: base.bodyMedium?.copyWith(fontSize: 15, height: 1.5),
          labelLarge: GoogleFonts.poppins(
            fontSize: 14,
            fontWeight: FontWeight.w600,
          ),
        );
    
        return MaterialApp(
          title: 'Tipografia Demo',
          debugShowCheckedModeBanner: false,
          theme: ThemeData(
            useMaterial3: true,
            colorSchemeSeed: const Color(0xFF3F51B5),
            textTheme: textTheme,
          ),
          home: const HomePage(),
        );
      }
    }

    Risultato atteso

    Dopo l'hot restart tutti i testi dell'app (compresi quelli dei bottoni e della AppBar) usano i nuovi font, senza aver modificato i singoli widget.

  5. 5

    Usare gli stili del tema nei widget

    Ora che la scala è definita, nei widget non devi più scrivere fontSize a mano: basta leggere lo stile dal tema con Theme.of(context).textTheme.

    Text('Titolo', style: Theme.of(context).textTheme.headlineMedium)
    

    Se ti serve una piccola variazione (ad esempio il colore), usa copyWith sullo stile del tema, così mantieni font e dimensioni coerenti:

    style: Theme.of(context).textTheme.bodyMedium?.copyWith(
      color: Theme.of(context).colorScheme.primary,
    )
    

    Per risparmiare righe, salva final text = Theme.of(context).textTheme; all'inizio del metodo build. Riscriviamo la home usando solo stili del tema.

    class HomePage extends StatelessWidget {
      const HomePage({super.key});
    
      @override
      Widget build(BuildContext context) {
        final text = Theme.of(context).textTheme;
        final colors = Theme.of(context).colorScheme;
    
        return Scaffold(
          appBar: AppBar(title: const Text('Tipografia')),
          body: ListView(
            padding: const EdgeInsets.all(24),
            children: [
              Text('Benvenuto in Flutter', style: text.headlineMedium),
              const SizedBox(height: 4),
              Text(
                'Guida alla tipografia',
                style: text.titleMedium?.copyWith(color: colors.primary),
              ),
              const SizedBox(height: 16),
              Text(
                'Usando TextTheme definisci gli stili una sola volta e li riusi '
                'in tutta l\'app. Cambiare font diventa una modifica di una riga.',
                style: text.bodyMedium,
              ),
              const SizedBox(height: 24),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text('Card di esempio', style: text.titleMedium),
                      const SizedBox(height: 8),
                      Text('Contenuto secondario della card.', style: text.bodySmall),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 24),
              FilledButton(
                onPressed: () {},
                child: const Text('Continua'),
              ),
            ],
          ),
        );
      }
    }

    Risultato atteso

    La schermata mostra una gerarchia visiva chiara (titolo, sottotitolo colorato, paragrafo, card e bottone) con un solo punto di configurazione per i font.

  6. 6

    Font come asset locali: niente download a runtime

    Scaricare i font a runtime va bene in sviluppo, ma in produzione significa dipendere dalla rete al primo avvio (e sul web può causare un lampo di testo con il font sbagliato). La soluzione è includere i file .ttf nel bundle dell'app.

    1. Scarica la famiglia da fonts.google.com e copia i file in assets/fonts/.
    2. Dichiarali nel pubspec.yaml rispettando l'indentazione (2 spazi, mai tabulazioni).
    3. Disattiva il download a runtime impostando GoogleFonts.config.allowRuntimeFetching = false; nel main: se un font non è tra gli asset, Flutter userà il fallback invece di scaricarlo.

    Dopo aver modificato il pubspec.yaml serve un full restart (non basta l'hot reload) perché gli asset vengano ricaricati.

    Se preferisci non usare google_fonts, puoi anche riferirti al font direttamente con fontFamily: 'Poppins' in un TextStyle: il nome deve corrispondere alla chiave family del pubspec.

    # pubspec.yaml
    flutter:
      uses-material-design: true
      fonts:
        - family: Poppins
          fonts:
            - asset: assets/fonts/Poppins-Regular.ttf
              weight: 400
            - asset: assets/fonts/Poppins-SemiBold.ttf
              weight: 600
            - asset: assets/fonts/Poppins-Bold.ttf
              weight: 700
    
    # --- lib/main.dart ---
    # void main() {
    #   WidgetsFlutterBinding.ensureInitialized();
    #   GoogleFonts.config.allowRuntimeFetching = false;
    #   runApp(const TipografiaApp());
    # }
    #
    # Uso senza google_fonts:
    # Text('Ciao', style: TextStyle(fontFamily: 'Poppins', fontWeight: FontWeight.w600))

    Risultato atteso

    L'app mostra i font corretti anche in modalità aereo, senza alcun download al primo avvio.

  7. 7

    Rispettare l'accessibilità: dimensioni del testo scalabili

    Gli utenti possono ingrandire il testo dalle impostazioni di sistema. Flutter lo gestisce automaticamente tramite il TextScaler: le tue fontSize vengono moltiplicate. Per questo è importante:

    • non bloccare mai lo scaling con TextScaler.noScaling su tutta l'app;
    • evitare altezze fisse sui contenitori di testo: preferisci Padding + contenuto che cresce;
    • usare maxLines e overflow dove serve, ma lasciando spazio sufficiente.

    Se vuoi evitare che testi enormi distruggano il layout, puoi limitare il fattore di scala con TextScaler.linear(...).clamp(...) mantenendo comunque l'accessibilità. Ecco come applicarlo globalmente tramite il builder di MaterialApp.

    Per testare: su Android vai in Impostazioni → Display → Dimensioni carattere, su iOS in Accessibilità → Testo più grande. In alternativa usa il pannello Accessibility dei Flutter DevTools.

    return MaterialApp(
      theme: ThemeData(useMaterial3: true, textTheme: textTheme),
      builder: (context, child) {
        final scaler = MediaQuery.textScalerOf(context).clamp(
          minScaleFactor: 0.9,
          maxScaleFactor: 1.5,
        );
        return MediaQuery.withClampedTextScaling(
          minScaleFactor: 0.9,
          maxScaleFactor: 1.5,
          child: child!,
        );
        // Nota: `scaler` qui sopra mostra l'API equivalente di basso livello,
        // withClampedTextScaling è la via più semplice e consigliata.
      },
      home: const HomePage(),
    );

    Risultato atteso

    Aumentando la dimensione del carattere nelle impostazioni di sistema i testi dell'app crescono, ma senza superare il 150% e senza rompere il layout.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!