Fluent2 Design System Material based
Installing
Add in your pubspec.yaml file and run dart pub get to download the package.
dependencies:
gbt_fluent2_ui:
Import in files that it will be used
import 'package:gbt_fluent2_ui/gbt_fluent2_ui.dart';
✨ Features
- Default theme based on Fluent2
- Useful components (including the behavior)
- Almost no dependencies but Flutter
Getting started
First of all, let's wrap our MaterialApp with FluentProvider
FluentProvider(
child: MaterialApp(...),
);
The FluentScaffold
Don't forget.
In order to make things work, make sure to always use FluentScaffold rather than Scaffold.
FluentScaffold(
appBar: FluentNavBar(...),
body: Placeholder(),
);
Theme
Import light and dark theme to your project:
import 'package:gbt_fluent2_ui/theme_data.dart' as theme_data;
final theme = theme_data.theme;
final darkTheme = theme_data.darkTheme;
Or you can pass your own brandColor:
Suggestion: use Smart Swatch Generator to get your color palette
import 'package:gbt_fluent2_ui/theme_data.dart';
const _brandColor = MaterialColor(
0xFF7f22e2,
<int, Color>{
50: Color(0xFFf5e6ff),
100: Color(0xFFd9bafa),
200: Color(0xFFbf8df2),
300: Color(0xFFa461eb),
400: Color(0xFF8934e4),
500: Color(0xFF701bcb),
600: Color(0xFF57149f),
700: Color(0xFF3e0e73),
800: Color(0xFF250747),
},
);
ThemeData get theme =>
getTheme(brandColor: _brandColor, brightness: Brightness.light,);
ThemeData get darkTheme =>
getTheme(brandColor: _brandColor, brightness: Brightness.dark);
And add the theme to your MaterialApp:
FluentProvider(
child: MaterialApp(
themeAnimationDuration: Duration.zero,
darkTheme: darkTheme,
themeMode: themeMode,
),
);
Fluent Icons
Import FluentIcons
:
import 'package:gbt_fluent2_ui/fluent_icons.dart';
Design Tokens
Design tokens are stored values used to assign Fluent styles like color, typography, spacing, or elevation, without hardcoding pixels and hex codes.
CornerRadius
The FluentThemeData alright have the predefined values to use in cornerRadius.
Use the FluentCornerRadius
radius tokens to change the corner radius on elements.
You can use the FluentContainer
component, which is basically a Material Container with properties that are compatible with Fluent 2 UI design rules.
FluentContainer(
cornerRadius: FluentCornerRadius.large,
)
Spacing Ramp
It’s used in every component and layout to create a familiar and cohesive product experience, regardless of device or environment.
Use FluentSize
values:
FluentContainer(
padding: EdgeInsets.symmetric(horizontal: FluentSize.size160.value),
)
Typography
The typography tokens are sets of global tokens that include font size, line height, weight and family.
Use Fluent Text
, a component created to accept Fluent typography tokens. So you get this value directly from the theme.
// ✅
FluentText(
"Text 1",
style: FluentThemeDataModel.of(context)
.fluentTextTheme
?.body2,
),
// ✅
FluentText(
"Text 2",
style: FluentThemeDataModel.of(context)
.fluentTextTheme
?.caption1,
),
// ❌ use the typography tokens from the theme
FluentText(
"Wrong use",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
),
)
But if you need to change some style of text like color, then you can use the fluentCopyWith()
:
FluentText(
"Text",
style: FluentThemeDataModel.of(context)
.fluentTextTheme
?.body2?.fluentCopyWith(
fluentColor: Colors.pink
),
)
Shadows
Fluent offers six sets of shadows, each consisting of two layers.
We have 2 elevation ramps:
- low elevation ramp (shadow2, shadow4, shadow8, shadow16)
- high elevation ramp (shadow28, shadow64)
If you choose brand shadow tokens to apply shadows to colors so the luminosity equation will be aplyed.
You can use them choosing the value from the theme:
FluentContainer(
shadow: FluentThemeDataModel.of(context).fluentShadowTheme?.shadow2,
width: 100,
height: 100,
),
FluentContainer(
color: FluentColors.of(context)?.brandBackground1Rest,
shadow: FluentThemeDataModel.of(context).fluentShadowTheme?.brandShadow64,
width: 100,
height: 100,
)
Stroke
FluentContainer
has the strokeStyle property where you can pass stroke styles, and to create lines you can use FluentStrokeDivider
:
FluentStrokeDivider(
style: FluentStrokeStyle(color: Colors.blue,thickness: FluentStrokeThickness.strokeWidth20),
startIndent: FluentStrokeBorderIndent.strokeIndent16,
)
You can choose the neutral strokes of the theme, the neutral strokes are: Accessible, Stroke1, Stroke2, StrokeDisabled.
FluentContainer(
strokeStyle: FluentThemeDataModel.of(context).fluentStrokeTheme?.strokeAccessible,
width: 100,
height: 100,
)
Or you can pass the weight of the stroke, color, distribution, and endpoint properties.
FluentContainer(
strokeStyle: FluentStrokeStyle(
thickness: FluentStrokeThickness.strokeWidth15,
color: Colors.red,
padding: 2,
dashArray: [
2,1,2,1
]
),
width: 100,
height: 100,
)
Color Tokens
Use the FluentColors
and FluentDarkColors
classes.
Interaction States:
According to the documentation, the Fluent palettes are often used to indicate interaction states on components.
color: FluentColors.of(context)?.brandForeground1Selected
color: FluentColors.of(context)?.brandBackground1Pressed
Neutral Colors
These colors are used on surfaces, text, and layout elements.
In Light Theme:
color: FluentColors.neutralBackground1Rest
In Dark Theme:
color: FluentDarkColors.neutralBackground1Rest
Brand colors
You pass the context, and the color dynamically adjusts for dark mode.
color: FluentColors.of(context)?.brandBackground1Rest
color: FluentColors.of(context).brandBackgroundTintRest
🧩 Components
Fluent Avatar
Types:
- Standard: circular containers that generally represent an individual.
FluentAvatar(
child: Image.asset(
"assets/images/avatars/avatar1.jpeg",
fit: BoxFit.cover,
width: double.maxFinite,
height: double.maxFinite,
),
),
- Group: square containers that represent many people, like teams, organizations, or companies.
FluentAvatar(
isGroup: true,
...
)
Behavior
Presence Badges
There are 8 avatar badge variants: away
, avaliable
, dnd
, offline
, unknown
, busy
,blocked
,oof
.
FluentAvatar(
statusPresenceBadge: StatusPresenceBadge.avaliable,
...
)
Activity Rings
FluentAvatar(
strokeStyle: FluentStrokeStyle(
padding: 4,
thickness: FluentStrokeThickness.strokeWidth30,
color: Colors.purple
),
child: Image.asset(
"assets/images/avatars/avatar1.jpeg",
fit: BoxFit.cover,
width: double.maxFinite,
height: double.maxFinite,
),
)
Cutout
Avatars at 40 and 56 pixels can display a cutout that communicates other dynamic information, like reactions, mentions, and recent file activity.
FluentAvatar(
size: FluentAvatarSize.size56,
cutoutSize: CutoutSize.size28,
cutout: Icon(
FluentIcons.heart_12_filled
),
child: Image.asset(...),
)
FluentAvatar(
size: FluentAvatarSize.size56,
cutoutSize: CutoutSize.size20,
cutout: Icon(
FluentIcons.heart_12_filled,
size: 16,
),
child: Image.asset(...),
)
Fluent Button
Sizes
The FluentButtonSize class has the following variations:
Large
Medium
Small
FluentButton(
title: "Click Me",
size: FluentButtonSize.medium,
onPressed: () {},
)
Variants
The FluentButtonVariant
class contains the following variants:
Accent
Outline Accent
Outline
Subtle
FluentButton(
title: "Click Me",
variant: FluentButtonVariant.accent,
onPressed: () {},
)
Fluent Navigation Bar
You can choose the brand or neutral variant in the Fluent NavBar.
FluentNavBar(
themeColorVariation: FluentThemeColorVariation.brand,
)
Leading
FluentNavBar(
leading: Icon(FluentIcons.leaf_two_32_filled),
)
Gradient
Suports the gradient propertie:
FluentNavBar(
title: NavCenterTitle(title: "Title"),
themeColorVariation: FluentThemeColorVariation.brand,
gradient: LinearGradient(
colors: [
Colors.purple,
Colors.deepPurple,
],
),
)
Nav title
There are 4 variants for nav title: Left title
, Left subtitle
, Center title
, Center
FluentNavBar(
themeColorVariation: FluentThemeColorVariation.brand,
title: NavLeftSubtitle(title: "Title", subtitle: "My subtitle",),
)
Nav actions
A list of widgets, the actions that will appear in the right side of nav bar:
FluentNavBar(
themeColorVariation: FluentThemeColorVariation.brand,
title: NavLeftSubtitle(title: "Title", subtitle: "My subtitle",),
actions: [
Icon(FluentIcons.airplane_24_regular),
Icon(FluentIcons.access_time_20_regular),
Icon(FluentIcons.sparkle_20_filled),
],
)
Fluent List
There are two variants and you can choose them using the named constructor:
- OneLine
FluentList.oneLine(
// Here you can only pass a list of FluentListItemOneLine
listItems: [...],
)
- MultiLine
FluentList.multiLine(
// Here you can only pass a list of FluentListItemMultiLine
listItems: [...],
)
FluentList has the following props:
-
sectionHeaderTitle
-
sectionHeaderTitleIcon
: The leading icon of section header -
sectionHeaderBackgroundColor
-
sectionHeaderTitleVariant
: You can choose the bold or subtle value. -
sectionHeaderActions
: Receives a FluentSectionHeaderActions where you can pass two action widgets. -
sectionDescriptionText
-
sectionDescriptionBackgroundColor
-
sectionDescriptionIcon
: The icon that will appear in your section description. -
separator
: A separator to your list.
FluentList.multiLine(
sectionHeaderTitle: "I'm header title",
sectionHeaderTitleVariant: SectionHeaderTitleVariant.bold,
sectionHeaderActions: FluentSectionHeaderActions(
action1: Icon(FluentIcons.circle_20_regular),
action2: Icon(FluentIcons.circle_20_regular),
),
sectionDescriptionText: "This is my list description",
separator: FluentStrokeDivider(),
sectionDescriptionIcon: FluentIcons.leaf_three_16_filled,
listItems: [
FluentListItemMultiLine(text: "Item 1"),
FluentListItemMultiLine(text: "Item 2"),
],
)
Fluent Card
FluentCard(
text: "Text",
subText: "Subtext",
leading: Image.asset(
"assets/images/cards/card2.jpeg",
fit: BoxFit.cover,
width: double.maxFinite,
height: double.maxFinite,
),
coverImage: Image.asset(
"assets/images/cards/card2.jpeg",
fit: BoxFit.cover,
width: double.maxFinite,
height: double.maxFinite,
),
onPressed: () {
// put your onPressed function here
},
)
If you just want to use a flexible container with card styles, you can use FluentCardContainer
:
FluentCardContainer(
padding: EdgeInsets.all(FluentSize.size160.value),
child: Text("Hi, i'm a text"),
)
Fluent Radio Button
FluentRadioButton<Option>(
value: Option.option1,
groupValue: _option,
onChanged: (value) {
// put your onChanged function here
},
)
Disabled:
FluentRadioButton<Option>(
value: Option.option1,
groupValue: _option,
onChanged: null,
)
Fluent Checkbox
FluentCheckbox(
value: isCheckbox1,
onChanged: (value) {
// put your onChanged function here
},
)
Disabled:
FluentCheckbox(
value: isCheckbox1,
onChanged: null,
)
Fluent Switch Toggle
FluentSwitchToggle(
value: showIcons,
onChanged: (value) => setState(() {
showIcons = value;
}),
)
Disabled:
FluentSwitchToggle(
value: showIcons,
onChanged: null,
)
Fluent Banner
final myBanner = FluentBanner(
bannerColor: FluentBannerColor.accent,
text: "It's me Mario",
);
Adding Fluent Banner
FluentButton(
title: "Open Banner",
onPressed: () async {
FluentScaffoldMessenger.of(context).addBanner(myBanner);
},
)
Removing Fluent Banner
FluentButton(
title: "Fechar Banner",
onPressed: () async {
FluentScaffoldMessenger.of(context).removeBanner(myBanner);
},
)
Fluent Toast
FluentToast
has 4 variants of FluentToastColor:
-
Accent
-
Neutral
-
Danger
-
Warning
FluentButton(
title: "Accent Toast",
onPressed: (){
FluentToast(
toastColor: FluentToastColor.accent,
text: "Fluent 2 is here",
subText: "See what’s changed.",
icon: Icon(FluentIcons.sparkle_20_filled),
action: Builder(
builder: (context) => IconButton(
onPressed: () {
FluentToastOverlayEntry.of(context).remove();
},
icon: Icon(Icons.cancel),
),
)).show(
context: context,
duration: null,
onDismissed: () {
print("Fechou!");
},
);
},
)
Fluent Text Field
FluentTextField(
label: "Last Name",
hintText: "Ballinger",
onChanged: (value) {
// put your onChanged function here
},
obscureText: false,
readOnly: false,
suffixIcon: Icon(FluentIcons.leaf_three_16_filled),
hasError: error != null,
assistiveText: error ?? "assistive",
)
Fluent Progress Bar
if (isUpdating)
FluentProgressBar(
value: null,
)
Fluent Heads-up Display
FluentButton(
title: "Open HUD",
onPressed: () {
FluentHeadsUpDisplayDialog(
future: Future.delayed(Duration(seconds: 1)),
confirmStopMessage: "Are you sure you want to close this?",
hud: FluentHeadsUpDisplay(
text: "Refreshing Data...",
),
).show(context);
},
)
📚 Additional information
This is mainly inspired on the Fluent2 iOS Figma UI Kit. It will be gradually adapted to Android as soon as the Microsoft release its Figma UI Kit.
Libraries
- color_mode
- fluent_icons
- gbt_fluent2_debug
- gbt_fluent2_ui
- A Dart library
- theme_data
- utils/debouncer