Themes
Define consistent visual styles across your Flutter application with Forui's theming system.
Forui themes allow you to define a consistent visual style across your application & widgets. It relies on the CLI to generate themes and styles that can be directly modified in your project.
Getting Started
Theme Brightness
Forui does not manage the theme brightness (light or dark) automatically.
You need to specify the theme explicitly in FAnimatedTheme(...).
1@override2Widget build(BuildContext context) => FTheme(3 data: FThemes.neutral.light, // or FThemes.neutral.dark4 child: const FScaffold(child: Placeholder()),5);6Forui includes predefined themes that can be used out of the box. They are heavily inspired by shadcn/ui.
| Theme | Light Accessor | Dark Accessor |
|---|---|---|
Neutral | FThemes.neutral.light | FThemes.neutral.dark |
Zinc | FThemes.zinc.light | FThemes.zinc.dark |
Slate | FThemes.slate.light | FThemes.slate.dark |
Blue | FThemes.blue.light | FThemes.blue.dark |
Green | FThemes.green.light | FThemes.green.dark |
Orange | FThemes.orange.light | FThemes.orange.dark |
Red | FThemes.red.light | FThemes.red.dark |
Rose | FThemes.rose.light | FThemes.rose.dark |
Violet | FThemes.violet.light | FThemes.violet.dark |
Yellow | FThemes.yellow.light | FThemes.yellow.dark |
Theme Components
There are 6 core components in Forui's theming system.
FAnimatedTheme: The root widget that provides the theme data to all widgets in the subtree.FThemeData: Main class that holds:FColors: Color scheme including primary, foreground, and background colors.FTypography: Typography settings including font family and text styles.FStyle: Misc. options such as border radius and icon size.- Individual widget styles.
- Individual widget motions.
The included BuildContext extension allows FThemeData can be accessed via context.theme:
1@override2Widget build(BuildContext context) {3 final FThemeData theme = context.theme;4 final FColors colors = context.theme.colors;5 final FTypography typography = context.theme.typography;6 final FStyle style = context.theme.style;78 return const Placeholder();9}10Colors
The FColors class contains the theme's color scheme. Colors come in pairs - a main color and its corresponding
foreground color for text and icons.
For example:
primary(background) +primaryForeground(text/icons)secondary(background) +secondaryForeground(text/icons)destructive(background) +destructiveForeground(text/icons)
1@override2Widget build(BuildContext context) {3 final colors = context.theme.colors;4 return ColoredBox(5 color: colors.primary,6 child: Text(7 'Hello World!',8 style: TextStyle(color: colors.primaryForeground),9 ),10 );11}12Hovered and Disabled Colors
To create hovered and disabled color variants, use the FColors.hover
and FColors.disable methods.
Typography
The FTypography class contains the theme's typography settings, including the default font family and various text
styles.
The TextStyles in FTypography are based on Tailwind CSS Font Size.
For example, FTypography.sm is the equivalent of text-sm in Tailwind CSS.
FTypography's text styles only specify fontSize and height. Use copyWith() to add colors and other properties:
1@override2Widget build(BuildContext context) {3 final typography = context.theme.typography;45 return Text(6 'Hello World!',7 style: typography.xs.copyWith(8 color: context.theme.colors.primaryForeground,9 fontWeight: .bold,10 ),11 );12}13Custom Font Family
Use the copyWith() method to change the default font family. As some fonts may have different sizes, the scale()
method is provided to quickly scale all the font sizes.
1@override2Widget build(BuildContext context) => FTheme(3 data: FThemeData(4 colors: FThemes.neutral.light.colors,5 typography: FThemes.neutral.light.typography6 .copyWith(xs: const TextStyle(fontSize: 12, height: 1))7 .scale(sizeScalar: 0.8),8 ),9 child: const FScaffold(child: Placeholder()),10);11Style
The FStyle class the theme's miscellaneous styling options such as the default border radius and icon size.
1@override2Widget build(BuildContext context) {3 final colors = context.theme.colors;4 final style = context.theme.style;56 return DecoratedBox(7 decoration: BoxDecoration(8 border: .all(color: colors.border, width: style.borderWidth),9 borderRadius: style.borderRadius,10 color: colors.primary,11 ),12 child: const Placeholder(),13 );14}15FWidgetStateMap
FWidgetStateMap lets you define
different values based on WidgetState combinations such as
hovered & pressed and focused | disabled.
This is useful for describing how widgets should respond to user interaction.
Each combination is also known as a constraint. Constraints are evaluated from top to bottom. The first matching will be used. In general, more specific constraints should be placed above more general ones.
In the following example, given the states {hovered, pressed}, the 1st constraint will always match.
Most FWidgetStateMap fields only support a subset of states. For example, FButtonStyle.decoration
only supports the disabled, hovered, pressed and focused states. A field's supported states are documented in its API docs.
Customization
Forui provides 2 ways to customize themes and widget styles.
- CLI - major and reusable style changes, such as creating your own design system.
copyWith(...)method - minor and one-off adjustments to existing styles.
Themes
The following section demonstrates how to use the CLI generate a theme and widget style that you can directly modify to fit your design needs.
We use FAccordionStyle
as an example, but the same principles apply to all Forui widgets.
Generate main.dart
Navigate to your project directory.
Run to generate a main.dart:
dart run forui initThis generates a main.dart file where you will add your generated theme:
12import 'package:flutter/material.dart';34import 'package:forui/forui.dart';56import 'theme/theme.dart';789void main() {10 runApp(const Application());11}1213class Application extends StatelessWidget {14 const Application({super.key});1516 @override17 Widget build(BuildContext context) {18 // Assign the generated theme to `theme`.19 final theme = neutralLight;2021 return MaterialApp(22 localizationsDelegates: FLocalizations.localizationsDelegates,23 supportedLocales: FLocalizations.supportedLocales,24 builder: (_, child) => FTheme(data: theme, child: child!),25 theme: theme.toApproximateMaterialTheme(),26 home: const FScaffold(27 // TODO: replace with your widget.28 child: Placeholder(),29 ),30 );31 }32}33Generate a Theme
Run to generate a theme based on neutral's light variant:
dart run forui theme create neutral-lightTip: Run dart run forui theme ls to see all available themes.
This generates a theme file which you can:
- add to your generated
main.dart. - add the generated styles to.
12import 'package:flutter/material.dart';34import 'package:forui/forui.dart';56import 'accordion_style.dart';789FThemeData get neutralLight {10 final colors = FThemes.neutral.light.colors;1112 final typography = _typography(colors: colors);13 final style = _style(colors: colors, typography: typography);1415 return FThemeData(16 colors: colors,17 typography: typography,18 style: style,19 // Add your generated styles here.20 accordionStyle: accordionStyle(21 colors: colors,22 typography: typography,23 style: style,24 ),25 );26}2728FTypography _typography({29 required FColors colors,30 String defaultFontFamily = 'packages/forui/Inter',31}) => FTypography(32 xs: TextStyle(33 color: colors.foreground,34 fontFamily: defaultFontFamily,35 fontSize: 12,36 height: 1,37 ),38 sm: TextStyle(39 color: colors.foreground,40 fontFamily: defaultFontFamily,41 fontSize: 14,42 height: 1.25,43 ),44);4546FStyle _style({required FColors colors, required FTypography typography}) =>47 FStyle(48 formFieldStyle: .inherit(colors: colors, typography: typography),49 focusedOutlineStyle: FFocusedOutlineStyle(50 color: colors.primary,51 borderRadius: const .all(.circular(8)),52 ),53 iconStyle: IconThemeData(color: colors.primary, size: 20),54 tappableStyle: FTappableStyle(),55 );56Generate a Style
Run to generate a FAccordionStyle:
dart run forui style create accordionTip: Run dart run forui style ls to see all available styles.
This generates a accordion style file which you can add to your theme:
1FAccordionStyle accordionStyle({2 required FColors colors,3 required FTypography typography,4 required FStyle style,5}) => FAccordionStyle(6 titleTextStyle: .delta(7 // This text style is applied when the accordion is NOT hovered OR pressed.8 typography.base.copyWith(fontWeight: .w500, color: colors.foreground),9 variants: {10 // This text style is applied when the accordion is hovered OR pressed.11 [.hovered, .pressed]: const .delta(decoration: .underline),12 },13 ),14See FWidgetStateMap for more information on FWidgetStateMaps.
Individual Widget Styles
You can customize a widget's style in 2 ways:
- Use
copyWith()- Best for minor adjustments to existing styles. - Generate via CLI - Best for major style overhauls.
copyWith(...)
All widgets and style copyWith(...)s accept style builder functions.
To change an accordion's focused outline color:
1FAccordion(2 style: .delta(3 focusedOutlineStyle: .delta(color: context.theme.colors.background),4 ),5 children: const [],6);7All styles implement the call function. This allows a style object to be
passed directly to a widget or style's copyWith(...) as a shortcut.
Both are equivalent:
1// Complete replacement by passing a style directly2FAccordion(3 style: FAccordionStyle.inherit(4 colors: colors,5 typography: typography,6 style: style,7 ),8 children: const [],9);1011// Short-form12FAccordion(13 style: .delta(titlePadding: .symmetric(vertical: 20)),14 children: [],15);16CLI
The following sections demonstrate how to override an accordion's style.
Generate the Style
Run to generate a widget style:
dart run forui style create accordionTip: Run dart run forui style ls to see all available styles.
Modify the Style
This example shows how to add underlining when the accordion title is focused, in addition to the existing hover and press states:
1FAccordionStyle accordionStyle({2 required FColors colors,3 required FTypography typography,4 required FStyle style,5}) => FAccordionStyle(6 titleTextStyle: .delta(7 // This text style is applied when the accordion is NOT hovered OR pressed.8 typography.base.copyWith(fontWeight: .w500, color: colors.foreground),9 variants: {10 // This text style is applied when the accordion is hovered OR pressed OR focused (new).11 [.hovered, .pressed, .focused]: const .delta(decoration: .underline),12 },13 ),14);15See FWidgetStateMap for more information on FWidgetStateMaps.
Pass the Style
12@override3Widget build(BuildContext context) => FAccordion(4 // Pass the modified style to the widget.5 style: accordionStyle(6 colors: context.theme.colors,7 typography: context.theme.typography,8 style: context.theme.style,9 ),10 children: const [11 FAccordionItem(12 title: Text('Is it accessible?'),13 child: Text('Yes. It adheres to the WAI-ARIA design pattern.'),14 ),15 ],16);17Custom Properties
Forui themes can be extended with your own application-specific properties using Flutter's ThemeExtension
system.
Create a Theme Extension
Theme extensions must extend ThemeExtension and implement copyWith() and lerp().
1import 'package:flutter/material.dart';23class BrandColor extends ThemeExtension<BrandColor> {4 final Color color;56 const BrandColor({required this.color});78 @override9 BrandColor copyWith({Color? color}) => BrandColor(color: color ?? this.color);1011 @override12 BrandColor lerp(BrandColor? other, double t) {13 if (other is! BrandColor) {14 return this;15 }1617 return BrandColor(color: Color.lerp(color, other.color, t)!);18 }19}20Add the extension to FThemeData(...) via its extensions parameter:
12FThemeData(3 colors: FThemes.neutral.light.colors,4 // ... other theme properties5 extensions: [const BrandColor(color: Color(0xFF6366F1))],6);7You can also add extensions to existing themes using copyWith(...):
1final theme = FThemes.neutral.light.copyWith(2 extensions: [const BrandColor(color: Color(0xFF6366F1))],3);4Accessing the Properties
Retrieve your custom theme extension via extension<T>():
1@override2Widget build(BuildContext context) {3 final brand = context.theme.extension<BrandColor>();4 return ColoredBox(color: brand.color);5}6Optionally, we recommend creating a getter on FThemeData:
1extension BrandColorExtension on FThemeData {2 BrandColor get brand => extension<BrandColor>();3}4Material Interoperability
Forui provides 2 ways to convert FThemeData
to Material's ThemeData.
This is useful when:
- Using Material widgets within a Forui application.
- Maintaining consistent theming across both Forui and Material components.
- Gradually migrating from Material to Forui.
toApproximateMaterialTheme()
A Forui theme can be converted to a Material theme using
toApproximateMaterialTheme().
The toApproximateMaterialTheme() method is marked as experimental. This method can change without prior warning. The
mapping between Forui and Material themes is done on a best-effort basis, and may not perfectly capture all the nuances
of a Forui theme.
1import 'package:flutter/material.dart';23import 'package:forui/forui.dart';45@override6Widget build(BuildContext context) => MaterialApp(7 theme: FThemes.neutral.light.toApproximateMaterialTheme(),8 home: Scaffold(9 body: Center(10 child: FCard(11 title: const Text('Mixed Widgets'),12 subtitle: const Text('Using both Forui and Material widgets together'),13 child: ElevatedButton(14 onPressed: () {},15 child: const Text('Material Button'),16 ),17 ),18 ),19 ),20);21CLI
Use the CLI to generate a copy of toApproximateMaterialTheme() inside your project:
dart run forui snippet create material-mappingThis should be preferred when you want to fine-tune the mapping between Forui and Material themes, as it allows you to modify the generated mapping directly to fit your design needs.