Theming
Read fossui tokens through one accessor, retheme the whole set from one source, and build your own widgets that match.
Every fossui component reads its colors, radius, type, spacing, shadow, and motion from a single theme. The same tokens are public, so your own widgets can read them too and stay on theme with the built-in ones. Retheme once and the whole set restyles, custom widgets included.
Read a token
There is one accessor, context.fossTheme. It returns a FossThemeData with six
token bundles:
final t = context.fossTheme;
t.colors.primary // Color
t.radii.lg // double (10)
t.spacing(2) // double (8)
t.typography.sm // TextStyle
t.shadows.md // List<BoxShadow>
t.motion.overlay // DurationIt resolves the FossTheme inherited widget first, then a FossThemeData
registered in ThemeData.extensions, then the light default, so it works under
MaterialApp, CupertinoApp, or a bare WidgetsApp. There is no FossApp
wrapper.
Register the theme
Register once at the app root. Material apps inject it as a theme extension;
other apps wrap the tree in FossTheme.
// Material
MaterialApp(
theme: FossThemeData.light.toThemeData(),
darkTheme: FossThemeData.dark.toThemeData(),
);
// Cupertino or bare WidgetsApp
FossTheme(
data: FossThemeData.light,
child: const MyApp(),
);The six families
| Family | Type at access | Steps |
|---|---|---|
| colors | Color | 26 semantic roles, light and dark sets |
| radii | double | sm (6), md (8), lg (10), xl (14), xl2 (16), plus full (pill) |
| spacing | double | one unit (4); spacing(2) is 8 |
| typography | TextStyle | xs, sm, base, lg, xl, xl2 (Geist) |
| shadows | List<BoxShadow> | xs, sm, md, lg, plus none |
| motion | Duration | skeleton, caretBlink, spinner, overlay, drawer, toast, progress |
Colors
Twenty-six roles, each in a light and a dark set. Surfaces pair with a
foreground: background / foreground, card / cardForeground, popover /
popoverForeground, primary / primaryForeground, secondary /
secondaryForeground, muted / mutedForeground, accent /
accentForeground.
Status roles cover destructive (with destructiveForeground and
destructiveForegroundOn), info, success, and warning, each with a paired
foreground. Lines are border, input (form-control border), and ring (focus
ring).
Typography
Weight helpers sit on any TextStyle from the scale:
Text('Label', style: t.typography.sm.medium); // 500
Text('Title', style: t.typography.xl.semibold); // 600Customize
Customization flows down from the theme. Three ways, from broad to narrow.
Retheme the whole app
Pass your own FossThemeData, or layer a FossThemeSpec over a default. The
spec is a compact override: enumerated color roles plus single seeds for
radius, spacing, shadowColor, and fontFamily.
final theme = FossThemeData.light.retheme(
const FossThemeSpec(
primary: Color(0xFF16A34A),
ring: Color(0xFF16A34A),
radius: 22,
fontFamily: 'Plus Jakarta Sans',
),
);
MaterialApp(theme: theme.toThemeData());Give light and dark their own values by layering a spec over each base:
MaterialApp(
theme: FossThemeData.light.retheme(
const FossThemeSpec(primary: Color(0xFF16A34A), radius: 22),
).toThemeData(),
darkTheme: FossThemeData.dark.retheme(
const FossThemeSpec(primary: Color(0xFF51F0A8), radius: 22),
).toThemeData(),
);The default type scale ships with its own font bundled, so it renders with no
setup on your side. A custom fontFamily is only a name, and Flutter still has
to know where that font lives: declare the font files under flutter/fonts in
pubspec.yaml and pass the same family name, or add
google_fonts and use the family it
exposes. Match the string exactly, and the whole type scale switches to it.
# pubspec.yaml
flutter:
fonts:
- family: Plus Jakarta Sans
fonts:
- asset: fonts/PlusJakartaSans-Regular.ttfStyle one instance
Each component takes a single style object (FossButtonStyle and the like)
for a one-off override, without touching the theme.
No per-instance token props
Constructors do not take borderRadius:, color:, or padding:. To change
corners or color, change the theme. This keeps every instance consistent by
default.
Build a custom widget
Read the same tokens and your widget matches the built-in ones, in light and dark and under any retheme:
Widget build(BuildContext context) {
final t = context.fossTheme;
return Container(
padding: t.spacing.all(4),
decoration: BoxDecoration(
color: t.colors.card,
borderRadius: BorderRadius.circular(t.radii.lg),
border: Border.all(color: t.colors.border),
boxShadow: t.shadows.sm,
),
child: Text('Custom', style: t.typography.sm.medium),
);
}If you use the MCP server, the build_custom_component
tool returns this recipe and get_theme_tokens returns the concrete values, so
an assistant builds matching widgets on its own.