Repository navigation
☂️ Reinforcement: Add more basic components to the core framework #97496
Description
Activity
HerrNiklasRaab commented
on Jan 30, 2022 on Jan 30, 2022 · Hidden as outdatedshow commentMore actions- addedin triagePresently being triaged by the triage teamPresently being triaged by the triage team
on Jan 31, 2022 @Hawmex
Thanks for the report. Implementing custom UIs is probably a broad topic. Can you give us a real world example where custom UI you proposed would be helpful rather than using the current implementation ?Reacted by Lasse Rosenow, Trevin Miller, Luca Venir, Francesco Iapicca and Harry Sild- addedwaiting for responseThe Flutter team cannot make further progress on this issue until the original reporter respondsThe Flutter team cannot make further progress on this issue until the original reporter responds
on Jan 31, 2022 Well, decoupling
material.dartfrom Flutter has been also discussed in #91605 and many reasons have somehow been ignored.FWIW, the main reason to not have material be its own package is that we want the out-of-box experience to be quick. That is, people should be able to download Flutter, and immediately start writing an app with pretty buttons.
Yeah, but Flutter kind of already does that for icons and it works, right? There is
uses-material-design: trueandcupertino_iconsin every app (because of the template) and you don't see people complaining it is hard to add material icons to Flutter because most use the template.But the reason for implementing custom UIs is in my opinion an obvious thing. Not all developers and companies want to follow Cupertino or Material design guidelines. And even in their UI architecture, they may want to follow different architecture patterns from
material.dart(for example theming, scaffolding, etc.).I live in Iran and I'd like to use Persian fonts rather than the default Roboto. I followed the documentation but it didn't work. Turned out I had to wrap my page in
Scaffold, but it wasn't mentioned in the docs.Some other reasons:
- Even in
material.dartitself, there are inconsistencies in coding patterns. Opening dialogs is handled through a top-level function while showing a snackbar is handled through anInheritedWidgetpattern. - Flutter has always had to avoid breaking changes in
material.dartand go on with accumulating old stuff and deprecating them for God knows how long. Just take a look at how many parametersThemeDatahas as of today. material.darthas been integrated with the framework itself that you can't make Flutter apps without it. A fine example would be the urge to useScaffoldto load custom fonts in the current theming pattern.
Just decouple
material.dartandcupertino.dartfrom Flutter and give people the option to make their own UI or choose it from external packages including Material and Cupertino. And I'm sure those who love material design would continue to use it in a separate package.Reacted by Lasse Rosenow, tRuNKator, Trevin Miller, Giannis Stergiou, Henry, Francesco Iapicca, Harry Sild, Deepanshu and Sergio Rodrigo- Even in
- removedwaiting for responseThe Flutter team cannot make further progress on this issue until the original reporter respondsThe Flutter team cannot make further progress on this issue until the original reporter responds
on Jan 31, 2022 Keeping it open and labeling as a proposal for further inputs from the team.
/cc @HansMuller
- addedp: material_uimaterial_ui package in flutter/packagesmaterial_ui package in flutter/packagesframeworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.c: proposalA detailed proposal for a change to FlutterA detailed proposal for a change to Flutterand removedin triagePresently being triaged by the triage teamPresently being triaged by the triage team
on Feb 1, 2022 I just found out this similar / related issue: flutter/website#7763
@Hawmex Please confirm if your proposal can be tracked in the linked issue.- addedwaiting for responseThe Flutter team cannot make further progress on this issue until the original reporter respondsThe Flutter team cannot make further progress on this issue until the original reporter responds
on Feb 1, 2022 @darshankawar
About Providing documentation on the undrlying Flutter framework widgets, yes. It can be tracked there.But I also propose getting
material.dartandcupertino.dartout of Flutter and providing them as separate packages.Reacted by _juliotati- removedwaiting for responseThe Flutter team cannot make further progress on this issue until the original reporter respondsThe Flutter team cannot make further progress on this issue until the original reporter responds
on Feb 2, 2022 34 remaining items
Hi everyone!
We've been so heads down on this work, if you haven't been keeping up with the individual issues, pull requests, and design documents, I thought an update was needed here. :)This issue represents one of the 4 pillars of this effort (see parent issue), adding new basic components to the core framework.
During this period that the libraries are still together, we've been primarily focused on refactoring existing design widgets to those basic components where we can (tracked in #53059), but as a result, we already have some new components in the widgets layer! I have updated the OP to cover a lot of the work that has been done in relation to this pillar.
Once we land the libraries in their new packages (hello, material_ui and cupertino_ui 👋 ), we'll continue to work on this pillar of the project to add more to the core. Thanks all, keep the feedback coming!
Reacted by Tirth Patel, Francesco Iapicca, mono — Masayuki Ono, Nikita Barbachov, Hannes Hultergård and Natan Amorim MoraesRegarding the latest update, and the point about buttons.
One thing I always struggle with when implementing custom buttons is the various states (active, disabled, hovered, pressed, etc.). What complicates is further is when the states interact with each other; a button can be disabled and hovered, active and pressed, etc.
I'm not sure if it's even possible for a design agnostic widget, but it would simplify creating buttons a lot.
I think Hixies original blankcanvas design doc had some discussions about buttons, but I can't find that now...
@Hannnes1 Correct! Eg. one often overlooked state for the iOS buttons is a loading state (button becomes disabled and shows a spinner, awaiting a Future to complete)
API proposal: what if the button had a an abstract base class with a generic type for a "state"? That could be an enum/string/int, evaluated like this:
abstract class BaseButton<S> { ... Widget buildState(S state) { ... } ... } enum CustomButtonState { idle, disabled, loading, dancing }; class CustomButton extends BaseButton<CustomButtonState> { ... }
One thing I always struggle with when implementing custom buttons is the various states (active, disabled, hovered, pressed, etc.). What complicates is further is when the states interact with each other; a button can be disabled and hovered, active and pressed, etc.
I'm not sure if it's even possible for a design agnostic widget, but it would simplify creating buttons a lot.
We faced a similar issue when designing Forui's styling system & I do think it's definitely possible using an enhanced
WidgetStateMapsimilar to what is outlined in our design docs for it.@Hannnes1 I've had luck using an InheritedNotifier that passes down widget state.
For example, here's a Button that only defines interactions:
Dartpad: https://dartpad.dev/45ab05be46c7a72a2ee59d52fba1d6a8
(also, I just wrote Button as an example, so make sure to test your own version if you use this approach)
class ButtonExample extends StatelessWidget { const ButtonExample({super.key}); @override Widget build(BuildContext context) { return Center( child: Button( onPressed: () { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Button Pressed!'))); }, behavior: HitTestBehavior.opaque, mouseCursor: WidgetStateProperty.all(SystemMouseCursors.click), child: Builder( builder: (context) { final ButtonState state = Button.of(context)!; final bool isHovered = state.hovered; final bool isPressed = state.pressed; final bool isFocused = state.focused; return AnimatedContainer( duration: const Duration(milliseconds: 50), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( color: isPressed ? Colors.blue.shade700 : isHovered ? Colors.blue.shade600 : Colors.blue.shade500, borderRadius: BorderRadius.circular(8), border: isFocused ? Border.all(color: Colors.yellow, width: 2) : null, ), child: const Text('Button', style: TextStyle(color: Colors.white, fontSize: 16)), ); }, ), ), ); } }
Button
import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; class _ButtonStateScope extends InheritedNotifier<WidgetStatesController> { const _ButtonStateScope({required super.notifier, required super.child}); } extension type ButtonState._(Set<WidgetState> value) { bool get hovered => value.contains(WidgetState.hovered); bool get pressed => value.contains(WidgetState.pressed); bool get focused => value.contains(WidgetState.focused); bool get disabled => value.contains(WidgetState.disabled); Set<WidgetState> get states => value; } class Button extends StatefulWidget { const Button({ super.key, this.onHover, this.onPressed, this.onFocusChange, this.focusNode, this.autofocus = false, this.behavior = HitTestBehavior.deferToChild, this.state, this.mouseCursor, required this.child, }); final ValueChanged<bool>? onHover; final VoidCallback? onPressed; final ValueChanged<bool>? onFocusChange; final FocusNode? focusNode; final bool autofocus; final HitTestBehavior behavior; final Set<WidgetState>? state; final WidgetStateProperty<MouseCursor>? mouseCursor; final Widget child; static ButtonState of(BuildContext context) { final Set<WidgetState> value = context .dependOnInheritedWidgetOfExactType<_ButtonStateScope>() !.notifier !.value; return ButtonState._(value); } @override State<Button> createState() => _ButtonState(); } class _ButtonState extends State<Button> { late final Map<Type, Action<Intent>> _actions = <Type, Action<Intent>>{ ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _handleActivation), ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(onInvoke: _handleActivation), }; Map<Type, GestureRecognizerFactory>? _gestures; DeviceGestureSettings? _gestureSettings; // If a focus node isn't given to the widget, then we have to manage our own. FocusNode? _internalFocusNode; FocusNode get _focusNode => widget.focusNode ?? _internalFocusNode!; final WidgetStatesController _statesController = WidgetStatesController(); bool get isHovered => _isHovered; bool _isHovered = false; set isHovered(bool value) { if (_isHovered != value) { _isHovered = value; _statesController.update(WidgetState.hovered, value); } } bool get isPressed => _isPressed; bool _isPressed = false; set isPressed(bool value) { if (_isPressed != value) { _isPressed = value; _statesController.update(WidgetState.pressed, value); } } bool get isFocused => _isFocused; bool _isFocused = false; set isFocused(bool value) { if (_isFocused != value) { _isFocused = value; _statesController.update(WidgetState.focused, value); } } bool get isEnabled => _isEnabled; bool _isEnabled = false; set isEnabled(bool value) { if (_isEnabled != value) { _isEnabled = value; _statesController.update(WidgetState.disabled, !value); } } @override void initState() { super.initState(); if (widget.focusNode == null) { _internalFocusNode = FocusNode(); } _statesController.value = widget.state ?? <WidgetState>{}; isEnabled = widget.onPressed != null; isFocused = _focusNode.hasPrimaryFocus; } @override void didUpdateWidget(Button oldWidget) { super.didUpdateWidget(oldWidget); if (widget.focusNode != oldWidget.focusNode) { if (widget.focusNode != null) { _internalFocusNode?.dispose(); _internalFocusNode = null; } else { assert(_internalFocusNode == null); _internalFocusNode = FocusNode(); } isFocused = _focusNode.hasPrimaryFocus; } if (widget.state != oldWidget.state && widget.state != null) { _statesController.value = widget.state!; } if (widget.onPressed != oldWidget.onPressed) { if (widget.onPressed == null) { isEnabled = isHovered = isPressed = isFocused = false; } else { isEnabled = true; } } } @override void dispose() { _statesController.dispose(); _internalFocusNode?.dispose(); _internalFocusNode = null; super.dispose(); } void _handleFocusChange([bool? focused]) { isFocused = _focusNode.hasPrimaryFocus; widget.onFocusChange?.call(isFocused); } void _handleActivation([Intent? intent]) { isPressed = false; widget.onPressed?.call(); } void _handleTapDown(TapDownDetails details) { isPressed = true; } void _handleTapUp(TapUpDetails? details) { isPressed = false; widget.onPressed?.call(); } void _handleTapCancel() { isPressed = false; } void _handlePointerExit(PointerExitEvent event) { if (isHovered) { isHovered = false; widget.onHover?.call(false); } } void _handlePointerEnter(PointerEnterEvent event) { if (!isHovered) { isHovered = true; widget.onHover?.call(true); } } void _handleDismiss() { Actions.invoke(context, const DismissIntent()); } @override Widget build(BuildContext context) { final DeviceGestureSettings? newGestureSettings = MediaQuery.maybeGestureSettingsOf(context); if (_gestureSettings != newGestureSettings) { _gestureSettings = newGestureSettings; _gestures = null; } _gestures ??= <Type, GestureRecognizerFactory>{ TapGestureRecognizer: GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( () => TapGestureRecognizer(debugOwner: this), (TapGestureRecognizer instance) { instance ..onTapDown = _handleTapDown ..onTapUp = _handleTapUp ..onTapCancel = _handleTapCancel ..gestureSettings = _gestureSettings; }, ), }; final child = RawGestureDetector( behavior: widget.behavior, gestures: isEnabled ? _gestures! : const <Type, GestureRecognizerFactory>{}, child: widget.child, ); return MergeSemantics( child: Semantics.fromProperties( properties: SemanticsProperties( enabled: isEnabled, onDismiss: isEnabled ? _handleDismiss : null, ), child: Actions( actions: isEnabled ? _actions : <Type, Action<Intent>>{}, child: Focus( autofocus: isEnabled && widget.autofocus, focusNode: _focusNode, canRequestFocus: isEnabled, skipTraversal: !isEnabled, onFocusChange: _handleFocusChange, child: _ButtonStateScope( notifier: _statesController, child: Builder( builder: (context) { final MouseCursor? cursor = widget.mouseCursor?.resolve( Button.of(context)?.states ?? <WidgetState>{}, ); return MouseRegion( onEnter: isEnabled ? _handlePointerEnter : null, onExit: isEnabled ? _handlePointerExit : null, hitTestBehavior: HitTestBehavior.deferToChild, cursor: cursor ?? MouseCursor.defer, child: child, ); }, ), ), ), ), ), ); } }
WidgetState is another good approach (as mentioned above). I've also had luck animating WidgetState like so. Let me know if you need the code for AnimatedBoxDecoration and/or how to implement the surface that consumes it.
Button( onPressed: () { onSectionTap(section); }, decoration: WidgetStateDecoration.fromMap({ WidgetState.pressed: AnimatedBoxDecoration( duration: const Duration(milliseconds: 100), color: Tail.gray200, border: depth < 3 ? border : null, ), WidgetState.any: AnimatedBoxDecoration( duration: const Duration(milliseconds: 100), color: Tail.lightTransparent, border: depth < 3 ? border : null, ), }), //...
The only problem with this approach is that there is no good way to use simulations, and I use simulations a lot.
- added a commit that references this issue
on Jun 12, 2026 - removedtriaged-designTriaged by Design Languages teamTriaged by Design Languages team
on Jul 15, 2026 This issue is marked P1 but has had no recent status updates.
The P1 label indicates high-priority issues that are at the top of the work list. This is the highest priority level a bug can have if it isn't affecting a top-tier customer or breaking the build. Bugs marked P1 are generally actively being worked on unless the assignee is dealing with a P0 bug (or another P1 bug). Issues at this level should be resolved in a matter of months and should have monthly updates on GitHub.
Please consider where this bug really falls in our current priorities, and label it or assign it accordingly. This allows people to have a clearer picture of what work is actually planned. Thanks!
- addedtriaged-designTriaged by Design Languages teamTriaged by Design Languages team
on Jul 15, 2026 What about checkbox? Will we have a RawCheckbox or similar basic component decoupled from Material's
Checkbox?- addedpackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.and removedframeworkflutter/packages/flutter repository. See also f: labels.flutter/packages/flutter repository. See also f: labels.
on Aug 17, 2026 - added a commit that references this issue
on Sep 28, 2026
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
This issue tracks proposals and development for net-new widgets and functionalities to be added to the core widgets library.
This effort is the other side of the coin to our reinforcement work, see the refactoring side as well. While the refactoring issue focuses on moving existing code, this issue is dedicated to identifying and building new, foundational, style-agnostic primitives that are missing from the core widgets library today. The goal is to fill gaps in the core and make it an even more powerful foundation for building any design system.
Note
Some of these may be best informed by a UXR study. Before considering contributing one of these net-new widgets, reach out to the team on Discord to confirm.
Propose a New Core Widget! 💡
What foundational widget or capability do you wish existed in Flutter's core? If you have an idea for a new, un-opinionated "headless" widget that would be broadly useful, this is the place to propose it. Please provide a clear use case and rationale for why it belongs in the core framework.
Last update Mar 4, 2026
So far, we have investigated many of the existing design components to determine if any refactoring can be done before decoupling, moving existing APIs to the widgets layer to a common surface (which is tracked in #53059). In some cases, we have decided not to break widgets in the design libraries to try to accomplish this, which leaves the opportunity for new, more generic APIs to be introduced in the widgets layer later.
As we have been focused on the opportunity to refactor existing widgets (#53059) ahead of actually separating the libraries, expect this work of adding new widgets to continue after the design libraries land in their new packages, which is what this issue is meant to track.
To check out all the issues and aspects of the decoupling effort, see the github project, and the root parent issue: #101479.
Status:
showDialogthat uses a native desktop window to display the content #181861Raw- widgets that pre-date this effort
This is not an exhaustive list of components available in the widgets library, but specifically those with Raw prefixes. 🙂
OP
Hi, It's been 2 months since I've ditched HTML, CSS, and JS for Flutter and I haven't looked back. I love the declarative style of Flutter and Dart itself.What's the problem?
What really bothers me is I find it hard to implement completely Custom UIs. I mean even when I wanted to use a custom font, I had to depend on
material.dartand useScaffoldto make it work. I don't even want to start talking about the frustrations of theming a material app in flutter. But this was not a problem using HTML, CSS, JS. They basically give you simple and minimal elements and then let you do whatever you want with them or use whatever UI package you want, such as Tailwind, MUI, etc.So, It's either a matter of Flutter lacking documentation on Custom UIs or me not reading enough docs about them.
Any other problems?
Well, if you just take a look at the reported issues, they're mostly about
material.dartandcupertino.dart. I feel like Flutter Team is spending most of their time, effort and energy on these two UIs instead of the framework itself. And still, Flutter is far behind from Material 3 spec (and even Material 2, becausematerial.dartuses 2014 typography by default.).My Ideal Flutter
So my ideal is:
GestureDetector,StatelessWidget,StatefulWidget, etc.). These can be currently found inwidgets.dart. This way, Flutter Team's efforts are concentrated on the framework itself.materialandcupertinoshould come in seperate packages (and export the main Flutter package) and be maintained by Flutter community more.materialorcupertino. This way more people will know how underlying widgets ofmaterialandcupertinowork and can take part in maintaining them or even make their own UIs in their apps and packages.This way we will be able to see more Flutter UI packages and a more diverse set of Flutter apps in terms of UI on the market.
After all, these are just my ideals and may be far from reality but I really like to see some discussion on this matter and finding the best solution to it.