Flutter Widgets Fundamentals

Text & Typography Widgets

45 min Lesson 3 of 18

The Text Widget

The Text widget is one of the most commonly used widgets in Flutter. It displays a string of text with a single style. For more complex text with multiple styles, Flutter provides RichText and Text.rich.

Basic Text Widget

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return const Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('Simple text with default styling'),
        Text(
          'Bold and larger text',
          style: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
          ),
        ),
        Text(
          'Colored italic text',
          style: TextStyle(
            fontSize: 18,
            fontStyle: FontStyle.italic,
            color: Colors.blue,
          ),
        ),
      ],
    );
  }
}

TextStyle — Complete Styling Control

TextStyle gives you full control over how text appears. Here are the most important properties:

TextStyle Properties

Text(
  'Styled Text Example',
  style: TextStyle(
    fontSize: 20,                    // Size in logical pixels
    fontWeight: FontWeight.w600,     // 100 to 900 (bold = 700)
    fontStyle: FontStyle.italic,     // normal or italic
    color: Colors.deepPurple,        // Text color
    backgroundColor: Colors.yellow.shade100, // Background highlight
    letterSpacing: 2.0,              // Space between letters
    wordSpacing: 4.0,               // Space between words
    height: 1.5,                     // Line height multiplier
    decoration: TextDecoration.underline,    // Underline, lineThrough, overline
    decorationColor: Colors.red,     // Decoration line color
    decorationStyle: TextDecorationStyle.wavy, // solid, double, dotted, dashed, wavy
    decorationThickness: 2.0,        // Thickness of decoration
    shadows: [
      Shadow(
        color: Colors.black26,
        offset: Offset(2, 2),
        blurRadius: 4,
      ),
    ],
  ),
)

FontWeight Scale

Flutter provides named constants for font weights, ranging from thin (100) to black (900):

Font Weight Examples

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Text('Thin (w100)', style: TextStyle(fontWeight: FontWeight.w100)),
    Text('Light (w300)', style: TextStyle(fontWeight: FontWeight.w300)),
    Text('Regular (w400)', style: TextStyle(fontWeight: FontWeight.w400)),
    Text('Medium (w500)', style: TextStyle(fontWeight: FontWeight.w500)),
    Text('SemiBold (w600)', style: TextStyle(fontWeight: FontWeight.w600)),
    Text('Bold (w700)', style: TextStyle(fontWeight: FontWeight.bold)),
    Text('ExtraBold (w800)', style: TextStyle(fontWeight: FontWeight.w800)),
    Text('Black (w900)', style: TextStyle(fontWeight: FontWeight.w900)),
  ],
)

TextAlign & TextDirection

Control how text is aligned within its container using textAlign. Note that alignment works relative to the text’s available width — the Text widget must have a defined width (or be in an expanded layout).

Text Alignment

SizedBox(
  width: double.infinity,
  child: Column(
    children: [
      Text('Left aligned', textAlign: TextAlign.left),
      Text('Center aligned', textAlign: TextAlign.center),
      Text('Right aligned', textAlign: TextAlign.right),
      Text(
        'Justified text spreads words evenly across the full width '
        'of the container making both edges align neatly.',
        textAlign: TextAlign.justify,
      ),
    ],
  ),
)

TextOverflow & maxLines

When text is too long for its container, you can control how it overflows using overflow and maxLines:

Handling Text Overflow

Column(
  children: [
    // Ellipsis at end
    Text(
      'This is a very long text that will be truncated with an ellipsis at the end when it overflows its container',
      maxLines: 1,
      overflow: TextOverflow.ellipsis,
    ),

    // Fade out
    Text(
      'This text fades out at the edge when it overflows its container boundary',
      maxLines: 1,
      overflow: TextOverflow.fade,
      softWrap: false,
    ),

    // Clip text
    Text(
      'This text is clipped when it exceeds the available space',
      maxLines: 1,
      overflow: TextOverflow.clip,
    ),

    // Multi-line with limit
    Text(
      'This is a longer paragraph that will show up to two lines '
      'of text and then display an ellipsis if there is still '
      'more content to show beyond those two lines.',
      maxLines: 2,
      overflow: TextOverflow.ellipsis,
    ),
  ],
)
Tip: TextOverflow.ellipsis is the most commonly used overflow style in production apps. It clearly signals to users that there is more content. Combine it with maxLines to control exactly how many lines are shown.

RichText & TextSpan

When you need to display text with multiple styles in a single paragraph, use RichText or the shorthand Text.rich. The TextSpan class represents a section of text with its own style.

RichText with Multiple Styles

// Using Text.rich (preferred shorthand)
Text.rich(
  TextSpan(
    style: const TextStyle(fontSize: 16, color: Colors.black87),
    children: [
      const TextSpan(text: 'Flutter is '),
      TextSpan(
        text: 'beautiful',
        style: TextStyle(
          color: Colors.blue.shade700,
          fontWeight: FontWeight.bold,
        ),
      ),
      const TextSpan(text: ', '),
      TextSpan(
        text: 'fast',
        style: TextStyle(
          color: Colors.green.shade700,
          fontWeight: FontWeight.bold,
        ),
      ),
      const TextSpan(text: ', and '),
      TextSpan(
        text: 'productive',
        style: TextStyle(
          color: Colors.orange.shade700,
          fontWeight: FontWeight.bold,
          decoration: TextDecoration.underline,
        ),
      ),
      const TextSpan(text: '.'),
    ],
  ),
)

// Using RichText directly
RichText(
  text: TextSpan(
    style: DefaultTextStyle.of(context).style,
    children: const [
      TextSpan(text: 'Price: '),
      TextSpan(
        text: '\$29.99',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Colors.green,
        ),
      ),
      TextSpan(
        text: ' \$49.99',
        style: TextStyle(
          decoration: TextDecoration.lineThrough,
          color: Colors.grey,
        ),
      ),
    ],
  ),
)
Note: When using RichText directly, it does not inherit the default text style from the widget tree. You must manually set the base style. The Text.rich shorthand inherits the default style automatically, making it the preferred approach in most cases.

SelectableText

By default, text in Flutter is not selectable. Use SelectableText when you want users to be able to select and copy text:

Selectable Text Widgets

Column(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    // Basic selectable text
    const SelectableText(
      'You can select and copy this text by long pressing on it.',
      style: TextStyle(fontSize: 16),
    ),

    const SizedBox(height: 16),

    // Selectable with custom cursor and toolbar
    SelectableText(
      'API Key: sk-abc123def456ghi789',
      style: const TextStyle(
        fontFamily: 'monospace',
        fontSize: 14,
        backgroundColor: Color(0xFFF5F5F5),
      ),
      cursorColor: Colors.blue,
      showCursor: true,
      onTap: () => debugPrint('Text tapped'),
    ),

    const SizedBox(height: 16),

    // Rich selectable text
    const SelectableText.rich(
      TextSpan(
        children: [
          TextSpan(text: 'Status: '),
          TextSpan(
            text: 'Active',
            style: TextStyle(
              color: Colors.green,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    ),
  ],
)

DefaultTextStyle

DefaultTextStyle sets a default text style for all Text widgets in its subtree. This is useful for applying consistent styling to a group of text widgets without repeating the style on each one.

DefaultTextStyle Usage

DefaultTextStyle(
  style: const TextStyle(
    fontSize: 16,
    color: Colors.black87,
    height: 1.6,
  ),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      // All these inherit the default style
      const Text('First paragraph with default styling.'),
      const SizedBox(height: 8),
      const Text('Second paragraph inherits the same style.'),
      const SizedBox(height: 8),
      // Override specific properties
      Text(
        'Bold text that also inherits base style.',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Colors.blue.shade800,
        ),
      ),
    ],
  ),
)

Google Fonts Package

The google_fonts package provides access to over 1,000 fonts from Google Fonts. Add it to your pubspec.yaml and use it directly in your styles.

Using Google Fonts

// pubspec.yaml
// dependencies:
//   google_fonts: ^6.1.0

import 'package:google_fonts/google_fonts.dart';

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

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'Roboto Slab Heading',
          style: GoogleFonts.robotoSlab(
            fontSize: 28,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 8),
        Text(
          'Lato for body text looks clean and modern.',
          style: GoogleFonts.lato(
            fontSize: 16,
            height: 1.6,
          ),
        ),
        const SizedBox(height: 8),
        Text(
          'var code = "monospace";',
          style: GoogleFonts.firaCode(
            fontSize: 14,
            color: Colors.green.shade800,
            backgroundColor: Colors.grey.shade100,
          ),
        ),
        const SizedBox(height: 8),
        Text(
          'Elegant Display Font',
          style: GoogleFonts.playfairDisplay(
            fontSize: 32,
            fontStyle: FontStyle.italic,
          ),
        ),
      ],
    );
  }
}

// Apply Google Fonts to entire theme
MaterialApp(
  theme: ThemeData(
    textTheme: GoogleFonts.interTextTheme(),
  ),
)
Tip: For production apps, consider using GoogleFonts.config.allowRuntimeFetching = false; and bundling fonts as assets. This prevents runtime font downloads and ensures fonts are always available offline.

Practical Example: Styled Article

Let’s combine everything to build a beautifully styled article widget:

Styled Article Widget

class StyledArticle extends StatelessWidget {
  final String title;
  final String author;
  final String date;
  final String body;
  final String category;

  const StyledArticle({
    super.key,
    required this.title,
    required this.author,
    required this.date,
    required this.body,
    required this.category,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // Category badge
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
            decoration: BoxDecoration(
              color: Colors.blue.shade50,
              borderRadius: BorderRadius.circular(16),
            ),
            child: Text(
              category.toUpperCase(),
              style: TextStyle(
                fontSize: 11,
                fontWeight: FontWeight.w700,
                color: Colors.blue.shade700,
                letterSpacing: 1.2,
              ),
            ),
          ),
          const SizedBox(height: 12),
          // Title
          Text(
            title,
            style: const TextStyle(
              fontSize: 28,
              fontWeight: FontWeight.w800,
              height: 1.2,
              letterSpacing: -0.5,
            ),
          ),
          const SizedBox(height: 12),
          // Author and date
          Text.rich(
            TextSpan(
              style: TextStyle(fontSize: 14, color: Colors.grey.shade600),
              children: [
                const TextSpan(text: 'By '),
                TextSpan(
                  text: author,
                  style: const TextStyle(fontWeight: FontWeight.w600),
                ),
                TextSpan(text: ' \u2022 \$date'),
              ],
            ),
          ),
          const SizedBox(height: 20),
          const Divider(),
          const SizedBox(height: 20),
          // Body text
          Text(
            body,
            style: TextStyle(
              fontSize: 16,
              height: 1.8,
              color: Colors.grey.shade800,
            ),
            textAlign: TextAlign.justify,
          ),
        ],
      ),
    );
  }
}

Practical Example: Rich Text Bio

Build a bio section with mixed styles, links, and interactive spans:

Rich Text Bio Widget

class RichTextBio extends StatelessWidget {
  final String name;
  final String role;
  final String bio;
  final List<String> skills;

  const RichTextBio({
    super.key,
    required this.name,
    required this.role,
    required this.bio,
    required this.skills,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 10,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            name,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 4),
          Text(
            role,
            style: TextStyle(
              fontSize: 16,
              color: Colors.blue.shade600,
              fontWeight: FontWeight.w500,
            ),
          ),
          const SizedBox(height: 16),
          Text(
            bio,
            style: TextStyle(
              fontSize: 15,
              height: 1.7,
              color: Colors.grey.shade700,
            ),
          ),
          const SizedBox(height: 16),
          Text.rich(
            TextSpan(
              style: const TextStyle(fontSize: 14),
              children: [
                const TextSpan(
                  text: 'Skills: ',
                  style: TextStyle(fontWeight: FontWeight.w600),
                ),
                ...skills.asMap().entries.map((entry) => TextSpan(
                  children: [
                    TextSpan(
                      text: entry.value,
                      style: TextStyle(
                        color: Colors.blue.shade700,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                    if (entry.key < skills.length - 1)
                      const TextSpan(text: ' \u2022 '),
                  ],
                )),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Summary

In this lesson, you learned:

  • The Text widget displays single-style text with full TextStyle control
  • TextStyle properties include fontSize, fontWeight, color, letterSpacing, height, decoration, and shadows
  • TextAlign controls horizontal alignment; TextOverflow and maxLines handle overflow
  • RichText and Text.rich with TextSpan enable multi-styled text in a single widget
  • SelectableText allows users to select and copy text content
  • DefaultTextStyle applies consistent styling to all Text widgets in a subtree
  • The google_fonts package provides access to 1,000+ fonts
What’s Next: In the next lesson, we will explore Image and Icon widgets, learning how to display images from various sources and use icons effectively in your Flutter apps.