Skip to content

[Material 3] Support 24dp minimum width for Primary TabBar active indicator and allow theming primary vs. secondary tab indicators in TabBarThemeData #194716

Open

Description

@mmohankrishna-lab

Steps to reproduce

  1. Create a Material 3 Flutter app (useMaterial3: true) with both a primary TabBar (containing a short tab label such as Tab(text: 'A') and indicatorColor: Colors.red) and a TabBar.secondary.
  2. Observe that by default, selecting the short Tab(text: 'A') in the primary TabBar renders an active indicator narrower than the 24dp minimum width specified by the Material 3 Tabs specification.
  3. Configure a custom indicator in ThemeData(tabBarTheme: TabBarThemeData(indicator: ...)) to customize the primary tab active indicator (e.g., with 3dp thickness, 3dp top corner radius, or custom width/insets).
  4. Run the sample code below and observe:
    • The TabBar.secondary widget also inherits tabBarTheme.indicator (rendering with a 3dp rounded indicator instead of its default 2dp flat full-width underline).
    • The indicatorColor: Colors.red property on the TabBar is ignored because _TabBarState._getIndicator returns tabBarTheme.indicator! before checking widget._isPrimary or widget.indicatorColor.

Expected results

  1. In a Material 3 primary TabBar (TabBar(...)), short tab labels narrower than 24dp (e.g., Tab(text: 'A')) should still render an active indicator with a minimum width of 24dp centered under the label, per the Material 3 Tabs specification
  2. When a custom primary tab indicator is configured in ThemeData(tabBarTheme: TabBarThemeData(indicator: ...)), TabBar.secondary should retain its Material 3 secondary tab default (2dp flat full-width underline) instead of inheriting the primary tab indicator, and TabBar(indicatorColor: ...) overrides should still be respected.

Actual results

  1. By default in Material 3, a primary TabBar with a short label (e.g., Tab(text: 'A')) renders an UnderlineTabIndicator narrower than 24dp because UnderlineTabIndicator does not support or enforce a 24dp minimum width.
  2. When setting TabBarThemeData(indicator: ...) in ThemeData to customize the primary tab indicator:
    a. _TabBarState._getIndicator returns tabBarTheme.indicator! before checking widget._isPrimary or widget.indicatorColor.
    b. TabBar.secondary unexpectedly inherits the 3dp rounded primary indicator instead of its 2dp flat full-width underline.
    c. TabBar(indicatorColor: Colors.red) is ignored and continues to paint with the theme indicator's color.

Code sample

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
        // Setting a custom indicator in TabBarThemeData to style primary tabs:
        tabBarTheme: const TabBarThemeData(
          indicator: UnderlineTabIndicator(
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(3),
              topRight: Radius.circular(3),
            ),
            borderSide: BorderSide(width: 3, color: Colors.blue),
            insets: EdgeInsets.symmetric(horizontal: 2),
          ),
        ),
      ),
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
          appBar: AppBar(
            title: const Text('TabBar Indicator Repro'),
            // 1. Primary TabBar with a short label ('A') and custom indicatorColor:
            // - Without tabBarTheme.indicator, 'A' has an indicator narrower than 24dp.
            // - With tabBarTheme.indicator, indicatorColor: Colors.red is ignored.
            bottom: const TabBar(
              indicatorColor: Colors.red,
              tabs: [
                Tab(text: 'A'),
                Tab(text: 'Medium'),
                Tab(text: 'Longer Tab'),
              ],
            ),
          ),
          body: const Column(
            children: [
              SizedBox(height: 24),
              Text('Secondary TabBar (should have 2dp flat full-width indicator):'),
              // 2. Secondary TabBar unexpectedly inherits the 3dp rounded indicator
              // from tabBarTheme.indicator instead of a 2dp flat underline.
              TabBar.secondary(
                tabs: [
                  Tab(text: 'Overview'),
                  Tab(text: 'Specifications'),
                  Tab(text: 'Reviews'),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Screenshots or Video

Image

Logs

Logs
[Paste your logs here]

Flutter Doctor output

[✓] Flutter (Channel master, 3.50.0-1.0.pre-58, on macOS 26.7.1 25G241 darwin-arm64, locale en-US) [4.9s]
• Flutter version 3.50.0-1.0.pre-58 on channel master at /opt/homebrew/share/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision de8353d (4 days ago), 2026-10-15 05:44:18 +0000
• Engine revision de8353d
• Dart version 3.15.0 (build 3.15.0-32.0.dev)
• DevTools version 2.61.0
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android,
enable-ios, cli-animations, enable-native-assets, enable-record-use, enable-swift-package-manager,
omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration, enable-riscv64, enable-hcpp

[✓] Android toolchain - develop for Android devices (Android SDK version 36.0.0) [1,762ms]
• Android SDK at /Users/mmohankrishna/Library/Android/sdk
• Emulator version 36.6.11.0 (build_id 15507667) (CL:N/A)
• Platform android-36.1, build-tools 36.0.0
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: flutter config --jdk-dir="path/to/jdk".
• Java version OpenJDK Runtime Environment (build 21.0.10+-117844308-b1163.108)
• All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.6) [2.2s]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 17F113
• CocoaPods version 1.17.0

[✓] Chrome - develop for the web [6ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (3 available) [7.5s]
• sdk gphone16k arm64 (mobile) • emulator-5554 • android-arm64 • Android 17 (API 37) (emulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 26.7.1 25G241 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 155.0.8059.84

[✓] Network resources [492ms]
• All expected network resources are available.

• No issues found!

Activity

  1. added
    in triagePresently being triaged by the triage team
    p: material_uimaterial_ui package in flutter/packages
    packageflutter/packages repository. See also p: labels.
    team-designOwned by Design Languages team
    and removed
    in triagePresently being triaged by the triage team
    on Oct 19, 2026
  2. mbcorona commented on Oct 19, 2026

    @mbcorona
    Member

    Hi @mmohankrishna-lab, thanks for the report and the PR. The _getIndicator ordering checks out on master, and the 24dp minimum is a fair M3 gap to close.
    Routing to team-design.

  3. added
    triaged-designTriaged by Design Languages team
    P2Important issues not at the top of the work list
    on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listp: material_uimaterial_ui package in flutter/packagespackageflutter/packages repository. See also p: labels.team-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions