Skip to Content
Forui 0.11.0 is released 🎉

Themes

Forui themes allows you to customize the look and feel of your Flutter application. Our theming solution is designed to help you get started quickly while offering powerful and flexible customization options.

Predefined Themes

Forui does not manage the theme brightness (light or dark) automatically. You need to specify the theme explicitly in FTheme(...).

main.dart
@override Widget build(BuildContext context) => FTheme( data: FThemes.zinc.light, // or FThemes.zinc.dark child: FScaffold(...), );

Forui provides a set of predefined themes that you can use out of the box. The color schemes are heavily inspired by shadcn/ui themes .

ThemeLight AccessorDark Accessor

Zinc

FThemes.zinc.lightFThemes.zinc.dark

Slate

FThemes.slate.lightFThemes.slate.dark

Red

FThemes.red.lightFThemes.red.dark

Rose

FThemes.rose.lightFThemes.rose.dark

Orange

FThemes.orange.lightFThemes.orange.dark

Green

FThemes.green.lightFThemes.green.dark

Blue

FThemes.blue.lightFThemes.blue.dark

Yellow

FThemes.yellow.lightFThemes.yellow.dark

Violet

FThemes.violet.lightFThemes.violet.dark

Usage

FTheme uses inherited widgets to provide the FThemeData to all widgets in the subtree. Forui provides an extension on BuildContext which allows for direct access to FThemeData through context.theme.

@override Widget build(BuildContext context) { final theme = context.theme; // FThemeData final colors = context.theme.colors; // FColors final typography = context.theme.typography; // FTypography final style = context.theme.style; // FStyle return const Placeholder(); }

A high level overview of the theme data structure is as follows:

  • FThemeData contains FColors, FTypography, FStyle, and widget styles (eg. FCardStyle).
  • FColors contains the color scheme (eg. background, foreground, and primary).
  • FTypography contains the defaultFontFamily and various TextStyles.
  • FStyle contains other miscellaneous styling options (eg. borderRadius).

A more detailed explanation of each class can be found in the Class Diagram section.

Customize Themes

CLI

It’s recommended to use the CLI to generate themes and styles in your project. These themes and styles can be directly modified to fit your design needs.

This example demonstrates how to generate and use both a theme and FButtonStyles in your project.

Navigate to your project directory.

Run the following command to generate a main.dart:

dart run forui init

Run the following command to generate a theme based on zinc:

dart run forui theme create zinc

Run the following command to generate a FButtonStyles:

dart run forui style create button

The first command, dart run forui init generates a main file which you can add the generated theme to:

lib/main.dart
import 'theme/theme.dart'; void main() { runApp(const Application()); } class Application extends StatelessWidget { const Application({super.key}); @override Widget build(BuildContext context) { // Assign the generated theme to `theme`. final theme = zincLight; return MaterialApp( localizationsDelegates: FLocalizations.localizationsDelegates, supportedLocales: FLocalizations.supportedLocales, builder: (_, child) => FTheme(data: theme, child: child!), theme: theme.toApproximateMaterialTheme(), // You can replace FScaffold with Material's Scaffold. home: const FScaffold( // TODO: replace with your widget. child: Example(), ), ); } } class Example extends StatefulWidget { const Example({super.key}); @override State<Example> createState() => _ExampleState(); } class _ExampleState extends State<Example> { int _count = 0; @override Widget build(BuildContext context) => Center( child: ... ); }

The second command, dart run forui theme create zinc generates a theme file which you can add the generated button styles to:

lib/theme/theme.dart
import 'button_styles.dart'; FThemeData get zincLight { const colors = FColors( brightness: Brightness.light, background: Color(0xFFFFFFFF), foreground: Color(0xFF09090B), ... ); final typography = _typography(colors: colors); final style = _style(colors: colors, typography: typography); return FThemeData( colors: colors, typography: typography, style: style, // Add your previously generated button styles and other generated styles here. buttonStyles: buttonStyles(colors: colors, typography, typography, style: style), ); } FThemeData get zincDark { const colors = FColors( brightness: Brightness.dark, background: Color(0xFF09090B), foreground: Color(0xFFFAFAFA), ... ); final typography = _typography(colors: colors); final style = _style(colors: colors, typography: typography); return FThemeData( colors: colors, typography: typography, style: style, // Add your previously generated button styles and other generated styles here. buttonStyles: buttonStyles(colors: colors, typography, typography, style: style), ); } FTypography _typography({ required FColors colors, String defaultFontFamily = 'packages/forui/Inter', }) => FTypography( xs: TextStyle( color: colors.foreground, fontFamily: defaultFontFamily, fontSize: 12, height: 1, ), sm: TextStyle( color: colors.foreground, fontFamily: defaultFontFamily, fontSize: 14, height: 1.25, ), ... ); FStyle _style({required FColors colors, required FTypography typography}) => FStyle( formFieldStyle: FFormFieldStyle.inherit( colors: colors, typography: typography, ), focusedOutlineStyle: FFocusedOutlineStyle( color: colors.primary, borderRadius: const BorderRadius.all(Radius.circular(8)), ), iconStyle: IconThemeData(color: colors.primary, size: 20), tappableStyle: FTappableStyle(), );

The last command, dart run forui style create button generates a button styles file which you can add to your theme.

FWidgetStateMap allows you to define different styles for different combinations of states for a widget. This combination of states is known as a “constraint”.

To add a decoration for a disabled button when it is hovered or pressed:

lib/theme/button_styles.dart
FButtonStyles buttonStyles({ required FColors colors, required FTypography typography, required FStyle style, }) => FButtonStyles( primary: _buttonStyle( colors: colors, style: style, typography: typography, color: colors.primary, foregroundColor: colors.primaryForeground, ), ... ); FButtonStyle _buttonStyle({ required FColors colors, required FTypography typography, required FStyle style, required Color color, required Color foregroundColor, }) => FButtonStyle( decoration: FWidgetStateMap({ WidgetState.disabled & (WidgetState.hovered | WidgetState.pressed): BoxDecoration( borderRadius: style.borderRadius, color: colors.hover(colors.disable(color)), ), WidgetState.disabled: BoxDecoration( borderRadius: style.borderRadius, color: colors.disable(color), ), WidgetState.hovered | WidgetState.pressed: BoxDecoration( borderRadius: style.borderRadius, color: colors.hover(color), ), WidgetState.any: BoxDecoration( borderRadius: style.borderRadius, color: color, ), }), ... );

Constraints are applied in the order they are defined (top to bottom). The first matching constraint will be used. This means that more specific constraints should be defined above more general ones.

Most FWidgetStateMap properties only support a subset of WidgetStates. For example, FButtonStyle.decoration only supports WidgetState.disabled, WidgetState.hovered, WidgetState.pressed and WidgetState.focused. A property’s supported states are documented in its API docs.

Class Diagram

Colors

The Colors class contains the color scheme for the theme. This usually consists of a set of colors with its corresponding foreground color (eg. primary and primaryForeground).

In most cases, the color will be used as the background color, and the foreground color will be used as the text/icon color.

@override Widget build(BuildContext context) { final colors = context.theme.colors; final typography = context.theme.typography; return ColoredBox( color: colors.primary, child: Text( 'Hello World!', style: typography.xs.copyWith( color: colors.primaryForeground ), ), ); }

Hovered and Disabled Colors

Hovered and disabled colors are derived by adjusting the opacity. To derive these colors, use the FColors.hover and FColors.disable methods. The opacity can be adjusted with FColors.enabledHoveredOpacity and FColors.disabledOpacity.

Typography

The FTypography class contains the typography settings for the theme. This includes the default font family and various TextStyles for different use cases.

The TextStyles stored in FTypography are based on Tailwind CSS Font Size . For instance, FTypography.sm is closely related to text-sm in Tailwind CSS.

It is recommended to use the copyWith(...) to apply colors and other styles as the TextStyles stored only contain the fontSize and height properties.

@override Widget build(BuildContext context) { final colors = context.theme.colors; final typography = context.theme.typography; return Text( 'Hello World!', style: typography.xs.copyWith( color: colors.primaryForeground, fontWeight: FontWeight.bold, fontStyle: FontStyle.italic, ), ); }

Custom Font Family

To change the default font family, use the copyWith(...) method to apply the new font family. As some fonts may have different sizes, the scale(...) method is provided to quickly scale all the font sizes.

main.dart
@override Widget build(BuildContext context) => FTheme( data: FThemeData( colors: FThemes.zinc.light.colors, typography: FThemes.zinc.light.typography.copyWith( defaultFontFamily: 'Roboto', ).scale(sizeScalar: 0.8), ), child: const FScaffold(...), );

Style

The FStyle class contains other miscellaneous styling options for the theme.

@override Widget build(BuildContext context) { final colors = context.theme.colors; final style = context.theme.style; return DecoratedBox( decoration: BoxDecoration( border: Border.all( color: colors.border, width: style.borderWidth, ), borderRadius: style.borderRadius, color: colors.primary, ), child: const Placeholder(), ); }

Override Individual Widget Styles

In certain cases, you may want to override the theme for a specific widget. This can be easily achieved by using the copyWith(...) function.

In the example below, styles are only overridden for this specific FCard widget. FCard widgets that are used elsewhere will still inherit from theme data.

main.dart
@override Widget build(BuildContext context) { final theme = context.theme; return FCard( title: 'Notification', subtitle: 'You have 3 new messages', style: theme.cardStyle.copyWith( decoration: theme.cardStyle.decoration.copyWith( borderRadius: BorderRadius.zero, // Remove border radius. ), ), ); }

Override FWidgetStateMap Properties

FWidgetStateMap allows you to define different styles for different combinations of states for a widget. This combination of states is known as a “constraint”.

Constraints are applied in the order they are defined (top to bottom). The first matching constraint will be used. This means that more specific constraints should be defined above more general ones.

@override Widget build(BuildContext context) { final style = context.theme.buttonStyles.primary; return Column( children: [ FButton( style: style.copyWith( // Override the decoration for the constraints that are hovered. decoration: style.decoration.replaceAllWhere({WidgetState.hovered}, BoxDecoration()), ), onPress: () {}, child: const Text('Button'), ), FButton( style: style.copyWith( // Override the decoration for the first constraint that is hovered. decoration: style.decoration.replaceFirstWhere({WidgetState.hovered}, BoxDecoration()), ), onPress: () {}, child: const Text('Button'), ), FButton( style: style.copyWith( // Override the decoration for the last constraint that is hovered. decoration: style.decoration.replaceLastWhere({WidgetState.hovered}, BoxDecoration()), ), onPress: () {}, child: const Text('Button'), ), ], ); }

Most FWidgetStateMap properties only support a subset of WidgetStates. For example, FButtonStyle.decoration only supports WidgetState.disabled, WidgetState.hovered, WidgetState.pressed and WidgetState.focused. A property’s supported states are documented in its API docs.

Material Design Interoperability

Forui provides a way to convert its themes to Material Design’s ThemeData for interoperability between Forui and Material widgets. This is useful when:

  • Using Material widgets within a Forui application
  • Maintaining consistent theming across both Forui and Material components
  • Gradually migrating from Material Design to Forui

toApproximateMaterialTheme()

A Forui theme can be converted to a Material theme using the toApproximateMaterialTheme() method.

⚠️

The toApproximateMaterialTheme() method is marked as experimental. As an experimental feature, 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.

import 'package:flutter/material.dart'; import 'package:forui/forui.dart'; @override Widget build(BuildContext context) { final fThemeData = FThemes.zinc.light; return MaterialApp( theme: fThemeData.toApproximateMaterialTheme(), home: Scaffold( body: Center( child: FCard( title: 'Mixed Widgets', subtitle: 'Using both Forui and Material widgets together', child: ElevatedButton( onPressed: () {}, child: const Text('Material Button'), ), ), ), ), ); }

CLI

For more fine-grained control, you can use the CLI to generate a copy of the toApproximateMaterialTheme() inside your project. The generated copy can then be directly modified to fit your design needs.

To generate a copy of toApproximateMaterialTheme(), run:

dart run forui snippet create material-mapping
Last updated on