Skip to Content
Forui 0.12.0 is released 🎉

Themes

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

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 includes predefined themes that can be used out of the box. They are heavily inspired by shadcn/ui.

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

Theme Components

There are 5 core components in Forui’s theming system.

  • FTheme: The root widget that provides the theme data to all widgets in the subtree.
  • FThemeData: Main class that holds individual widget styles and the following:
    • 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.

The included BuildContext extension allows FThemeData can be accessed via context.theme:

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

Colors

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)
@override Widget build(BuildContext context) { final colors = context.theme.colors; return ColoredBox( color: colors.primary, child: Text( 'Hello World!', style: TextStyle(color: colors.primaryForeground), ), ); }

Hovered 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:

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

Custom 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.

@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 the theme’s miscellaneous styling options such as the default border radius and icon size.

@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(), ); }

FWidgetStateMap

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.

FWidgetStateMap({ // ❌ Don't declare more general constraints above more specific ones! // 1st constraint: applied when the accordion is hovered. WidgetState.hovered: typography.base.copyWith( fontWeight: FontWeight.w500, color: colors.foreground, decoration: TextDecoration.underline, ), // 2nd constraint: applied when the accordion is hovered OR pressed. WidgetState.hovered | WidgetState.pressed: typography.base.copyWith( fontWeight: FontWeight.w500, color: colors.foreground, decoration: TextDecoration.underline, ), // 3rd constraint: This text style is applied when the accordion is NOT hovered OR pressed. WidgetState.any: typography.base.copyWith( fontWeight: FontWeight.w500, color: colors.foreground, ), });

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

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 init

This generates a main.dart file where you will add your generated theme:

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(), home: const FScaffold( // TODO: replace with your widget. child: Placeholder(), ), ); } }

Generate a Theme

Run to generate a theme based on zinc’s light variant:

dart run forui theme create zinc-light

This generates a theme file which you can:

  • add to your generated main.dart.
  • add the generated styles to.
lib/theme/theme.dart
import 'accordion_style.dart'; FThemeData get zincLight { const colors = FColors( brightness: Brightness.light, barrier: Color(0x33000000), 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 generated styles here. accordionStyle: accordionStyle(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(), ... );

Generate a Style

Run to generate a FAccordionStyle:

dart run forui style create accordion

This generates a accordion style file which you can add to your theme:

lib/theme/accordion_style.dart
FAccordionStyle accordionStyle({ required FColors colors, required FTypography typography, required FStyle style, }) => FAccordionStyle( titleTextStyle: FWidgetStateMap({ // This text style is applied when the accordion is hovered OR pressed. WidgetState.hovered | WidgetState.pressed: typography.base.copyWith( fontWeight: FontWeight.w500, color: colors.foreground, decoration: TextDecoration.underline, ), // This text style is applied when the accordion is NOT hovered OR pressed. WidgetState.any: typography.base.copyWith( fontWeight: FontWeight.w500, color: colors.foreground, ), }), childTextStyle: typography.sm.copyWith(color: colors.foreground), // This decoration is ALWAYS applied. iconStyle: FWidgetStateMap.all( IconThemeData(color: colors.primary, size: 20), ), focusedOutlineStyle: style.focusedOutlineStyle, dividerStyle: FDividerStyle(color: colors.border, padding: EdgeInsets.zero), tappableStyle: style.tappableStyle.copyWith( animationTween: FTappableAnimations.none, ), titlePadding: const EdgeInsets.symmetric(vertical: 15), childPadding: const EdgeInsets.only(bottom: 15), animationDuration: const Duration(milliseconds: 200), );

See FWidgetStateMap for more information on FWidgetStateMaps.

Individual Widget Styles

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 accordion

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:

lib/theme/accordion_style.dart
FAccordionStyle accordionStyle({ required FColors colors, required FTypography typography, required FStyle style, }) => FAccordionStyle( titleTextStyle: FWidgetStateMap({ // This text style is applied when the accordion is hovered OR pressed OR focused (new). WidgetState.hovered | WidgetState.pressed | Widget.focused: typography.base.copyWith( fontWeight: FontWeight.w500, color: colors.foreground, decoration: TextDecoration.underline, ), // This text style is applied when the accordion is NOT hovered OR pressed. WidgetState.any: typography.base.copyWith( fontWeight: FontWeight.w500, color: colors.foreground, ), }), ... );

See FWidgetStateMap for more information on FWidgetStateMaps.

Pass the Style

main.dart
import 'lib/theme/accordion_style.dart'; @override Widget build(BuildContext context) => FAccordion( // Pass the modified style to the widget. style: accordionStyle( colors: context.theme.colors, typography: context.theme.typography, style: context.theme.style, ), children: const [ FAccordionItem( title: Text('Is it accessible?'), child: Text('Yes. It adheres to the WAI-ARIA design pattern.'), ), ], );

Material 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.

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

Use the CLI to generate a copy of toApproximateMaterialTheme() inside your project:

dart run forui snippet create material-mapping

This 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.

Last updated on