iTranslated by AI
Deep Dive into Buttons for a Better User Experience
Out of the blue, what do you think is the best thing about personal development?
For me, the attraction lies in the fact that it's easy to take on technical challenges.
In professional development, output must be the top priority, so you are required to achieve things cost-effectively.
However, in personal development, there are no deadlines, and you can decide the development priorities yourself.
What is covered in this article
- Buttons considering Hover and Pressed states
- Buttons considering accessibility
- Buttons considering desktop applications
I will explore how to create such buttons.
Also, I am putting the concepts discussed in this article into practice in a memo app I'm developing personally.
Please feel free to install it and try it out.
Creating a Button from Scratch
Using the button-related widgets provided by Flutter, you can easily implement buttons, but they inevitably end up looking like Material Design.
Of course, customization is possible, but since this is personal development, I'm going to try creating a Button from scratch.
What should be considered when creating a Button from scratch?
Simply using a GestureDetector isn't enough to call it a button.
I'll explore what kind of functions exist in a button.
To start, let's take a look at the familiar ElevatedButton.
What is a Button? ...Diving into ElevatedButton.
If you trace the internal structure of the ElevatedButton that everyone uses so often, you'll find it's made up of several widgets. I've extracted only the widgets I want to discuss this time.
- Semantics
- InkWell
- Actions
- Focus
- MouseRegion
- GestureDetector
What we can learn from this is that when creating a button, it seems necessary to consider the following:
- UI states such as Hover and Pressed
- Desktop application considerations
- Accessibility considerations
I've listed these in the order I personally find most interesting. Let's look at them starting from the top.
Considering UI States such as Hover and Pressed
The InkWell widget is a widget that expresses a ripple effect while being pressed.
Many of you have probably used it in your implementations.
In fact, InkWell doesn't just change while being pressed; the background color also changes while hovering.

Reference: https://api.flutter.dev/flutter/material/InkWell-class.html
Other things that change based on user interaction like hovering include the following:
-
Enabled: A state indicating that the button can be pressed. -
Disabled: A state indicating that the button cannot be pressed. -
Hover: A state where the mouse is hovering. -
Focused: A state where focus is held. -
Pressed: A state where the button is being pressed. -
Dragged: A state where the button is being dragged (not considered this time).
If you touch the buttons around you, you'll notice that many buttons change their color or shape in each state.
Let's think about how to design and implement this, using Material Design as a reference.

Zenn's save button. It tells you the shortcut.
Don't prepare separate colors for Hover and Disabled!
In Material Design, instead of preparing dedicated colors to represent Hover or Disabled states, the concept of "State layers" is used.
This changes the color the user sees by overlaying a semi-transparent layer on top of the background color.
The semi-transparent layer is defined based on the color of icons or text—for example, a transparency of 8% for Hover, 10% for Pressed, and so on.
(If expressing this in Flutter, you would use withOpacity.)

https://m3.material.io/foundations/interaction/states/state-layers
Advantages of State layers
The advantage of adopting State layers is that you don't have to define Hover color, Pressed color, etc., every time.
Once the background color and content (text/icons) color are decided, the Hover color and others are naturally determined, making it systematic.
This is just a rough image, but it might look like this if expressed in Flutter:
const containerColor = Colors.xxx;
Stack(
children: [
Background(),
StateLayer(color: containerColor.withOpacity(0.08)),
Text('text', style: TextStyle(color: containerColor)),
],
)
Personally, I prefer... over the ripple effect
This is purely a matter of preference, but while Material Design expresses the Pressed state with a ripple effect, I have a long history of using iOS and it's not quite to my taste...
Personally, I like the kind of Pressed state where the button itself becomes slightly smaller and bounces.

When pressing a tab in Arc
Consideration for Desktop Applications
Next, we will consider MouseRegion, Focus, and Action.
While these are rarely used consciously in mobile app development, they are essential when creating desktop applications.
Being Mindful of the Cursor with MouseRegion
In desktop applications, when you hover over a button, the cursor changes its shape to indicate that it is clickable.
This change was also visible in the InkWell GIF we saw earlier.
It's also necessary to ensure that it doesn't change when Disabled (unclickable) and only changes when clickable.

Reference: https://api.flutter.dev/flutter/material/InkWell-class.html
Being Mindful of Focus
The Focus widget is used internally by the TextField widget, which is commonly used in mobile apps.
Focus is for capturing keyboard focus, and it applies not just to TextField but to buttons as well.
For example, in Slack's message input area, you can move the focus between buttons using the Tab or arrow keys.

Being Mindful of Shortcuts with Actions
It is common for pressing the Enter key while a button has focus to perform the same process as when it is tapped.
The Action widget exists for this purpose.
I will omit the implementation details, but by passing a key and the process to be executed when that key is pressed, it seems possible to do various things.
Taking a Shortcut with FocusableActionDetector
In fact, there is a widget called FocusableActionDetector that combines MouseRegion, Focus, and Action.
Using this allows you to quickly support desktop applications!!
FocusableActionDetector(
mouseCursor: SystemMouseCursors.click,
actions: <Type, Action<Intent>>{
ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: (_) => onInvoke()),
ButtonActivateIntent: CallbackAction<ButtonActivateIntent>(
onInvoke: (_) => onInvoke(),
),
},
),
Internally, it actually uses MouseRegion, Focus, and Action.
Semantics is Accessibility
Semantics is a widget for giving meaning to widgets.
The most well-known use is for screen readers using iOS's VoiceOver feature.
While the semantics of ButtonStyleButton itself doesn't have a label (the text read by screen readers), it is included in the Text widget.
Let's Reinvent the Button!
Looking at it this way, you can see that buttons are surprisingly deep.
Of course, ElevatedButton has much more complex logic, but having understood part of it, you should be able to implement buttons that feel more like buttons than if you just used a GestureDetector.
I hope you all try this challenge in your own personal development!
I also share information about design and Figma, not just Flutter.
I'll include my Twitter link below, so please follow me if you're interested!
Please also give this article a like!
Also, I'm practicing the things I wrote about in this article in a memo app I'm developing personally.
I'd love for you to install it and give it a try.
Reference Articles to Learn More About Buttons
Discussion