fossui

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  // Duration

It 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

FamilyType at accessSteps
colorsColor26 semantic roles, light and dark sets
radiidoublesm (6), md (8), lg (10), xl (14), xl2 (16), plus full (pill)
spacingdoubleone unit (4); spacing(2) is 8
typographyTextStylexs, sm, base, lg, xl, xl2 (Geist)
shadowsList<BoxShadow>xs, sm, md, lg, plus none
motionDurationskeleton, 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);  // 600

Customize

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

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

On this page