Skip to content

Card.outlined doesn't apply border if custom CardThemeData applied (even without side parameter) #178611

Closed

Description

@Nazacheres

Steps to reproduce

Apply clean custom Theme with useMaterial3 true, and following cardTheme:

const CardThemeData(
          elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(sizeCornerMedium)))),

Notice, how side is not applied.

Use

Card.outlined(clipBehavior: Clip.hardEdge, child: SizedBox(width: 100, height: 100, child: Image.asset('assets/map_styles/map_style_satelite_256.png'))),

to build a card with image inside, clipped and with default outline border.

Expected results

See a card with custom corners and 0 elevation (per our custom theme), image inside and border of 1 according to Material 3 and Card.outlined documentation.

Actual results

Card without outlined border.
Worth mentioning: removing the custom CardThemeData, brings back the border (but our custom rounded corners are gone expectedly as well).

Code sample

Code sample
ThemeData(
      useMaterial3: true,
      colorScheme: scheme,
      typography: typography,
      cardTheme: const CardThemeData(
          elevation: 0, shape: RoundedRectangleBorder(BorderRadius.all(Radius.circular(sizeCornerMedium)))),
    );

.... 
Row(
                  children: [
                    Card.outlined(
                      clipBehavior: Clip.hardEdge,
                      child: SizedBox(width: 100, height: 100, child: Image.asset('assets/map_styles/map_style_satelite_256.png')),
                    ),
...

Screenshots or Video

No response

Logs

No response

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.38.1, on macOS 26.1 25B78 darwin-arm64, locale en-CZ) [274ms]
    • Flutter version 3.38.1 on channel stable at /Users/nazariiovcharchyn/fvm/versions/stable
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision b45fa18946 (3 days ago), 2025-11-12 22:09:06 -0600
    • Engine revision b5990e5ccc
    • Dart version 3.10.0
    • DevTools version 2.51.1
    • Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, omit-legacy-version-file, enable-lldb-debugging

[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [1,145ms]
    • Android SDK at /Users/nazariiovcharchyn/Library/Android/sdk
    • Emulator version 36.2.12.0 (build_id 14214601) (CL:N/A)
    • Platform android-36, build-tools 36.1.0
    • ANDROID_HOME = /Users/nazariiovcharchyn/Library/Android/sdk
    • 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.8+-14018985-b1038.68)
    • All Android licenses accepted.

[!] Xcode - develop for iOS and macOS (Xcode 26.1.1) [717ms]
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 17B100
    ! iOS 26.1 Simulator not installed; this may be necessary for iOS and macOS development.
      To download and install the platform, open Xcode, select Xcode > Settings > Components,
      and click the GET button for the required platform.

      For more information, please visit:
        https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes
    ! CocoaPods 1.15.2 out of date (1.16.2 is recommended).
        CocoaPods is a package manager for iOS or macOS platform code.
        Without CocoaPods, plugins will not work on iOS or macOS.
        For more info, see https://flutter.dev/to/platform-plugins
      To update CocoaPods, see https://guides.cocoapods.org/using/getting-started.html#updating-cocoapods

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

[✓] Connected device (3 available) [6.1s]
    • Pixel 8 Pro (mobile) • 37290DLJG00128 • android-arm64  • Android 16 (API 36)
    • macOS (desktop)      • macos          • darwin-arm64   • macOS 26.1 25B78 darwin-arm64
    • Chrome (web)         • chrome         • web-javascript • Google Chrome 142.0.7444.162

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

Activity

  1. tirth-patel-nc commented on Nov 17, 2025

    @tirth-patel-nc
    Member

    @Nazacheres thanks for the report. as per the docs, it seems to be working as intended. please let me know if I misunderstood your query.

    abstract class OutlinedBorder extends ShapeBorder {
      /// Abstract const constructor. This constructor enables subclasses to provide
      /// const constructors so that they can be used in const expressions.
      const OutlinedBorder({this.side = BorderSide.none});
    
      /// The border outline's color and weight.
      ///
      /// If [side] is [BorderSide.none], which is the default, an outline is not drawn.
      /// Otherwise the outline is centered over the shape's boundary.
      final BorderSide side;
    
       /// ...
    }
  2. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Nov 17, 2025
  3. Nazacheres commented on Nov 17, 2025

    @Nazacheres
    Author

    @tirth-patel-nc hm, seems like we are looking into different parts of documentation. Here's how I follow my usage:
    Card.outlined( ->
    has followig constructor:

      const Card.outlined({
        super.key,
        this.color,
        this.shadowColor,
        this.surfaceTintColor,
        this.elevation,
        this.shape,
        this.borderOnForeground = true,
        this.margin,
        this.clipBehavior,
        this.child,
        this.semanticContainer = true,
      }) : assert(elevation == null || elevation >= 0.0),
           _variant = _CardVariant.outlined;
    

    the first place the _variant variable is used is in build method of the card:

    
      @override
      Widget build(BuildContext context) {
        final CardThemeData cardTheme = CardTheme.of(context);
        final CardThemeData defaults;
        if (Theme.of(context).useMaterial3) {
          defaults = switch (_variant) {
            _CardVariant.elevated => _CardDefaultsM3(context),
            _CardVariant.filled => _FilledCardDefaultsM3(context),
            _CardVariant.outlined => _OutlinedCardDefaultsM3(context),
          };
        } else {
          defaults = _CardDefaultsM2(context);
        }
    ...
    

    so later these default are used here in build method return:

        return Semantics(
          container: semanticContainer,
          child: Padding(
            padding: margin ?? cardTheme.margin ?? defaults.margin!,
            child: Material(
    ...          
    shape: shape ?? cardTheme.shape ?? defaults.shape,
    ...
    

    so if I read this correctly
    shape - is the direct parameter of the Card constructor - makes sense, if you want to override - go for it in-line
    cardTheme.shape - that's my theme, where I have custom elevation and corner radius - ok
    defaults.shape - my variant's default shape induced by the constructor I used in place - ok

    accroding to the variants docs:

    /// Material Design 3 introduced new types of cards. The default [Card] is the
    /// elevated card. To create a filled card, use [Card.filled]; to create a outlined
    /// card, use [Card.outlined].
    /// {@tool dartpad}
    /// This sample shows creation of [Card] widgets for elevated, filled and
    /// outlined types, as described in: https://m3.material.io/components/cards/overview
    ///
    

    therefore my understanding is that current logic and order of priority of shape application is incorrect cause instead of "adding the outline" the logic is "adding the outline, unless you have card theme applied".

    Please correct me if I am wrong.

  4. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Nov 17, 2025
  5. tirth-patel-nc commented on Nov 18, 2025

    @tirth-patel-nc
    Member

    the card theme shape completely overrides the outlined variant shape, so the default outline is removed unless you explicitly add a side. as shown below, you only get the border back when you specify shape.side yourself. given this behavior, does it seem correct that cardthemedata.shape fully replaces the outlined card defaults instead of merging with them?

    Without CardThemeData.shape.side
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const App());
    }
    
    class App extends StatelessWidget {
      const App({super.key});
    
      @override
      Widget build(BuildContext context) {
        const sizecornermedium = 12.0;
    
        final scheme = ColorScheme.fromSeed(seedColor: Colors.blue);
        final typography = Typography.material2021();
    
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Card.outlined(
                    clipBehavior: Clip.hardEdge,
                    child: SizedBox(
                      width: 100,
                      height: 100,
                      child: Image.asset('assets/test.webp'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          theme: ThemeData(
            useMaterial3: true,
            colorScheme: scheme,
            typography: typography,
            cardTheme: CardThemeData(
              elevation: 0,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(sizecornermedium)),
              ),
            ),
          ),
        );
      }
    }
    Image
    With CardThemeData.shape.side
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const App());
    }
    
    class App extends StatelessWidget {
      const App({super.key});
    
      @override
      Widget build(BuildContext context) {
        const sizecornermedium = 12.0;
    
        final scheme = ColorScheme.fromSeed(seedColor: Colors.blue);
        final typography = Typography.material2021();
    
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Card.outlined(
                    clipBehavior: Clip.hardEdge,
                    child: SizedBox(
                      width: 100,
                      height: 100,
                      child: Image.asset('assets/test.webp'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          theme: ThemeData(
            useMaterial3: true,
            colorScheme: scheme,
            typography: typography,
            cardTheme: CardThemeData(
              elevation: 0,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.all(Radius.circular(sizecornermedium)),
                side: BorderSide(color: scheme.outline, width: 1),
              ),
            ),
          ),
        );
      }
    }
    Image
    Without CardThemeData
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const App());
    }
    
    class App extends StatelessWidget {
      const App({super.key});
    
      @override
      Widget build(BuildContext context) {
        const sizecornermedium = 12.0;
    
        final scheme = ColorScheme.fromSeed(seedColor: Colors.blue);
        final typography = Typography.material2021();
    
        return MaterialApp(
          home: Scaffold(
            body: Center(
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Card.outlined(
                    clipBehavior: Clip.hardEdge,
                    child: SizedBox(
                      width: 100,
                      height: 100,
                      child: Image.asset('assets/test.webp'),
                    ),
                  ),
                ],
              ),
            ),
          ),
          theme: ThemeData(
            useMaterial3: true,
            colorScheme: scheme,
            typography: typography,
          ),
        );
      }
    }
    Image
  6. added
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Nov 18, 2025
  7. Nazacheres commented on Nov 18, 2025

    @Nazacheres
    Author

    @tirth-patel-nc thanks for a fast response, I don't want to waste your time and clearly there's a workaround, so if we don't understand each other, lets just close, I'll try last time:

    The examples you provided make sense, where existing logic falls apart is when you do custom theme, but don't override the shape - Card.outlined doesn't apply border in that case.

    ThemeData(
            useMaterial3: true,
            colorScheme: scheme,
            typography: typography,
            cardTheme: CardThemeData(
              elevation: 0,
            ),
          ),
        );
    

    Therefore, you are probably right - I can't point at part of the documenation, which would define this as a bug, but this
    shape: shape ?? cardTheme.shape ?? defaults.shape, seems to be

    1. Incosistent - other *ThemeData class properties work like that - unless you override them the default material values are applied. But here, the mere existence of the card theme, overrides the default outline shape - which is 1 width of the border all around the card.
    2. Limiting - unless there's a dedicated OutlineCardThemeData, the mere existance of custom CardThemeData renders custom card constructors unusable.

    If you do not agree, feel free to close this. If you do, let me know how I can help to move this forward.
    Thanky you for your time.

  8. removed
    waiting for responseThe Flutter team cannot make further progress on this issue until the original reporter responds
    on Nov 18, 2025
  9. tirth-patel-nc commented on Nov 19, 2025

    @tirth-patel-nc
    Member

    shape property needs a ShapeBorder and in example we use RoundedRectangleBorder that extends the OutlinedBorder and its Docs says:

      /// Abstract const constructor. This constructor enables subclasses to provide
      /// const constructors so that they can be used in const expressions.
      const OutlinedBorder({this.side = BorderSide.none});

    It seems to be WAI. I'll keep it open as documentation issue if there's a room for improvement.

  10. added
    d: api docsIssues with https://api.flutter.dev/
    f: themingStyling widgets with a theme
    p: material_uimaterial_ui package in flutter/packages
    team-designOwned by Design Languages team
    frameworkflutter/packages/flutter repository. See also f: labels.
    and removed
    in triagePresently being triaged by the triage team
    on Nov 19, 2025
  11. added
    P2Important issues not at the top of the work list
    triaged-designTriaged by Design Languages team
    on Nov 19, 2025
  12. dkwingsmt commented on Nov 19, 2025

    @dkwingsmt
    Contributor

    I agree that this is WAI. The border configuration is part of shape and will be overridden by the same value either the property or the theme as a whole.

    The following snippet reproduces the problem and compares various usages side by side.

    Image
    Repro code
    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const App());
    }
    
    class App extends StatelessWidget {
      const App({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(theme: ThemeData.dark(), home: const Demo());
      }
    }
    
    class Demo extends StatelessWidget {
      const Demo({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Theme(
            data: ThemeData(
              useMaterial3: true,
            ),
            child: Column(
              children: [
                const Text('M3 Card.outlined - default:'),
                const Card.outlined(
                  clipBehavior: Clip.hardEdge,
                  child: SizedBox(
                      width: 100,
                      height: 100,
                      child: ColoredBox(color: Color(0xFFAA3300))),
                ),
                const Text('M3 Card.outlined with theme shape:'),
                Theme(
                  data: ThemeData(
                    cardTheme: const CardThemeData(
                        elevation: 0,
                        shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.all(Radius.circular(10)))),
                  ),
                  child: const Card.outlined(
                    clipBehavior: Clip.hardEdge,
                    child: SizedBox(
                        width: 100,
                        height: 100,
                        child: ColoredBox(color: Color(0xFFAA3300))),
                  ),
                ),
                const Text('M3 Card.outlined with theme shape that defines side:'),
                Theme(
                  data: ThemeData(
                    cardTheme: const CardThemeData(
                        elevation: 0,
                        shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.all(Radius.circular(10)),
                            side: BorderSide(color: Colors.white))),
                  ),
                  child: const Card.outlined(
                    clipBehavior: Clip.hardEdge,
                    child: SizedBox(
                        width: 100,
                        height: 100,
                        child: ColoredBox(color: Color(0xFFAA3300))),
                  ),
                ),
    
                const Text('M3 Card.outlined with property shape:'),
                const Card.outlined(
                  clipBehavior: Clip.hardEdge,
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(Radius.circular(10))),
                  child: SizedBox(
                      width: 100,
                      height: 100,
                      child: ColoredBox(color: Color(0xFFAA3300))),
                ),
                const Text('M3 Card.outlined with property shape that defines side:'),
                const Card.outlined(
                  clipBehavior: Clip.hardEdge,
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(Radius.circular(10)),
                      side: BorderSide(color: Colors.white)),
                  child: SizedBox(
                      width: 100,
                      height: 100,
                      child: ColoredBox(color: Color(0xFFAA3300))),
                ),
    
                const Text('To recreate with the default color:'),
                Builder(
                  builder: (BuildContext context) => Card.outlined(
                    clipBehavior: Clip.hardEdge,
                    shape: RoundedRectangleBorder(
                        borderRadius: const BorderRadius.all(Radius.circular(10)),
                        side: BorderSide(
                            color: ColorScheme.of(context).outlineVariant)),
                    child: const SizedBox(
                        width: 100,
                        height: 100,
                        child: ColoredBox(color: Color(0xFFAA3300))),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    

    I agree that we can use a clearer documentation. Thanks for the discussion and I'll make a PR soon.

  13. github-actions commented on Jan 1, 2026

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  14. locked as resolved and limited conversation to collaborators on Jan 1, 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 listd: api docsIssues with https://api.flutter.dev/d: examplesSample code and demosf: themingStyling widgets with a themeframeworkflutter/packages/flutter repository. See also f: labels.p: material_uimaterial_ui package in flutter/packagesr: fixedIssue is closed as already fixed in a newer versionteam-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