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 optionally 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 FTheme(...).
1@override2Widget build(BuildContext context) => FTheme(3 data: FThemes.neutral.light.touch, // or FThemes.neutral.dark.touch4 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 |
Each light and dark accessor also contains desktop and touch variants with font sizes and padding optimized for their
respective platforms. For example, FThemes.neutral.light.desktop is the desktop variant of the neutral light theme,
while FThemes.neutral.light.touch is the touch variant.
See Responsive for more details.
Theme Components
The main 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:FColors: Color scheme including primary, foreground, and background colors.FTypography: Typography, composed ofbodyanddisplayFTypefaces that hold the font family and text styles.FIcons: Icons used by Forui widgets.FStyle: Misc. options such as border radius and icon size.FHapticFeedback: Haptic feedback callbacks shared across widgets.FVariants: Maps variant constraints, e.g. hovered and pressed, to values.- Individual widget styles.
- Individual widget motions.
A 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;7 final FIcons icons = context.theme.icons;89 return const Placeholder();10}11Colors
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 two semantic FTypefaces:
displayfor prominent text such as headings.bodyfor content and UI text.
An FTypeface contains several TextStyles across a scale based on
Tailwind CSS Font Size. Its 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.body.xs.copyWith(8 color: context.theme.colors.primaryForeground,9 fontWeight: .bold,10 ),11 );12}13Set an FTypeface's font family via its constructor, e.g. FTypeface.inherit(fontFamily: ...), and use scale() to
quickly scale all the font sizes.
1@override2Widget build(BuildContext context) {3 final typography = FThemes.neutral.light.touch.typography;4 return FTheme(5 data: FThemeData(6 colors: FThemes.neutral.light.touch.colors,7 typography: typography8 .copyWith(9 body: typography.body.copyWith(10 xs: const TextStyle(fontSize: 12, height: 1),11 ),12 )13 .scale(sizeScalar: 0.8),14 touch: true,15 ),16 child: const FScaffold(child: Placeholder()),17 );18}19Icons
The FIcons class contains the icon that Forui widgets use. It defaults to Lucide icons in
FLucideIcons
Pass a custom FIcons to FThemeData to
swap icons across all Forui widgets.
See the customizing icons guide for more information.
Style
The FStyle class defines 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.md,10 color: colors.primary,11 ),12 child: const Placeholder(),13 );14}15Haptic Feedback
The FHapticFeedback class holds the haptic callbacks (e.g. selectionClick, mediumImpact) that widgets such as
FPicker, FSlider, and FTooltip invoke on interaction. Override it on FThemeData to customize or disable haptics
across the entire theme; pass const FHapticFeedback.none() to disable them.
Variants
FVariants lets you define a base value with optional overrides for specific variant constraints.
This is useful for expressing a wide range of styling concepts:
- User interaction states, e.g. hovered, pressed.
- Semantic states, e.g. disabled, error.
- Stylistic variants, e.g. destructive and outlined buttons.
- Platform differences, e.g. touch vs desktop.
Each widget defines its own variant type, e.g. FTappableVariant and FCalendarVariant, ensuring only valid variants
can be used. Constraints are composed using .and(...) and .not(...):
1FVariants(2 // base (default if no variants match)3 const BoxDecoration(color: Colors.white),4 variants: {5 // NOT hovered6 [.not(.hovered)]: const BoxDecoration(color: Colors.red),7 // hovered OR pressed8 [.hovered, .pressed]: const BoxDecoration(color: Colors.grey),9 // disabled AND pressed10 [.disabled.and(.pressed)]: const BoxDecoration(color: Colors.black26),11 },12);13Variants can also be expressed as deltas (modifications) applied to a base value:
1FVariants.from(2 // base (default if no variants match)3 const BoxDecoration(4 color: Colors.white,5 borderRadius: .all(.circular(8)),6 ),7 variants: {8 // NOT hovered - keeps border radius9 [.not(.hovered)]: const .delta(color: Colors.red),10 // hovered OR pressed - keeps border radius11 [.hovered, .pressed]: const .delta(color: Colors.grey),12 // disabled AND pressed - keeps border radius13 [.disabled.and(.pressed)]: const .delta(color: Colors.black26),14 },15);16Resolution uses a tiered most-specific-wins strategy which is deterministic and order-independent.
Each variant belongs to one of three tiers:
| Tier | Category | Examples |
|---|---|---|
| 2 | Semantic | disabled, selected, error |
| 1 | Interaction | hovered, focused, pressed |
| 0 | Platform | android, iOS, web |
Higher tiers always take precedence.
For example, given the states {.disabled, .pressed}, .disabled.and(.pressed) wins over .pressed because disabled
is a tier 2 (semantic) state which outranks tier 1 (interaction) states.
To learn how to customize FVariants, see the customizing widget styles
guide.
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.
A Forui theme can be converted to a Material theme using
toApproximateMaterialTheme().
The mapping is done on a best-effort basis, may not capture all nuances, and can change without prior warning.
1import 'package:flutter/material.dart';23import 'package:forui/forui.dart';45@override6Widget build(BuildContext context) => MaterialApp(7 theme: FThemes.neutral.light.touch.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);21Alternatively, you can generate a copy of toApproximateMaterialTheme() inside your project using the CLI:
dart run forui snippet create material-mappingThis is 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.